From 74d27be2228d2e0ed88bcbc920841a527c007728 Mon Sep 17 00:00:00 2001 From: l-nmch Date: Sun, 22 Mar 2026 02:35:30 +0100 Subject: [PATCH] chore(templates): Added IaaS Instance --- .gitignore | 6 + README.md | 17 ++ iaas-instance/README.md | 56 +++++++ iaas-instance/cloud-init/user-data.tftpl | 53 ++++++ iaas-instance/main.tf | 196 +++++++++++++++++++++++ 5 files changed, 328 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 iaas-instance/README.md create mode 100644 iaas-instance/cloud-init/user-data.tftpl create mode 100644 iaas-instance/main.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f9406d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.coder/ +.terraform/ +.terraform.lock.hcl +terraform.tfvars +terraform.tfstate +terraform.tfstate.backup \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b885e23 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Coder Templates + +![Phorge logo](https://avatars.githubusercontent.com/u/187407936?s=200&v=4) + +Local registry for [Coder Wokerspaces](https://coder.phorge.fr/) + +## Temaplates + +- [Iaas-Instance](./iaas-instance/README.md) Run Workspaces on [Phorge IaaS](https://iaas.phorge.fr/) + +## Push a new template + +Go into your template, follow the `README.md` and run : + +```bash +coder template push -d . +``` \ No newline at end of file diff --git a/iaas-instance/README.md b/iaas-instance/README.md new file mode 100644 index 0000000..d993165 --- /dev/null +++ b/iaas-instance/README.md @@ -0,0 +1,56 @@ +> Based on the work of [umair](https://github.com/l-nmch/registry/tree/main/registry/umair) + +# Incus VM Template for Coder + +Provision Linux VMs & Containers on Incus/LXD as [Coder workspaces](https://coder.phorge.fr). The template deploys an instance with cloud-init, and runs the Coder agent under the workspace owner's Linux user. + +## Prerequisites + +- Incus server / cluster with exposed API + +### Setup the template + +1. Create an Incus trust token: + +```bash +incus config trust add coder # Save the token +``` + +2. Setup an Incus project with a network: + +```bash +incus project create Coder -c features.network=true +incus network create Main --project Coder +``` + +3. Prepare `terraform.tfvars` in your environment: + +```py +remote_name = "iaas" +remote_address = "https://iaas.phorge.fr:443" +remote_token = "" + +remote_coder_project = "Coder" +remote_coder_network = "Main" +remote_coder_storage_pool = "local" +remote_coder_profiles = ["1vCPU-1GiB-20GB", "2vCPU-2GiB-20GB"] +remote_coder_images = ["images:ubuntu/24.04/cloud", "images:debian/13/cloud", "images:alpine/3.23/cloud"] +``` + +## Use + +```bash +coder template push iaas-instance -d . +``` + +## Warnings + +Incus often works with cloud image, please use `cloud` tagged images such as `images:ubuntu/22.04/cloud` to be able to use cloud-init (Using non cloud tagged images will lead into your workspaces not working as the coder agent installs through cloud-init) + +## References + +- Incus: [source](https://linuxcontainers.org/incus/) +- Incus Terraform Provider: [source](https://registry.terraform.io/providers/lxc/incus/latest/docs) +- Coder – Best practices & templates: + - https://coder.com/docs/tutorials/best-practices/speed-up-templates + - https://coder.com/docs/tutorials/template-from-scratch \ No newline at end of file diff --git a/iaas-instance/cloud-init/user-data.tftpl b/iaas-instance/cloud-init/user-data.tftpl new file mode 100644 index 0000000..2cd34d0 --- /dev/null +++ b/iaas-instance/cloud-init/user-data.tftpl @@ -0,0 +1,53 @@ +#cloud-config +hostname: ${hostname} + +users: + - name: ${linux_user} + groups: [sudo] + shell: /bin/bash + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + +package_update: false +package_upgrade: false +packages: + - curl + - ca-certificates + - git + - jq + +write_files: + - path: /opt/coder/init.sh + permissions: "0755" + owner: root:root + encoding: b64 + content: | + ${coder_init_script_b64} + + - path: /etc/systemd/system/coder-agent.service + permissions: "0644" + owner: root:root + content: | + [Unit] + Description=Coder Agent + Wants=network-online.target + After=network-online.target + + [Service] + Type=simple + User=${linux_user} + WorkingDirectory=/home/${linux_user} + Environment=HOME=/home/${linux_user} + Environment=CODER_AGENT_TOKEN=${coder_token} + ExecStart=/opt/coder/init.sh + OOMScoreAdjust=-1000 + Restart=always + RestartSec=5 + + [Install] + WantedBy=multi-user.target + +runcmd: + - systemctl daemon-reload + - systemctl enable --now coder-agent.service + +final_message: "Cloud-init complete on ${hostname}" \ No newline at end of file diff --git a/iaas-instance/main.tf b/iaas-instance/main.tf new file mode 100644 index 0000000..ae0fb96 --- /dev/null +++ b/iaas-instance/main.tf @@ -0,0 +1,196 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + incus = { + source = "lxc/incus" + version = "1.0.2" + } + } +} + +provider "coder" {} + +provider "incus" { + accept_remote_certificate = true + generate_client_certificates = true + default_remote = var.remote_name + remote { + name = var.remote_name + address = var.remote_address + token = var.remote_token + } +} + +variable "remote_name" { + description = "Incus remote host/cluster name" + type = string + default = "remote" +} + +variable "remote_address" { + description = "Incus remote address (e.g. https://lxc.example.com:8443)" + type = string +} + +variable "remote_token" { + description = "Incus remote API token with permissions to manage instances" + type = string + sensitive = true +} + +variable "remote_coder_project" { + description = "Incus remote project to use for instances" + type = string + default = "default" +} + +variable "remote_coder_network" { + description = "Incus remote network to attach instances to" + type = string +} + +variable "remote_coder_profiles" { + description = "Incus remote profiles to use for instances" + type = list(string) + default = [] +} + +variable "remote_coder_images" { + description = "Incus remote images to use for instances" + type = list(string) + default = [] +} + +variable "remote_coder_instance_type" { + description = "Incus remote instance type (e.g. virtual-machine or container)" + type = string + default = "virtual-machine" +} + +variable "remote_coder_storage_pool" { + description = "Incus remote storage pool to use for instances" + type = string +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# data "coder_parameter" "isolated" { # TO BE IMPLEMENTED - requires network configuration in the module +# name = "isolated" +# display_name = "Isolated Environment(dedicated network)" +# type = "bool" +# mutable = true +# default = false +# } + +data "coder_parameter" "profile" { + name = "profile" + display_name = "Instance Profile" + type = "string" + mutable = true + + dynamic "option" { + for_each = [for p in var.remote_coder_profiles : { name = title(p), value = p }] + content { + name = option.value.name + value = option.value.value + } + } +} + +data "coder_parameter" "image" { + name = "image" + display_name = "Instance Image" + type = "string" + mutable = true + + dynamic "option" { + for_each = [for img in var.remote_coder_images : { name = title(img), value = img }] + content { + name = option.value.name + value = option.value.value + } + } +} + +resource "coder_agent" "dev" { + arch = "amd64" + os = "linux" + + env = { + GIT_AUTHOR_NAME = data.coder_workspace_owner.me.name + GIT_AUTHOR_EMAIL = data.coder_workspace_owner.me.email + } + + startup_script_behavior = "non-blocking" + startup_script = <<-EOT + set -e + # Add any startup scripts here + EOT + + metadata { + display_name = "CPU Usage" + key = "cpu_usage" + script = "coder stat cpu" + interval = 10 + timeout = 1 + order = 1 + } + + metadata { + display_name = "RAM Usage" + key = "ram_usage" + script = "coder stat mem" + interval = 10 + timeout = 1 + order = 2 + } + + metadata { + display_name = "Disk Usage" + key = "disk_usage" + script = "coder stat disk" + interval = 600 + timeout = 30 + order = 3 + } +} + +locals { + hostname = lower(data.coder_workspace.me.name) + vm_name = "coder-${lower(data.coder_workspace_owner.me.name)}-${local.hostname}" + snippet_filename = "${local.vm_name}.yml" + base_user = replace(replace(replace(lower(data.coder_workspace_owner.me.name), " ", "-"), "/", "-"), "@", "-") # to avoid special characters in the username + linux_user = contains(["root", "admin", "daemon", "bin", "sys"], local.base_user) ? "${local.base_user}1" : local.base_user # to avoid conflict with system users + + rendered_user_data = templatefile("${path.module}/cloud-init/user-data.tftpl", { + coder_token = coder_agent.dev.token + coder_init_script_b64 = base64encode(coder_agent.dev.init_script) + hostname = local.vm_name + linux_user = local.linux_user + }) +} + +resource "incus_instance" "workspace" { + count = data.coder_workspace.me.start_count + remote = var.remote_name + name = local.vm_name + image = data.coder_parameter.image.value + type = var.remote_coder_instance_type + profiles = data.coder_parameter.profile.value != "" ? [data.coder_parameter.profile.value] : [] # if profile is not selected, use no profile instead of default + project = var.remote_coder_project + running = true + + config = { + "cloud-init.user-data" = local.rendered_user_data + } +} + +module "code-server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/code-server/coder" + version = "1.3.1" + agent_id = coder_agent.dev.id +} \ No newline at end of file