diff --git a/.github/typos.toml b/.github/typos.toml index 7d53f9bf1..fdb747483 100644 --- a/.github/typos.toml +++ b/.github/typos.toml @@ -1,5 +1,6 @@ [default.extend-words] muc = "muc" # For Munich location code +tyo = "tyo" # For Tokyo location code Hashi = "Hashi" HashiCorp = "HashiCorp" mavrickrishi = "mavrickrishi" # Username diff --git a/.icons/akamai.svg b/.icons/akamai.svg new file mode 100644 index 000000000..4af3fe08b --- /dev/null +++ b/.icons/akamai.svg @@ -0,0 +1,4 @@ + + Akamai + + diff --git a/registry/umair/templates/linode-vm/README.md b/registry/umair/templates/linode-vm/README.md new file mode 100644 index 000000000..8ba19aa25 --- /dev/null +++ b/registry/umair/templates/linode-vm/README.md @@ -0,0 +1,47 @@ +--- +display_name: Linode Instance (Linux) +description: Provision Linode instances as Coder workspaces +icon: ../../../../.icons/akamai.svg +verified: false +tags: [vm, linux, linode] +--- + +# Remote Development on Linode Instances + +Provision Linode instances as [Coder workspaces](https://coder.com/docs/workspaces) with this example template. + + + +## Prerequisites + +To deploy workspaces as Linode instances, you'll need: + +- Linode [personal access token (PAT)](https://www.linode.com/docs/products/tools/api/guides/manage-api-tokens/) + +### Authentication + +This template assumes that the Coder Provisioner is run in an environment that is authenticated with Linode. + +Obtain a [Linode Personal Access Token](https://cloud.linode.com/profile/tokens) and set the `linode_token` variable when deploying the template. +For other ways to authenticate [consult the Terraform provider's docs](https://registry.terraform.io/providers/linode/linode/latest/docs). + +## Features + +- **Multiple Instance Types**: From Nanode 1GB to 32GB configurations +- **Comprehensive OS Support**: Ubuntu, Debian, CentOS, Fedora, AlmaLinux, Rocky Linux +- **Global Regions**: 32 Linode regions across North America, Europe, Asia-Pacific, South America, and Australia +- **Persistent Storage**: Configurable volumes (10GB-1TB) that persist `$HOME` across workspace restarts +- **Development Tools**: Pre-configured with VS Code Server +- **Monitoring**: Built-in CPU, memory, and disk usage monitoring + +## Architecture + +This template provisions the following resources: + +- Linode instance (ephemeral, deleted on stop) +- Linode volume (persistent, mounted to `/home/coder`) + +This means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the VM image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script). + +> [!NOTE] +> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case. diff --git a/registry/umair/templates/linode-vm/cloud-init/cloud-config.yaml.tftpl b/registry/umair/templates/linode-vm/cloud-init/cloud-config.yaml.tftpl new file mode 100644 index 000000000..d432b0793 --- /dev/null +++ b/registry/umair/templates/linode-vm/cloud-init/cloud-config.yaml.tftpl @@ -0,0 +1,56 @@ +#cloud-config +hostname: ${hostname} +users: + - name: ${username} + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + groups: sudo + shell: /bin/bash +packages: + - git + - curl + - wget + - unzip +disk_setup: + /dev/sdb: + table_type: 'gpt' + layout: true + overwrite: false +fs_setup: + - label: ${home_volume_label} + filesystem: ext4 + device: /dev/sdb + partition: auto +mounts: + - ["/dev/sdb", "/home/${username}", "ext4", "defaults", "0", "2"] +write_files: + - path: /opt/coder/init + permissions: "0755" + encoding: b64 + content: ${init_script} + - path: /etc/systemd/system/coder-agent.service + permissions: "0644" + content: | + [Unit] + Description=Coder Agent + After=network-online.target + Wants=network-online.target + + [Service] + User=${username} + ExecStart=/opt/coder/init + Environment=CODER_AGENT_TOKEN=${coder_agent_token} + Restart=always + RestartSec=10 + TimeoutStopSec=90 + KillMode=process + + OOMScoreAdjust=-1000 + SyslogIdentifier=coder-agent + + [Install] + WantedBy=multi-user.target +runcmd: + - mkdir -p /home/${username} + - chown ${username}:${username} /home/${username} + - systemctl enable coder-agent + - systemctl start coder-agent diff --git a/registry/umair/templates/linode-vm/main.tf b/registry/umair/templates/linode-vm/main.tf new file mode 100644 index 000000000..8462a6c46 --- /dev/null +++ b/registry/umair/templates/linode-vm/main.tf @@ -0,0 +1,397 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + linode = { + source = "linode/linode" + } + } +} + +provider "coder" {} + +# Variable for Linode API token +variable "linode_token" { + description = "Linode API token for authentication" + type = string + sensitive = true + default = "" +} + +# Configure the Linode Provider +provider "linode" { + token = var.linode_token != "" ? var.linode_token : null +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "main" { + os = "linux" + arch = "amd64" + + metadata { + key = "cpu" + display_name = "CPU Usage" + interval = 5 + timeout = 5 + script = "coder stat cpu" + } + metadata { + key = "memory" + display_name = "Memory Usage" + interval = 5 + timeout = 5 + script = "coder stat mem" + } + metadata { + key = "home" + display_name = "Home Usage" + interval = 600 # every 10 minutes + timeout = 30 # df can take a while on large filesystems + script = "coder stat disk --path /home/${lower(data.coder_workspace_owner.me.name)}" + } +} + +locals { + vm_name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}" + root_disk_label = substr("${local.vm_name}-root", 0, 32) + home_volume_label = substr("${local.vm_name}-home", 0, 32) +} + +# See https://registry.coder.com/modules/coder/code-server +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.id + order = 1 +} + +data "coder_parameter" "region" { + name = "region" + display_name = "Region" + description = "This is the region where your workspace will be created." + icon = "/emojis/1f30e.png" + type = "string" + default = "us-east" + mutable = false + + option { + name = "Newark, NJ (US East)" + value = "us-east" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Washington, DC (US East)" + value = "us-iad" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Fremont, CA (US West)" + value = "us-west" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Los Angeles, CA (US West)" + value = "us-lax" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Dallas, TX (US Central)" + value = "us-central" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Chicago, IL (US Central)" + value = "us-ord" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Atlanta, GA (US Southeast)" + value = "us-southeast" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Miami, FL (US Southeast)" + value = "us-mia" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Seattle, WA (US West)" + value = "us-sea" + icon = "/emojis/1f1fa-1f1f8.png" + } + option { + name = "Toronto, CA" + value = "ca-central" + icon = "/emojis/1f1e8-1f1e6.png" + } + option { + name = "London, UK" + value = "eu-west" + icon = "/emojis/1f1ec-1f1e7.png" + } + option { + name = "London 2, UK" + value = "gb-lon" + icon = "/emojis/1f1ec-1f1e7.png" + } + option { + name = "Frankfurt, DE" + value = "eu-central" + icon = "/emojis/1f1e9-1f1ea.png" + } + option { + name = "Frankfurt 2, DE" + value = "de-fra-2" + icon = "/emojis/1f1e9-1f1ea.png" + } + option { + name = "Paris, FR" + value = "fr-par" + icon = "/emojis/1f1eb-1f1f7.png" + } + option { + name = "Amsterdam, NL" + value = "nl-ams" + icon = "/emojis/1f1f3-1f1f1.png" + } + option { + name = "Stockholm, SE" + value = "se-sto" + icon = "/emojis/1f1f8-1f1ea.png" + } + option { + name = "Madrid, ES" + value = "es-mad" + icon = "/emojis/1f1ea-1f1f8.png" + } + option { + name = "Milan, IT" + value = "it-mil" + icon = "/emojis/1f1ee-1f1f9.png" + } + option { + name = "Singapore, SG" + value = "ap-south" + icon = "/emojis/1f1f8-1f1ec.png" + } + option { + name = "Singapore 2, SG" + value = "sg-sin-2" + icon = "/emojis/1f1f8-1f1ec.png" + } + option { + name = "Tokyo 2, JP" + value = "ap-northeast" + icon = "/emojis/1f1ef-1f1f5.png" + } + option { + name = "Tokyo 3, JP" + value = "jp-tyo-3" + icon = "/emojis/1f1ef-1f1f5.png" + } + option { + name = "Osaka, JP" + value = "jp-osa" + icon = "/emojis/1f1ef-1f1f5.png" + } + option { + name = "Sydney, AU" + value = "ap-southeast" + icon = "/emojis/1f1e6-1f1fa.png" + } + option { + name = "Melbourne, AU" + value = "au-mel" + icon = "/emojis/1f1e6-1f1fa.png" + } + option { + name = "Mumbai, IN" + value = "ap-west" + icon = "/emojis/1f1ee-1f1f3.png" + } + option { + name = "Mumbai 2, IN" + value = "in-bom-2" + icon = "/emojis/1f1ee-1f1f3.png" + } + option { + name = "Chennai, IN" + value = "in-maa" + icon = "/emojis/1f1ee-1f1f3.png" + } + option { + name = "Jakarta, ID" + value = "id-cgk" + icon = "/emojis/1f1ee-1f1e9.png" + } + option { + name = "Sao Paulo, BR" + value = "br-gru" + icon = "/emojis/1f1e7-1f1f7.png" + } +} + +data "coder_parameter" "instance_type" { + name = "instance_type" + display_name = "Instance Type" + description = "Which Linode instance type would you like to use?" + default = "g6-nanode-1" + type = "string" + icon = "/icon/memory.svg" + mutable = false + + option { + name = "Nanode 1GB (1 vCPU, 1 GB RAM)" + value = "g6-nanode-1" + } + option { + name = "Linode 2GB (1 vCPU, 2 GB RAM)" + value = "g6-standard-1" + } + option { + name = "Linode 4GB (2 vCPU, 4 GB RAM)" + value = "g6-standard-2" + } + option { + name = "Linode 8GB (4 vCPU, 8 GB RAM)" + value = "g6-standard-4" + } + option { + name = "Linode 16GB (6 vCPU, 16 GB RAM)" + value = "g6-standard-6" + } + option { + name = "Linode 32GB (8 vCPU, 32 GB RAM)" + value = "g6-standard-8" + } +} + +data "coder_parameter" "instance_image" { + name = "instance_image" + display_name = "Instance Image" + description = "Which Linode image would you like to use?" + default = "linode/ubuntu24.04" + type = "string" + mutable = false + + option { + name = "Ubuntu 24.04 LTS" + value = "linode/ubuntu24.04" + icon = "/icon/ubuntu.svg" + } + option { + name = "Debian 13" + value = "linode/debian13" + icon = "/icon/debian.svg" + } + option { + name = "Fedora 42" + value = "linode/fedora42" + icon = "/icon/fedora.svg" + } + option { + name = "AlmaLinux 9" + value = "linode/almalinux9" + icon = "/icon/almalinux.svg" + } + option { + name = "Rocky Linux 9" + value = "linode/rocky9" + icon = "/icon/rockylinux.svg" + } +} + +data "coder_parameter" "home_volume_size" { + name = "home_volume_size" + display_name = "Home Volume Size (GB)" + description = "How large would you like your home volume to be (in GB)?" + type = "number" + default = 20 + mutable = true + + validation { + min = 10 + max = 1024 + monotonic = "increasing" + } +} + +resource "linode_volume" "home_volume" { + label = local.home_volume_label + size = data.coder_parameter.home_volume_size.value + region = data.coder_parameter.region.value + + # Protect the volume from being deleted due to changes in attributes. + lifecycle { + ignore_changes = all + } +} + +resource "linode_instance" "workspace" { + count = data.coder_workspace.me.start_count + label = local.vm_name + region = data.coder_parameter.region.value + type = data.coder_parameter.instance_type.value + + private_ip = true + + metadata { + user_data = base64encode(templatefile("cloud-init/cloud-config.yaml.tftpl", { + hostname = local.vm_name + username = lower(data.coder_workspace_owner.me.name) + home_volume_label = linode_volume.home_volume.label + init_script = base64encode(coder_agent.main.init_script) + coder_agent_token = coder_agent.main.token + })) + } + tags = ["coder", "workspace", lower(data.coder_workspace_owner.me.name), lower(data.coder_workspace.me.name)] +} + +# Create root disk +resource "linode_instance_disk" "root" { + count = data.coder_workspace.me.start_count + label = "boot" + linode_id = linode_instance.workspace[0].id + size = 25000 # 25GB boot disk + image = data.coder_parameter.instance_image.value +} + +# Create instance configuration with volume attached +resource "linode_instance_config" "workspace" { + count = data.coder_workspace.me.start_count + label = "${local.vm_name}-config" + linode_id = linode_instance.workspace[0].id + + device { + device_name = "sda" + disk_id = linode_instance_disk.root[0].id + } + + device { + device_name = "sdb" + volume_id = linode_volume.home_volume.id + } + + root_device = "/dev/sda" + kernel = "linode/latest-64bit" + booted = true +} + +resource "coder_metadata" "workspace-info" { + count = data.coder_workspace.me.start_count + resource_id = linode_instance.workspace[0].id + + item { + key = "region" + value = linode_instance.workspace[0].region + } + item { + key = "type" + value = linode_instance.workspace[0].type + } +}