chore(templates): Added IaaS Instance

This commit is contained in:
l-nmch
2026-03-22 02:35:30 +01:00
commit 74d27be222
5 changed files with 328 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.coder/
.terraform/
.terraform.lock.hcl
terraform.tfvars
terraform.tfstate
terraform.tfstate.backup

17
README.md Normal file
View File

@@ -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 <new-template> -d .
```

56
iaas-instance/README.md Normal file
View File

@@ -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 = "<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

View File

@@ -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}"

196
iaas-instance/main.tf Normal file
View File

@@ -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
}