mirror of
https://github.com/coder/registry.git
synced 2026-06-05 05:58:14 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f80f36d674 | |||
| d1d2d5b433 | |||
| 358ca6804b |
@@ -93,7 +93,7 @@ jobs:
|
||||
- name: Validate formatting
|
||||
run: bun fmt:ci
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@7b04f660f4ee4f048d18fd341887cf28dfbedfe2 # v1.46.3
|
||||
uses: crate-ci/typos@f8a58b6b53f2279f71eb605f03a4ae4d10608f45 # v1.47.0
|
||||
with:
|
||||
config: .github/typos.toml
|
||||
validate-readme-files:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <workspace>` 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.
|
||||
@@ -0,0 +1,287 @@
|
||||
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/<ver>" 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"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
display_name: Incus VM
|
||||
description: Run a full virtual machine on a local 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.
|
||||
|
||||
## 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 working:
|
||||
|
||||
```sh
|
||||
incus list
|
||||
```
|
||||
|
||||
### 2. Run the Coder provisioner on the same machine
|
||||
|
||||
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.
|
||||
|
||||
### 3. Set the architecture when pushing the template
|
||||
|
||||
The `arch` variable must match your Incus host's CPU architecture. Pass it when pushing:
|
||||
|
||||
```sh
|
||||
# For amd64 (x86-64) hosts:
|
||||
coder templates push incus-vm --directory . --variable arch=amd64
|
||||
|
||||
# For arm64 (aarch64) hosts:
|
||||
coder templates push incus-vm --directory . --variable arch=arm64
|
||||
```
|
||||
|
||||
### 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. Create a storage pool (if needed)
|
||||
|
||||
The template uses an Incus storage pool to back the VM root disk. If you don't already have one:
|
||||
|
||||
```sh
|
||||
incus storage create default btrfs
|
||||
```
|
||||
|
||||
List existing pools:
|
||||
|
||||
```sh
|
||||
incus storage list
|
||||
```
|
||||
|
||||
The pool name defaults to `default` and can be overridden when pushing the template with `--variable storage_pool=<name>`.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Push this template to your Coder deployment:
|
||||
|
||||
```sh
|
||||
coder templates push incus-vm --directory . --variable arch=amd64
|
||||
```
|
||||
|
||||
2. Create a workspace and select an image and resource sizes.
|
||||
|
||||
3. Connect via `coder ssh <workspace>` or open VS Code in the browser.
|
||||
|
||||
## 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:
|
||||
|
||||
```sh
|
||||
# On the Incus host — generate a trust token:
|
||||
incus config trust add coder-provisioner
|
||||
|
||||
# On the provisioner — add the remote:
|
||||
incus remote add my-server https://<host-ip>:8443 --token <paste-token>
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,304 @@
|
||||
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"
|
||||
}
|
||||
|
||||
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 (e.g. `ubuntu/noble/cloud`). The template architecture is appended automatically."
|
||||
type = "string"
|
||||
default = "ubuntu/noble/cloud"
|
||||
icon = "/icon/image.svg"
|
||||
mutable = true
|
||||
order = 1
|
||||
|
||||
option {
|
||||
name = "Ubuntu 24.04 LTS (Noble)"
|
||||
value = "ubuntu/noble/cloud"
|
||||
icon = "/icon/ubuntu.svg"
|
||||
}
|
||||
|
||||
option {
|
||||
name = "Ubuntu 22.04 LTS (Jammy)"
|
||||
value = "ubuntu/jammy/cloud"
|
||||
icon = "/icon/ubuntu.svg"
|
||||
}
|
||||
|
||||
option {
|
||||
name = "Debian 12 (Bookworm)"
|
||||
value = "debian/12/cloud"
|
||||
icon = "/icon/debian.svg"
|
||||
}
|
||||
}
|
||||
|
||||
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 = 2
|
||||
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 = 3
|
||||
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 = 4
|
||||
validation {
|
||||
min = 10
|
||||
max = 500
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
resource "incus_image" "image" {
|
||||
source_image = {
|
||||
remote = "images"
|
||||
name = "${data.coder_parameter.image.value}/${var.arch}"
|
||||
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.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 = var.storage_pool
|
||||
size = "${data.coder_parameter.disk.value}GiB"
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
config["cloud-init.user-data"],
|
||||
config["user.coder-agent-token"],
|
||||
image,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
INSTANCE="${incus_instance.dev.name}"
|
||||
echo "Waiting for VM agent..."
|
||||
for i in $(seq 1 40); do
|
||||
incus exec "$INSTANCE" -- true 2>/dev/null && break
|
||||
echo "Attempt $i: not ready, waiting..."
|
||||
sleep 5
|
||||
done
|
||||
echo "Waiting for cloud-init..."
|
||||
incus exec "$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 "$INSTANCE" -- bash -c 'cat > /opt/coder/init.env && chmod 600 /opt/coder/init.env'
|
||||
incus exec "$INSTANCE" -- systemctl restart coder-agent.service
|
||||
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:${data.coder_parameter.image.value}/${var.arch}"
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
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 : ""
|
||||
}
|
||||
Reference in New Issue
Block a user