From 83291cc681f5c15d377863dfba5d69c807c08fe1 Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Tue, 2 Jun 2026 21:40:46 +0000 Subject: [PATCH 1/7] Add bpmct/incus-vm template --- registry/bpmct/README.md | 2 +- registry/bpmct/templates/incus-vm/README.md | 133 ++++++++ registry/bpmct/templates/incus-vm/main.tf | 348 ++++++++++++++++++++ 3 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 registry/bpmct/templates/incus-vm/README.md create mode 100644 registry/bpmct/templates/incus-vm/main.tf diff --git a/registry/bpmct/README.md b/registry/bpmct/README.md index a19a70c07..a7af289bf 100644 --- a/registry/bpmct/README.md +++ b/registry/bpmct/README.md @@ -8,4 +8,4 @@ status: community # Ben Potter -Tinkerer and Product Manager at Coder. Building modules to make dev environments better. +Tinkerer and Product Manager at Coder. diff --git a/registry/bpmct/templates/incus-vm/README.md b/registry/bpmct/templates/incus-vm/README.md new file mode 100644 index 000000000..5a660bb52 --- /dev/null +++ b/registry/bpmct/templates/incus-vm/README.md @@ -0,0 +1,133 @@ +--- +display_name: Incus VM +description: Run a full virtual machine on a remote Incus host +icon: ../../../../.icons/lxc.svg +verified: false +tags: [local, incus, vm, virtual-machine] +--- + +# Incus VM + +Provision a full KVM virtual machine on an [Incus](https://linuxcontainers.org/incus/) host. Unlike the system container template, this creates an isolated VM with its own kernel. Images are pulled from [images.linuxcontainers.org](https://images.linuxcontainers.org) and cached on the host. + +Unlike the upstream `coder/incus` template, this variant: + +- Launches a **virtual machine** (`type = "virtual-machine"`) instead of a system container +- Supports **remote Incus hosts** via `incus remote add` — the Coder provisioner does not need to be on the same machine as Incus +- Handles **token rotation** on every workspace start via a `null_resource` provisioner + +## Prerequisites + +### 1. Install Incus on the VM host + +Follow the [Incus installation guide](https://linuxcontainers.org/incus/docs/main/installing/) for your distro. On Debian/Ubuntu: + +```sh +sudo apt-get install incus +sudo incus admin init +``` + +Verify it's running: + +```sh +incus list +``` + +### 2. Add the host as a remote on the Coder provisioner + +The Coder provisioner (the machine running `coderd` or a provisioner daemon) needs to reach the Incus API on the VM host. Run these commands **on the Coder provisioner**. + +**On the VM host** — generate a trust token: + +```sh +incus config trust add coder-provisioner +``` + +This prints a one-time token. Copy it. + +**On the Coder provisioner** — add the remote using that token: + +```sh +incus remote add my-server https://:8443 --token +``` + +Verify connectivity: + +```sh +incus list my-server: +``` + +> **Tip:** The remote name you use here (`my-server` in the example) is what you'll enter in the **Incus Remote** workspace parameter. Add one `option` block per remote in `main.tf`. + +### 3. Create a storage pool on the VM host + +The template uses an Incus storage pool to back the VM root disk. If you don't already have one, create it on the VM host: + +```sh +incus storage create default btrfs +``` + +Or to back it with a specific directory or block device: + +```sh +incus storage create hdd btrfs source=/data/incus-pool +``` + +List existing pools: + +```sh +incus storage list +``` + +Set the pool name in the **Storage Pool** workspace parameter (default: `default`). + +### 4. Ensure the VM host has KVM + +VMs require hardware virtualisation. Check on the host: + +```sh +ls /dev/kvm +``` + +If the device is missing, enable virtualisation in your BIOS/UEFI or, in a nested setup, pass through the kvm module. + +### 5. Add image options (optional) + +Images are cached automatically from `images.linuxcontainers.org` when a workspace is first created. You can pre-cache an image manually on the host to speed up the first launch: + +```sh +incus image copy images:ubuntu/noble/cloud/amd64 local: --vm --alias ubuntu/noble/cloud/amd64 +``` + +List cached images: + +```sh +incus image list +``` + +To add a custom image option, edit `main.tf` and add an `option` block inside `data.coder_parameter.image`. + +## Usage + +1. Push this template to your Coder deployment: + + ```sh + coder templates push incus-vm --directory . + ``` + +2. Create a workspace and select your Incus remote, image, and resource sizes. + +3. Connect via `coder ssh ` or open VS Code in the browser. + +## Adding additional remotes + +Edit the `data.coder_parameter.remote` block in `main.tf` and add an `option` for each host: + +```terraform +option { + name = "my-server" + value = "my-server" +} +``` + +The `value` must exactly match the remote name shown in `incus remote list` on the provisioner. diff --git a/registry/bpmct/templates/incus-vm/main.tf b/registry/bpmct/templates/incus-vm/main.tf new file mode 100644 index 000000000..f04ea76da --- /dev/null +++ b/registry/bpmct/templates/incus-vm/main.tf @@ -0,0 +1,348 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.4.0" + } + incus = { + source = "lxc/incus" + version = "~> 1.0" + } + null = { + source = "hashicorp/null" + version = "~> 3.0" + } + } +} + +provider "incus" {} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# --------------------------------------------------------------------------- +# Parameters +# --------------------------------------------------------------------------- + +data "coder_parameter" "remote" { + name = "remote" + display_name = "Incus Remote" + description = "The Incus remote to run this VM on. Must be configured on the Coder provisioner with `incus remote add`." + type = "string" + default = "local" + mutable = false + order = 1 + + option { + name = "local (same machine as Coder)" + value = "local" + } + + # Add more remotes here — one option per host you have added via `incus remote add`. + # option { + # name = "my-server" + # value = "my-server" + # } +} + +data "coder_parameter" "image" { + name = "image" + display_name = "Image" + description = "The VM image to use. Images are pulled from https://images.linuxcontainers.org and cached on the host. Use the `images:` remote format, e.g. `ubuntu/noble/cloud/amd64`." + type = "string" + default = "ubuntu/noble/cloud/amd64" + icon = "/icon/image.svg" + mutable = true + order = 2 + + option { + name = "Ubuntu 24.04 LTS (Noble)" + value = "ubuntu/noble/cloud/amd64" + icon = "/icon/ubuntu.svg" + } + + option { + name = "Ubuntu 22.04 LTS (Jammy)" + value = "ubuntu/jammy/cloud/amd64" + icon = "/icon/ubuntu.svg" + } + + option { + name = "Debian 12 (Bookworm)" + value = "debian/12/cloud/amd64" + icon = "/icon/debian.svg" + } +} + +data "coder_parameter" "cpu" { + name = "cpu" + display_name = "CPU" + description = "Number of vCPUs to allocate." + type = "number" + default = 2 + icon = "https://raw.githubusercontent.com/matifali/logos/main/cpu-3.svg" + mutable = true + order = 3 + validation { + min = 1 + max = 16 + } +} + +data "coder_parameter" "memory" { + name = "memory" + display_name = "Memory (GB)" + description = "Amount of memory in GB." + type = "number" + default = 4 + icon = "/icon/memory.svg" + mutable = true + order = 4 + validation { + min = 1 + max = 64 + } +} + +data "coder_parameter" "disk" { + name = "disk" + display_name = "Disk (GB)" + description = "Root disk size in GB." + type = "number" + default = 30 + icon = "/icon/database.svg" + mutable = true + order = 5 + validation { + min = 10 + max = 500 + } +} + +data "coder_parameter" "storage_pool" { + name = "storage_pool" + display_name = "Storage Pool" + description = "Incus storage pool to use for the VM root disk." + type = "string" + default = "default" + mutable = false + order = 6 +} + +# --------------------------------------------------------------------------- +# Agent +# --------------------------------------------------------------------------- + +resource "coder_agent" "main" { + count = data.coder_workspace.me.start_count + arch = "amd64" + os = "linux" + dir = "/home/${local.workspace_user}" + + metadata { + display_name = "CPU Usage" + key = "0_cpu_usage" + script = "coder stat cpu" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "RAM Usage" + key = "1_ram_usage" + script = "coder stat mem" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "Disk" + key = "2_disk" + script = "coder stat disk --path /" + interval = 60 + timeout = 1 + } +} + +module "code-server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/code-server/coder" + version = "~> 1.0" + agent_id = coder_agent.main[0].id +} + +# --------------------------------------------------------------------------- +# Image — cached from images.linuxcontainers.org +# --------------------------------------------------------------------------- + +resource "incus_cached_image" "image" { + remote = data.coder_parameter.remote.value + source_remote = "images" + source_image = data.coder_parameter.image.value + type = "virtual-machine" +} + +# --------------------------------------------------------------------------- +# VM instance +# --------------------------------------------------------------------------- + +resource "incus_instance" "dev" { + remote = data.coder_parameter.remote.value + running = data.coder_workspace.me.start_count == 1 + name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}" + image = incus_cached_image.image.fingerprint + type = "virtual-machine" + + config = { + "limits.cpu" = tostring(data.coder_parameter.cpu.value) + "limits.memory" = "${data.coder_parameter.memory.value}GiB" + "security.secureboot" = false + "boot.autostart" = data.coder_workspace.me.start_count == 1 + "user.coder-agent-token" = local.agent_token + + "cloud-init.user-data" = <<-EOF + #cloud-config + hostname: ${lower(data.coder_workspace.me.name)} + users: + - name: ${local.workspace_user} + uid: 1000 + groups: sudo + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + write_files: + - path: /opt/coder/init + permissions: "0755" + encoding: b64 + content: ${base64encode(local.agent_init_script)} + - path: /opt/coder/init.env + permissions: "0600" + content: | + CODER_AGENT_TOKEN=${local.agent_token} + CODER_AGENT_URL=${data.coder_workspace.me.access_url} + - path: /etc/systemd/system/coder-agent.service + permissions: "0644" + content: | + [Unit] + Description=Coder Agent + After=network-online.target + Wants=network-online.target + [Service] + User=${local.workspace_user} + EnvironmentFile=/opt/coder/init.env + ExecStart=/opt/coder/init + Restart=always + RestartSec=10 + TimeoutStopSec=90 + KillMode=process + OOMScoreAdjust=-900 + SyslogIdentifier=coder-agent + [Install] + WantedBy=multi-user.target + runcmd: + - systemctl enable --now coder-agent.service + EOF + } + + device { + name = "root" + type = "disk" + properties = { + path = "/" + pool = data.coder_parameter.storage_pool.value + size = "${data.coder_parameter.disk.value}GiB" + } + } + + lifecycle { + ignore_changes = [ + config["cloud-init.user-data"], + config["user.coder-agent-token"], + image, + ] + } +} + +# --------------------------------------------------------------------------- +# Token refresh on every start (handles token rotation without rebuilding) +# --------------------------------------------------------------------------- + +resource "null_resource" "token_refresh" { + count = data.coder_workspace.me.start_count + + triggers = { + agent_token = local.agent_token + instance = incus_instance.dev.name + } + + depends_on = [incus_instance.dev] + + provisioner "local-exec" { + command = <<-EOT + REMOTE="${data.coder_parameter.remote.value}" + INSTANCE="${incus_instance.dev.name}" + echo "Waiting for VM agent to be ready..." + for i in $(seq 1 40); do + if incus exec "$REMOTE:$INSTANCE" -- true 2>/dev/null; then + break + fi + echo "Attempt $i: not ready, waiting..." + sleep 5 + done + echo "Waiting for cloud-init..." + incus exec "$REMOTE:$INSTANCE" -- bash -c ' + for i in $(seq 1 60); do + [ -f /var/lib/cloud/instance/boot-finished ] && break + sleep 5 + done + ' + echo "Refreshing agent token..." + printf 'CODER_AGENT_TOKEN=${local.agent_token}\nCODER_AGENT_URL=${data.coder_workspace.me.access_url}\n' \ + | incus exec "$REMOTE:$INSTANCE" -- bash -c 'cat > /opt/coder/init.env && chmod 600 /opt/coder/init.env' + incus exec "$REMOTE:$INSTANCE" -- systemctl restart coder-agent.service + EOT + } +} + +# --------------------------------------------------------------------------- +# Metadata +# --------------------------------------------------------------------------- + +resource "coder_metadata" "info" { + count = data.coder_workspace.me.start_count + resource_id = incus_instance.dev.name + + item { + key = "instance" + value = incus_instance.dev.name + } + item { + key = "remote" + value = data.coder_parameter.remote.value + } + item { + key = "image" + value = "images:${data.coder_parameter.image.value}" + } + item { + key = "cpu" + value = tostring(data.coder_parameter.cpu.value) + } + item { + key = "memory" + value = "${data.coder_parameter.memory.value} GiB" + } + item { + key = "disk" + value = "${data.coder_parameter.disk.value} GiB" + } +} + +# --------------------------------------------------------------------------- +# Locals +# --------------------------------------------------------------------------- + +locals { + workspace_user = lower(data.coder_workspace_owner.me.name) + agent_token = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].token : "" + agent_init_script = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].init_script : "" +} From 250403a0354943b80cf9df4d098a17b581df284e Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Tue, 2 Jun 2026 22:13:20 +0000 Subject: [PATCH 2/7] Remove hardcoded remote options; add commented example --- registry/bpmct/templates/incus-vm/main.tf | 89 ++++++++++++++--------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/registry/bpmct/templates/incus-vm/main.tf b/registry/bpmct/templates/incus-vm/main.tf index f04ea76da..c4be0c0ea 100644 --- a/registry/bpmct/templates/incus-vm/main.tf +++ b/registry/bpmct/templates/incus-vm/main.tf @@ -27,49 +27,65 @@ data "coder_workspace_owner" "me" {} data "coder_parameter" "remote" { name = "remote" display_name = "Incus Remote" - description = "The Incus remote to run this VM on. Must be configured on the Coder provisioner with `incus remote add`." + description = "The Incus remote to run this VM on. Must match a remote configured via `incus remote add` on the provisioner." type = "string" default = "local" mutable = false order = 1 - option { - name = "local (same machine as Coder)" - value = "local" - } - - # Add more remotes here — one option per host you have added via `incus remote add`. + # Add one option block per remote you have configured on the provisioner. + # Run `incus remote list` on the provisioner to see what's available. # option { # name = "my-server" # value = "my-server" # } } +data "coder_parameter" "arch" { + name = "arch" + display_name = "Architecture" + description = "CPU architecture of the VM. Must match the Incus remote host." + type = "string" + default = "amd64" + mutable = false + order = 2 + + option { + name = "amd64 (x86-64)" + value = "amd64" + } + + option { + name = "arm64 (aarch64)" + value = "arm64" + } +} + data "coder_parameter" "image" { name = "image" display_name = "Image" - description = "The VM image to use. Images are pulled from https://images.linuxcontainers.org and cached on the host. Use the `images:` remote format, e.g. `ubuntu/noble/cloud/amd64`." + description = "Base image name from images.linuxcontainers.org — without the arch suffix (e.g. `ubuntu/noble/cloud`). The selected architecture is appended automatically." type = "string" - default = "ubuntu/noble/cloud/amd64" + default = "ubuntu/noble/cloud" icon = "/icon/image.svg" mutable = true - order = 2 + order = 3 option { name = "Ubuntu 24.04 LTS (Noble)" - value = "ubuntu/noble/cloud/amd64" + value = "ubuntu/noble/cloud" icon = "/icon/ubuntu.svg" } option { name = "Ubuntu 22.04 LTS (Jammy)" - value = "ubuntu/jammy/cloud/amd64" + value = "ubuntu/jammy/cloud" icon = "/icon/ubuntu.svg" } option { name = "Debian 12 (Bookworm)" - value = "debian/12/cloud/amd64" + value = "debian/12/cloud" icon = "/icon/debian.svg" } } @@ -77,12 +93,12 @@ data "coder_parameter" "image" { data "coder_parameter" "cpu" { name = "cpu" display_name = "CPU" - description = "Number of vCPUs to allocate." + description = "Number of vCPUs." type = "number" default = 2 icon = "https://raw.githubusercontent.com/matifali/logos/main/cpu-3.svg" mutable = true - order = 3 + order = 4 validation { min = 1 max = 16 @@ -92,12 +108,11 @@ data "coder_parameter" "cpu" { data "coder_parameter" "memory" { name = "memory" display_name = "Memory (GB)" - description = "Amount of memory in GB." type = "number" default = 4 icon = "/icon/memory.svg" mutable = true - order = 4 + order = 5 validation { min = 1 max = 64 @@ -107,12 +122,11 @@ data "coder_parameter" "memory" { data "coder_parameter" "disk" { name = "disk" display_name = "Disk (GB)" - description = "Root disk size in GB." type = "number" default = 30 icon = "/icon/database.svg" mutable = true - order = 5 + order = 6 validation { min = 10 max = 500 @@ -122,11 +136,11 @@ data "coder_parameter" "disk" { data "coder_parameter" "storage_pool" { name = "storage_pool" display_name = "Storage Pool" - description = "Incus storage pool to use for the VM root disk." + description = "Incus storage pool for the root disk. Run `incus storage list` on the host to see available pools." type = "string" default = "default" mutable = false - order = 6 + order = 7 } # --------------------------------------------------------------------------- @@ -135,7 +149,7 @@ data "coder_parameter" "storage_pool" { resource "coder_agent" "main" { count = data.coder_workspace.me.start_count - arch = "amd64" + arch = data.coder_parameter.arch.value os = "linux" dir = "/home/${local.workspace_user}" @@ -172,14 +186,17 @@ module "code-server" { } # --------------------------------------------------------------------------- -# Image — cached from images.linuxcontainers.org +# Image # --------------------------------------------------------------------------- -resource "incus_cached_image" "image" { - remote = data.coder_parameter.remote.value - source_remote = "images" - source_image = data.coder_parameter.image.value - type = "virtual-machine" +resource "incus_image" "image" { + remote = data.coder_parameter.remote.value + source_image = { + remote = "images" + name = "${data.coder_parameter.image.value}/${data.coder_parameter.arch.value}" + type = "virtual-machine" + architecture = data.coder_parameter.arch.value == "amd64" ? "x86_64" : "aarch64" + } } # --------------------------------------------------------------------------- @@ -190,7 +207,7 @@ resource "incus_instance" "dev" { remote = data.coder_parameter.remote.value running = data.coder_workspace.me.start_count == 1 name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}" - image = incus_cached_image.image.fingerprint + image = incus_image.image.fingerprint type = "virtual-machine" config = { @@ -263,7 +280,7 @@ resource "incus_instance" "dev" { } # --------------------------------------------------------------------------- -# Token refresh on every start (handles token rotation without rebuilding) +# Token refresh on every start # --------------------------------------------------------------------------- resource "null_resource" "token_refresh" { @@ -280,11 +297,9 @@ resource "null_resource" "token_refresh" { command = <<-EOT REMOTE="${data.coder_parameter.remote.value}" INSTANCE="${incus_instance.dev.name}" - echo "Waiting for VM agent to be ready..." + echo "Waiting for VM agent..." for i in $(seq 1 40); do - if incus exec "$REMOTE:$INSTANCE" -- true 2>/dev/null; then - break - fi + incus exec "$REMOTE:$INSTANCE" -- true 2>/dev/null && break echo "Attempt $i: not ready, waiting..." sleep 5 done @@ -321,7 +336,11 @@ resource "coder_metadata" "info" { } item { key = "image" - value = "images:${data.coder_parameter.image.value}" + value = "images:${data.coder_parameter.image.value}/${data.coder_parameter.arch.value}" + } + item { + key = "arch" + value = data.coder_parameter.arch.value } item { key = "cpu" From 72952be51bd47a0177e57581d71fc60d795c71eb Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Tue, 2 Jun 2026 22:19:44 +0000 Subject: [PATCH 3/7] Remove section divider comments --- registry/bpmct/templates/incus-vm/main.tf | 28 ----------------------- 1 file changed, 28 deletions(-) diff --git a/registry/bpmct/templates/incus-vm/main.tf b/registry/bpmct/templates/incus-vm/main.tf index c4be0c0ea..04389a481 100644 --- a/registry/bpmct/templates/incus-vm/main.tf +++ b/registry/bpmct/templates/incus-vm/main.tf @@ -20,10 +20,6 @@ provider "incus" {} data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} -# --------------------------------------------------------------------------- -# Parameters -# --------------------------------------------------------------------------- - data "coder_parameter" "remote" { name = "remote" display_name = "Incus Remote" @@ -143,10 +139,6 @@ data "coder_parameter" "storage_pool" { order = 7 } -# --------------------------------------------------------------------------- -# Agent -# --------------------------------------------------------------------------- - resource "coder_agent" "main" { count = data.coder_workspace.me.start_count arch = data.coder_parameter.arch.value @@ -185,10 +177,6 @@ module "code-server" { agent_id = coder_agent.main[0].id } -# --------------------------------------------------------------------------- -# Image -# --------------------------------------------------------------------------- - resource "incus_image" "image" { remote = data.coder_parameter.remote.value source_image = { @@ -199,10 +187,6 @@ resource "incus_image" "image" { } } -# --------------------------------------------------------------------------- -# VM instance -# --------------------------------------------------------------------------- - resource "incus_instance" "dev" { remote = data.coder_parameter.remote.value running = data.coder_workspace.me.start_count == 1 @@ -279,10 +263,6 @@ resource "incus_instance" "dev" { } } -# --------------------------------------------------------------------------- -# Token refresh on every start -# --------------------------------------------------------------------------- - resource "null_resource" "token_refresh" { count = data.coder_workspace.me.start_count @@ -318,10 +298,6 @@ resource "null_resource" "token_refresh" { } } -# --------------------------------------------------------------------------- -# Metadata -# --------------------------------------------------------------------------- - resource "coder_metadata" "info" { count = data.coder_workspace.me.start_count resource_id = incus_instance.dev.name @@ -356,10 +332,6 @@ resource "coder_metadata" "info" { } } -# --------------------------------------------------------------------------- -# Locals -# --------------------------------------------------------------------------- - locals { workspace_user = lower(data.coder_workspace_owner.me.name) agent_token = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].token : "" From eb0f84a256b37451d7dae5845e31c386efcb67f9 Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Tue, 2 Jun 2026 22:42:13 +0000 Subject: [PATCH 4/7] refactor: drop remote param, replace arch param with template variable --- registry/bpmct/templates/incus-vm/README.md | 93 +++++++-------------- registry/bpmct/templates/incus-vm/main.tf | 84 ++++++------------- 2 files changed, 53 insertions(+), 124 deletions(-) diff --git a/registry/bpmct/templates/incus-vm/README.md b/registry/bpmct/templates/incus-vm/README.md index 5a660bb52..bb30cf51d 100644 --- a/registry/bpmct/templates/incus-vm/README.md +++ b/registry/bpmct/templates/incus-vm/README.md @@ -1,6 +1,6 @@ --- display_name: Incus VM -description: Run a full virtual machine on a remote Incus host +description: Run a full virtual machine on a local Incus host icon: ../../../../.icons/lxc.svg verified: false tags: [local, incus, vm, virtual-machine] @@ -10,12 +10,6 @@ tags: [local, incus, vm, virtual-machine] Provision a full KVM virtual machine on an [Incus](https://linuxcontainers.org/incus/) host. Unlike the system container template, this creates an isolated VM with its own kernel. Images are pulled from [images.linuxcontainers.org](https://images.linuxcontainers.org) and cached on the host. -Unlike the upstream `coder/incus` template, this variant: - -- Launches a **virtual machine** (`type = "virtual-machine"`) instead of a system container -- Supports **remote Incus hosts** via `incus remote add` — the Coder provisioner does not need to be on the same machine as Incus -- Handles **token rotation** on every workspace start via a `null_resource` provisioner - ## Prerequisites ### 1. Install Incus on the VM host @@ -27,60 +21,28 @@ sudo apt-get install incus sudo incus admin init ``` -Verify it's running: +Verify it's working: ```sh incus list ``` -### 2. Add the host as a remote on the Coder provisioner - -The Coder provisioner (the machine running `coderd` or a provisioner daemon) needs to reach the Incus API on the VM host. Run these commands **on the Coder provisioner**. - -**On the VM host** — generate a trust token: - -```sh -incus config trust add coder-provisioner -``` - -This prints a one-time token. Copy it. - -**On the Coder provisioner** — add the remote using that token: - -```sh -incus remote add my-server https://:8443 --token -``` - -Verify connectivity: - -```sh -incus list my-server: -``` - -> **Tip:** The remote name you use here (`my-server` in the example) is what you'll enter in the **Incus Remote** workspace parameter. Add one `option` block per remote in `main.tf`. - -### 3. Create a storage pool on the VM host +### 2. Run the Coder provisioner on the same machine -The template uses an Incus storage pool to back the VM root disk. If you don't already have one, create it on the VM host: +This template uses Incus via the local Unix socket, so the Coder provisioner (or `coderd` itself) must run on the same machine as Incus. The simplest setup is a [provisioner daemon](https://coder.com/docs/admin/provisioners) running directly on the Incus host. -```sh -incus storage create default btrfs -``` +### 3. Set the architecture when pushing the template -Or to back it with a specific directory or block device: +The `arch` variable must match your Incus host's CPU architecture. Pass it when pushing: ```sh -incus storage create hdd btrfs source=/data/incus-pool -``` - -List existing pools: +# For amd64 (x86-64) hosts: +coder templates push incus-vm --directory . --variable arch=amd64 -```sh -incus storage list +# For arm64 (aarch64) hosts: +coder templates push incus-vm --directory . --variable arch=arm64 ``` -Set the pool name in the **Storage Pool** workspace parameter (default: `default`). - ### 4. Ensure the VM host has KVM VMs require hardware virtualisation. Check on the host: @@ -89,45 +51,46 @@ VMs require hardware virtualisation. Check on the host: ls /dev/kvm ``` -If the device is missing, enable virtualisation in your BIOS/UEFI or, in a nested setup, pass through the kvm module. +If the device is missing, enable virtualisation in your BIOS/UEFI or, in a nested setup, pass through the `kvm` module. -### 5. Add image options (optional) +### 5. Create a storage pool (if needed) -Images are cached automatically from `images.linuxcontainers.org` when a workspace is first created. You can pre-cache an image manually on the host to speed up the first launch: +The template uses an Incus storage pool to back the VM root disk. If you don't already have one: ```sh -incus image copy images:ubuntu/noble/cloud/amd64 local: --vm --alias ubuntu/noble/cloud/amd64 +incus storage create default btrfs ``` -List cached images: +List existing pools: ```sh -incus image list +incus storage list ``` -To add a custom image option, edit `main.tf` and add an `option` block inside `data.coder_parameter.image`. +The pool name defaults to `default` and can be changed per-workspace via the **Storage Pool** parameter. ## Usage 1. Push this template to your Coder deployment: ```sh - coder templates push incus-vm --directory . + coder templates push incus-vm --directory . --variable arch=amd64 ``` -2. Create a workspace and select your Incus remote, image, and resource sizes. +2. Create a workspace and select an image and resource sizes. 3. Connect via `coder ssh ` or open VS Code in the browser. -## Adding additional remotes +## Advanced: using a remote Incus host + +By default this template connects to the local Incus socket. If you want the provisioner to target a separate Incus host over the network, add a `remote` parameter and use `incus remote add` to register the host on the provisioner machine: -Edit the `data.coder_parameter.remote` block in `main.tf` and add an `option` for each host: +```sh +# On the Incus host — generate a trust token: +incus config trust add coder-provisioner -```terraform -option { - name = "my-server" - value = "my-server" -} +# On the provisioner — add the remote: +incus remote add my-server https://:8443 --token ``` -The `value` must exactly match the remote name shown in `incus remote list` on the provisioner. +Then update `main.tf` to accept a `remote` parameter and pass it to the `incus_image` and `incus_instance` resources. See the [Incus remote docs](https://linuxcontainers.org/incus/docs/main/remotes/) for details. diff --git a/registry/bpmct/templates/incus-vm/main.tf b/registry/bpmct/templates/incus-vm/main.tf index 04389a481..fc684e9ab 100644 --- a/registry/bpmct/templates/incus-vm/main.tf +++ b/registry/bpmct/templates/incus-vm/main.tf @@ -17,55 +17,28 @@ terraform { provider "incus" {} -data "coder_workspace" "me" {} -data "coder_workspace_owner" "me" {} - -data "coder_parameter" "remote" { - name = "remote" - display_name = "Incus Remote" - description = "The Incus remote to run this VM on. Must match a remote configured via `incus remote add` on the provisioner." - type = "string" - default = "local" - mutable = false - order = 1 - - # Add one option block per remote you have configured on the provisioner. - # Run `incus remote list` on the provisioner to see what's available. - # option { - # name = "my-server" - # value = "my-server" - # } -} - -data "coder_parameter" "arch" { - name = "arch" - display_name = "Architecture" - description = "CPU architecture of the VM. Must match the Incus remote host." - type = "string" - default = "amd64" - mutable = false - order = 2 - - option { - name = "amd64 (x86-64)" - value = "amd64" - } - - option { - name = "arm64 (aarch64)" - value = "arm64" +variable "arch" { + description = "CPU architecture of the VM host. Set this when pushing the template to match your Incus host. Valid values: amd64, arm64." + type = string + default = "amd64" + validation { + condition = contains(["amd64", "arm64"], var.arch) + error_message = "arch must be amd64 or arm64." } } +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + data "coder_parameter" "image" { name = "image" display_name = "Image" - description = "Base image name from images.linuxcontainers.org — without the arch suffix (e.g. `ubuntu/noble/cloud`). The selected architecture is appended automatically." + description = "Base image name from images.linuxcontainers.org (e.g. `ubuntu/noble/cloud`). The template architecture is appended automatically." type = "string" default = "ubuntu/noble/cloud" icon = "/icon/image.svg" mutable = true - order = 3 + order = 1 option { name = "Ubuntu 24.04 LTS (Noble)" @@ -94,7 +67,7 @@ data "coder_parameter" "cpu" { default = 2 icon = "https://raw.githubusercontent.com/matifali/logos/main/cpu-3.svg" mutable = true - order = 4 + order = 2 validation { min = 1 max = 16 @@ -108,7 +81,7 @@ data "coder_parameter" "memory" { default = 4 icon = "/icon/memory.svg" mutable = true - order = 5 + order = 3 validation { min = 1 max = 64 @@ -122,7 +95,7 @@ data "coder_parameter" "disk" { default = 30 icon = "/icon/database.svg" mutable = true - order = 6 + order = 4 validation { min = 10 max = 500 @@ -136,12 +109,12 @@ data "coder_parameter" "storage_pool" { type = "string" default = "default" mutable = false - order = 7 + order = 5 } resource "coder_agent" "main" { count = data.coder_workspace.me.start_count - arch = data.coder_parameter.arch.value + arch = var.arch os = "linux" dir = "/home/${local.workspace_user}" @@ -178,17 +151,15 @@ module "code-server" { } resource "incus_image" "image" { - remote = data.coder_parameter.remote.value source_image = { remote = "images" - name = "${data.coder_parameter.image.value}/${data.coder_parameter.arch.value}" + name = "${data.coder_parameter.image.value}/${var.arch}" type = "virtual-machine" - architecture = data.coder_parameter.arch.value == "amd64" ? "x86_64" : "aarch64" + architecture = var.arch == "amd64" ? "x86_64" : "aarch64" } } resource "incus_instance" "dev" { - remote = data.coder_parameter.remote.value running = data.coder_workspace.me.start_count == 1 name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}" image = incus_image.image.fingerprint @@ -275,16 +246,15 @@ resource "null_resource" "token_refresh" { provisioner "local-exec" { command = <<-EOT - REMOTE="${data.coder_parameter.remote.value}" INSTANCE="${incus_instance.dev.name}" echo "Waiting for VM agent..." for i in $(seq 1 40); do - incus exec "$REMOTE:$INSTANCE" -- true 2>/dev/null && break + incus exec "$INSTANCE" -- true 2>/dev/null && break echo "Attempt $i: not ready, waiting..." sleep 5 done echo "Waiting for cloud-init..." - incus exec "$REMOTE:$INSTANCE" -- bash -c ' + incus exec "$INSTANCE" -- bash -c ' for i in $(seq 1 60); do [ -f /var/lib/cloud/instance/boot-finished ] && break sleep 5 @@ -292,8 +262,8 @@ resource "null_resource" "token_refresh" { ' echo "Refreshing agent token..." printf 'CODER_AGENT_TOKEN=${local.agent_token}\nCODER_AGENT_URL=${data.coder_workspace.me.access_url}\n' \ - | incus exec "$REMOTE:$INSTANCE" -- bash -c 'cat > /opt/coder/init.env && chmod 600 /opt/coder/init.env' - incus exec "$REMOTE:$INSTANCE" -- systemctl restart coder-agent.service + | incus exec "$INSTANCE" -- bash -c 'cat > /opt/coder/init.env && chmod 600 /opt/coder/init.env' + incus exec "$INSTANCE" -- systemctl restart coder-agent.service EOT } } @@ -306,17 +276,13 @@ resource "coder_metadata" "info" { key = "instance" value = incus_instance.dev.name } - item { - key = "remote" - value = data.coder_parameter.remote.value - } item { key = "image" - value = "images:${data.coder_parameter.image.value}/${data.coder_parameter.arch.value}" + value = "images:${data.coder_parameter.image.value}/${var.arch}" } item { key = "arch" - value = data.coder_parameter.arch.value + value = var.arch } item { key = "cpu" From 603aa04c84606a34147ff88f67c25c81e22fed5d Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Tue, 2 Jun 2026 22:45:17 +0000 Subject: [PATCH 5/7] refactor: storage_pool as template variable, drop deprecated agent dir --- registry/bpmct/templates/incus-vm/README.md | 2 +- registry/bpmct/templates/incus-vm/main.tf | 23 ++++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/registry/bpmct/templates/incus-vm/README.md b/registry/bpmct/templates/incus-vm/README.md index bb30cf51d..db41328ce 100644 --- a/registry/bpmct/templates/incus-vm/README.md +++ b/registry/bpmct/templates/incus-vm/README.md @@ -67,7 +67,7 @@ List existing pools: incus storage list ``` -The pool name defaults to `default` and can be changed per-workspace via the **Storage Pool** parameter. +The pool name defaults to `default` and can be overridden when pushing the template with `--variable storage_pool=`. ## Usage diff --git a/registry/bpmct/templates/incus-vm/main.tf b/registry/bpmct/templates/incus-vm/main.tf index fc684e9ab..cbaf74c62 100644 --- a/registry/bpmct/templates/incus-vm/main.tf +++ b/registry/bpmct/templates/incus-vm/main.tf @@ -27,6 +27,12 @@ variable "arch" { } } +variable "storage_pool" { + description = "Incus storage pool for the root disk. Run `incus storage list` on the host to see available pools." + type = string + default = "default" +} + data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} @@ -102,21 +108,10 @@ data "coder_parameter" "disk" { } } -data "coder_parameter" "storage_pool" { - name = "storage_pool" - display_name = "Storage Pool" - description = "Incus storage pool for the root disk. Run `incus storage list` on the host to see available pools." - type = "string" - default = "default" - mutable = false - order = 5 -} - resource "coder_agent" "main" { count = data.coder_workspace.me.start_count arch = var.arch os = "linux" - dir = "/home/${local.workspace_user}" metadata { display_name = "CPU Usage" @@ -220,7 +215,7 @@ resource "incus_instance" "dev" { type = "disk" properties = { path = "/" - pool = data.coder_parameter.storage_pool.value + pool = var.storage_pool size = "${data.coder_parameter.disk.value}GiB" } } @@ -280,6 +275,10 @@ resource "coder_metadata" "info" { key = "image" value = "images:${data.coder_parameter.image.value}/${var.arch}" } + item { + key = "storage_pool" + value = var.storage_pool + } item { key = "arch" value = var.arch From c6940ffecd3e653ff306c3d43e792eb5c8184f31 Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Wed, 3 Jun 2026 02:56:46 +0000 Subject: [PATCH 6/7] feat(bpmct/templates/incus-nixos): add NixOS VM template --- .../bpmct/templates/incus-nixos/README.md | 75 ++++ registry/bpmct/templates/incus-nixos/main.tf | 349 ++++++++++++++++++ 2 files changed, 424 insertions(+) create mode 100644 registry/bpmct/templates/incus-nixos/README.md create mode 100644 registry/bpmct/templates/incus-nixos/main.tf diff --git a/registry/bpmct/templates/incus-nixos/README.md b/registry/bpmct/templates/incus-nixos/README.md new file mode 100644 index 000000000..648b20a08 --- /dev/null +++ b/registry/bpmct/templates/incus-nixos/README.md @@ -0,0 +1,75 @@ +--- +display_name: Incus NixOS VM +description: Run a NixOS virtual machine on a local Incus host +icon: ../../../../.icons/lxc.svg +verified: false +tags: [local, incus, vm, nixos] +--- + +# Incus NixOS VM + +Provision a NixOS KVM virtual machine on an [Incus](https://linuxcontainers.org/incus/) host. The image is pulled from [images.linuxcontainers.org](https://images.linuxcontainers.org) and cached on the host. + +NixOS does not support cloud-init. This template uses `nixos-rebuild switch` via `incus exec` to configure the workspace user and start the Coder agent. The rebuild only runs on first boot; subsequent starts rotate the agent token and restart the service directly. + +## Prerequisites + +### 1. Install Incus on the VM host + +Follow the [Incus installation guide](https://linuxcontainers.org/incus/docs/main/installing/) for your distro. On Debian/Ubuntu: + +```sh +sudo apt-get install incus +sudo incus admin init +``` + +### 2. Run the Coder provisioner on the same machine + +This template uses the local Incus socket, so the Coder provisioner must run on the same machine as Incus. See [provisioner daemons](https://coder.com/docs/admin/provisioners). + +### 3. Ensure the host has KVM + +```sh +ls /dev/kvm +``` + +If the device is missing, enable virtualisation in your BIOS/UEFI or, in a nested setup, pass through the `kvm` module. + +### 4. Create a storage pool (if needed) + +```sh +incus storage create default btrfs +incus storage list +``` + +### 5. Push the template + +```sh +# amd64 host: +coder templates push incus-nixos --directory . --variable arch=amd64 + +# arm64 host: +coder templates push incus-nixos --directory . --variable arch=arm64 +``` + +The `storage_pool` variable defaults to `default`. Override if needed: + +```sh +coder templates push incus-nixos --directory . \ + --variable arch=arm64 \ + --variable storage_pool=fast-nvme +``` + +The `nixos_channel` variable controls which NixOS channel is used for `nixos-rebuild`. It must match the image version (default: `nixos-25.11`). + +## Usage + +1. Create a workspace from this template and choose CPU, memory, and disk. +2. Connect via `coder ssh ` or use VS Code in the browser via the [VS Code extension](https://coder.com/docs/user-guides/workspace-access/vscode). +3. Install packages declaratively by editing `/etc/nixos/coder.nix` and running `sudo nixos-rebuild switch`. + +## Notes + +- `code-server` is not installed by this template. The Coder agent is started as a plain systemd service. Install editors via nix packages in `coder.nix`. +- The first workspace start takes several minutes while `nixos-rebuild switch` runs. Subsequent starts are fast. +- Advanced Incus remotes (targeting a separate host over the network) are not supported by this template. See the `incus-vm` template for guidance on adding remote support. diff --git a/registry/bpmct/templates/incus-nixos/main.tf b/registry/bpmct/templates/incus-nixos/main.tf new file mode 100644 index 000000000..9440552f3 --- /dev/null +++ b/registry/bpmct/templates/incus-nixos/main.tf @@ -0,0 +1,349 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.4.0" + } + incus = { + source = "lxc/incus" + version = "~> 1.0" + } + null = { + source = "hashicorp/null" + version = "~> 3.0" + } + } +} + +provider "incus" {} + +variable "arch" { + description = "CPU architecture of the VM host. Set this when pushing the template to match your Incus host. Valid values: amd64, arm64." + type = string + default = "amd64" + validation { + condition = contains(["amd64", "arm64"], var.arch) + error_message = "arch must be amd64 or arm64." + } +} + +variable "storage_pool" { + description = "Incus storage pool for the root disk. Run `incus storage list` on the host to see available pools." + type = string + default = "default" +} + +variable "nixos_channel" { + description = "NixOS channel to use for nixos-rebuild. Must match the image version (e.g. nixos-25.11)." + type = string + default = "nixos-25.11" +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +data "coder_parameter" "cpu" { + name = "cpu" + display_name = "CPU" + description = "Number of vCPUs." + type = "number" + default = 2 + icon = "https://raw.githubusercontent.com/matifali/logos/main/cpu-3.svg" + mutable = true + order = 1 + validation { + min = 1 + max = 16 + } +} + +data "coder_parameter" "memory" { + name = "memory" + display_name = "Memory (GB)" + type = "number" + default = 4 + icon = "/icon/memory.svg" + mutable = true + order = 2 + validation { + min = 1 + max = 64 + } +} + +data "coder_parameter" "disk" { + name = "disk" + display_name = "Disk (GB)" + type = "number" + default = 30 + icon = "/icon/database.svg" + mutable = true + order = 3 + validation { + min = 10 + max = 500 + } +} + +locals { + workspace_user = lower(data.coder_workspace_owner.me.name) + agent_token = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].token : "" + agent_init_script = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].init_script : "" + + # NixOS images on images.linuxcontainers.org use "nixos/" with no arch suffix. + # The channel version (e.g. "25.11") is extracted from var.nixos_channel. + nixos_version = replace(var.nixos_channel, "nixos-", "") + image_alias = "nixos/${local.nixos_version}" + + # PATH required for incus exec commands on NixOS VMs. The Nix store is not + # on the default system PATH until after the first nixos-rebuild switch. + nix_path = "/nix/var/nix/profiles/system/sw/bin:/run/current-system/sw/bin:/nix/var/nix/profiles/default/bin:/run/wrappers/bin" +} + +resource "coder_agent" "main" { + count = data.coder_workspace.me.start_count + arch = var.arch + os = "linux" + + metadata { + display_name = "CPU Usage" + key = "0_cpu_usage" + script = "coder stat cpu" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "RAM Usage" + key = "1_ram_usage" + script = "coder stat mem" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "Disk" + key = "2_disk" + script = "coder stat disk --path /" + interval = 60 + timeout = 1 + } +} + +resource "incus_image" "nixos" { + source_image = { + remote = "images" + name = local.image_alias + type = "virtual-machine" + architecture = var.arch == "amd64" ? "x86_64" : "aarch64" + } +} + +resource "incus_instance" "dev" { + running = data.coder_workspace.me.start_count == 1 + name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}" + image = incus_image.nixos.fingerprint + type = "virtual-machine" + + config = { + "limits.cpu" = tostring(data.coder_parameter.cpu.value) + "limits.memory" = "${data.coder_parameter.memory.value}GiB" + "security.secureboot" = false + "boot.autostart" = data.coder_workspace.me.start_count == 1 + "user.coder-agent-token" = local.agent_token + } + + device { + name = "root" + type = "disk" + properties = { + path = "/" + pool = var.storage_pool + size = "${data.coder_parameter.disk.value}GiB" + } + } + + lifecycle { + ignore_changes = [ + config["user.coder-agent-token"], + image, + ] + } +} + +# NixOS does not support cloud-init. Provisioning steps: +# 1. Wait for the incus-agent to be ready (virtio serial channel). +# 2. Push the Coder agent binary (/opt/coder/init) and token env file. +# 3. On first boot: write coder.nix and an updated configuration.nix +# that imports the Incus VM module, then run nixos-rebuild switch. +# Leave a marker so subsequent starts skip the rebuild. +# 4. On subsequent starts: overwrite init.env + restart coder-agent. + +resource "null_resource" "provision" { + count = data.coder_workspace.me.start_count + + triggers = { + agent_token = local.agent_token + instance = incus_instance.dev.name + } + + depends_on = [incus_instance.dev] + + provisioner "local-exec" { + command = <<-EOT + set -e + INSTANCE="${incus_instance.dev.name}" + WUSER="${local.workspace_user}" + NIX_PATH="${local.nix_path}" + CHANNEL="${var.nixos_channel}" + + echo "Waiting for incus-agent..." + for i in $(seq 1 60); do + incus exec "$INSTANCE" -- true 2>/dev/null && break + echo " attempt $i/60..." + sleep 5 + done + + echo "Pushing Coder agent binary..." + TMPDIR=$(mktemp -d) + echo "${base64encode(local.agent_init_script)}" | base64 -d > "$TMPDIR/init" + chmod 755 "$TMPDIR/init" + incus exec "$INSTANCE" -- env PATH="$NIX_PATH" mkdir -p /opt/coder + incus file push "$TMPDIR/init" "$INSTANCE/opt/coder/init" + incus exec "$INSTANCE" -- env PATH="$NIX_PATH" chmod 755 /opt/coder/init + rm -rf "$TMPDIR" + + printf 'CODER_AGENT_TOKEN=${local.agent_token}\nCODER_AGENT_URL=${data.coder_workspace.me.access_url}\n' \ + | incus file push - "$INSTANCE/opt/coder/init.env" --mode 0600 + + # Fast path: already provisioned -- just rotate token and restart. + if incus exec "$INSTANCE" -- test -f /etc/nixos/.coder-provisioned 2>/dev/null; then + echo "Already provisioned; restarting coder-agent..." + incus exec "$INSTANCE" -- env PATH="$NIX_PATH" systemctl restart coder-agent.service + echo "Done." + exit 0 + fi + + # First boot: write NixOS config and rebuild. + echo "Writing /etc/nixos/coder.nix..." + cat <<'NIXEOF' | incus exec "$INSTANCE" -- env PATH="$NIX_PATH" bash -c 'cat > /etc/nixos/coder.nix' +{ config, pkgs, lib, ... }: +{ + users.users."${local.workspace_user}" = { + isNormalUser = true; + uid = 1000; + home = "/home/${local.workspace_user}"; + shell = pkgs.bash; + extraGroups = [ "wheel" ]; + }; + security.sudo.wheelNeedsPassword = false; + nix.settings.trusted-users = [ "root" "${local.workspace_user}" ]; + + systemd.services.coder-agent = { + description = "Coder Agent"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = "${local.workspace_user}"; + EnvironmentFile = "/opt/coder/init.env"; + ExecStart = "/opt/coder/init"; + Environment = "PATH=/run/current-system/sw/bin:/run/wrappers/bin:/usr/local/bin:/usr/bin:/bin"; + Restart = "always"; + RestartSec = 10; + TimeoutStopSec = 90; + KillMode = "process"; + OOMScoreAdjust = -900; + SyslogIdentifier = "coder-agent"; + }; + }; +} +NIXEOF + + echo "Writing /etc/nixos/configuration.nix..." + cat <<'NIXEOF' | incus exec "$INSTANCE" -- env PATH="$NIX_PATH" bash -c 'cat > /etc/nixos/configuration.nix' +{ modulesPath, ... }: +{ + imports = [ + "$${modulesPath}/virtualisation/incus-virtual-machine.nix" + ./incus.nix + ./coder.nix + ]; + + systemd.network = { + enable = true; + networks."50-enp5s0" = { + matchConfig.Name = "enp5s0"; + networkConfig = { + DHCP = "ipv4"; + IPv6AcceptRA = true; + }; + linkConfig.RequiredForOnline = "routable"; + }; + }; + + system.stateVersion = "${local.nixos_version}"; +} +NIXEOF + + echo "Restoring nixos channel if needed..." + incus exec "$INSTANCE" -- env PATH="$NIX_PATH" HOME=/root bash -c " + if [ ! -e /nix/var/nix/profiles/per-user/root/channels/nixos ]; then + nix-channel --add https://channels.nixos.org/$CHANNEL nixos + nix-channel --update nixos + fi + " + + echo "Running nixos-rebuild switch..." + incus exec "$INSTANCE" -- env PATH="$NIX_PATH" HOME=/root bash -c " + NIXOS_CH=\$(ls -d /nix/var/nix/profiles/per-user/root/channels/nixos 2>/dev/null || echo '') + nixos-rebuild switch -I nixpkgs=\"\$NIXOS_CH\" -I nixos-config=/etc/nixos/configuration.nix \ + || { EC=\$?; [ \$EC -eq 4 ] || exit \$EC; } + " + + incus exec "$INSTANCE" -- env PATH="$NIX_PATH" touch /etc/nixos/.coder-provisioned + incus exec "$INSTANCE" -- env PATH="$NIX_PATH" bash -c \ + "mkdir -p /home/$WUSER && chown 1000:1000 /home/$WUSER" + + echo "NixOS provisioning complete." + EOT + } +} + +resource "coder_metadata" "info" { + count = data.coder_workspace.me.start_count + resource_id = incus_instance.dev.name + + item { + key = "instance" + value = incus_instance.dev.name + } + item { + key = "image" + value = "images:${local.image_alias}/${var.arch}" + } + item { + key = "nixos_channel" + value = var.nixos_channel + } + item { + key = "storage_pool" + value = var.storage_pool + } + item { + key = "arch" + value = var.arch + } + item { + key = "cpu" + value = tostring(data.coder_parameter.cpu.value) + } + item { + key = "memory" + value = "${data.coder_parameter.memory.value} GiB" + } + item { + key = "disk" + value = "${data.coder_parameter.disk.value} GiB" + } +} From 14e1f146792c0428a15e282af78fd46dae97d9b7 Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Thu, 4 Jun 2026 09:42:24 -0500 Subject: [PATCH 7/7] fix(incus-nixos): remove coder_metadata block (resource_id not supported on incus_instance name) --- registry/bpmct/templates/incus-nixos/main.tf | 38 -------------------- 1 file changed, 38 deletions(-) diff --git a/registry/bpmct/templates/incus-nixos/main.tf b/registry/bpmct/templates/incus-nixos/main.tf index 9440552f3..ed09b68d7 100644 --- a/registry/bpmct/templates/incus-nixos/main.tf +++ b/registry/bpmct/templates/incus-nixos/main.tf @@ -309,41 +309,3 @@ NIXEOF EOT } } - -resource "coder_metadata" "info" { - count = data.coder_workspace.me.start_count - resource_id = incus_instance.dev.name - - item { - key = "instance" - value = incus_instance.dev.name - } - item { - key = "image" - value = "images:${local.image_alias}/${var.arch}" - } - item { - key = "nixos_channel" - value = var.nixos_channel - } - item { - key = "storage_pool" - value = var.storage_pool - } - item { - key = "arch" - value = var.arch - } - item { - key = "cpu" - value = tostring(data.coder_parameter.cpu.value) - } - item { - key = "memory" - value = "${data.coder_parameter.memory.value} GiB" - } - item { - key = "disk" - value = "${data.coder_parameter.disk.value} GiB" - } -}