diff --git a/.github/typos.toml b/.github/typos.toml index 7ebdacef4..d0b1f2863 100644 --- a/.github/typos.toml +++ b/.github/typos.toml @@ -3,6 +3,7 @@ muc = "muc" # For Munich location code tyo = "tyo" # For Tokyo location code Hashi = "Hashi" HashiCorp = "HashiCorp" +hel = "hel" # For Helsinki location code mavrickrishi = "mavrickrishi" # Username mavrick = "mavrick" # Username inh = "inh" # Option in setpriv command diff --git a/.icons/hetzner.svg b/.icons/hetzner.svg new file mode 100644 index 000000000..74bb87c1a --- /dev/null +++ b/.icons/hetzner.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/registry/Excellencedev/.images/avatar.png b/registry/Excellencedev/.images/avatar.png new file mode 100644 index 000000000..4ca6333fb Binary files /dev/null and b/registry/Excellencedev/.images/avatar.png differ diff --git a/registry/Excellencedev/README.md b/registry/Excellencedev/README.md new file mode 100644 index 000000000..13200875a --- /dev/null +++ b/registry/Excellencedev/README.md @@ -0,0 +1,7 @@ +--- +display_name: "Excellencedev" +bio: "Love to contribute" +avatar: "./.images/avatar.png" +support_email: "ademiluyisuccessandexcellence@gmail.com" +status: "community" +--- diff --git a/registry/Excellencedev/templates/hetzner-linux/README.md b/registry/Excellencedev/templates/hetzner-linux/README.md new file mode 100644 index 000000000..60575e679 --- /dev/null +++ b/registry/Excellencedev/templates/hetzner-linux/README.md @@ -0,0 +1,32 @@ +--- +display_name: Hetzner Cloud Server +description: Provision Hetzner Cloud servers as Coder workspaces +icon: ../../../../.icons/hetzner.svg +tags: [vm, linux, hetzner] +--- + +# Remote Development on Hetzner Cloud (Linux) + +Provision Hetzner Cloud servers as [Coder workspaces](https://coder.com/docs/workspaces) with this example template. + +> [!WARNING] +> **Workspace Storage Persistence:** When a workspace is stopped, the Hetzner Cloud server instance is stopped but your home volume and stored data persist. This means your files and data remain intact when you resume the workspace. + +> [!IMPORTANT] +> **Volume Management & Costs:** Hetzner Cloud volumes persist even when workspaces are stopped and will continue to incur storage costs (€0.0476/GB/month). Volumes are only automatically deleted when the workspace is completely deleted. Monitor your volumes in the [Hetzner Cloud Console](https://console.hetzner.cloud/) to manage costs effectively. + +## Prerequisites + +To deploy workspaces as Hetzner Cloud servers, you'll need: + +- Hetzner Cloud [API token](https://console.hetzner.cloud/projects) (create under Security > API Tokens) + +### Authentication + +This template assumes that the Coder Provisioner is run in an environment that is authenticated with Hetzner Cloud. + +Obtain a Hetzner Cloud API token from your [Hetzner Cloud Console](https://console.hetzner.cloud/projects) and provide it as the `hcloud_token` variable when creating a workspace. +For more authentication options, see the [Terraform provider documentation](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs#authentication). + +> [!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/Excellencedev/templates/hetzner-linux/cloud-config.yaml.tftpl b/registry/Excellencedev/templates/hetzner-linux/cloud-config.yaml.tftpl new file mode 100644 index 000000000..73d4897ab --- /dev/null +++ b/registry/Excellencedev/templates/hetzner-linux/cloud-config.yaml.tftpl @@ -0,0 +1,62 @@ +#cloud-config +users: + - name: ${username} + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + groups: sudo + shell: /bin/bash +packages: + - git +%{ if home_volume_label != "" ~} +fs_setup: + - device: /dev/disk/by-id/scsi-0HC_Volume_${volume_id} + filesystem: ext4 + label: ${home_volume_label} + overwrite: false # This prevents reformatting the disk on every boot + +mounts: + - [ + "/dev/disk/by-id/scsi-0HC_Volume_${volume_id}", + "/home/${username}", + ext4, + "defaults,uid=1000,gid=1000", + ] +%{ endif ~} +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=-900 + SyslogIdentifier=coder-agent + + [Install] + WantedBy=multi-user.target +runcmd: +%{ if home_volume_label != "" ~} + - | + until [ -e /dev/disk/by-id/scsi-0HC_Volume_${volume_id} ]; do + echo "Waiting for volume device..." + sleep 2 + done +%{ endif ~} + - mount -a + - chown ${username}:${username} /home/${username} + - systemctl enable coder-agent + - systemctl start coder-agent \ No newline at end of file diff --git "a/registry/Excellencedev/templates/hetzner-linux/hetzner_server_types.json\342\200\216" "b/registry/Excellencedev/templates/hetzner-linux/hetzner_server_types.json\342\200\216" new file mode 100644 index 000000000..6be0938a7 --- /dev/null +++ "b/registry/Excellencedev/templates/hetzner-linux/hetzner_server_types.json\342\200\216" @@ -0,0 +1,27 @@ +{ + "type_meta": { + "cx22": { "cores": 2, "memory_gb": 4, "disk_gb": 40 }, + "cx32": { "cores": 4, "memory_gb": 8, "disk_gb": 80 }, + "cx42": { "cores": 8, "memory_gb": 16, "disk_gb": 160 }, + "cx52": { "cores": 16, "memory_gb": 32, "disk_gb": 320 }, + "cpx11": { "cores": 2, "memory_gb": 2, "disk_gb": 40 }, + "cpx21": { "cores": 3, "memory_gb": 4, "disk_gb": 80 }, + "cpx31": { "cores": 4, "memory_gb": 8, "disk_gb": 160 }, + "cpx41": { "cores": 8, "memory_gb": 16, "disk_gb": 240 }, + "cpx51": { "cores": 16, "memory_gb": 32, "disk_gb": 360 }, + "ccx13": { "cores": 2, "memory_gb": 8, "disk_gb": 80 }, + "ccx23": { "cores": 4, "memory_gb": 16, "disk_gb": 160 }, + "ccx33": { "cores": 8, "memory_gb": 32, "disk_gb": 240 }, + "ccx43": { "cores": 16, "memory_gb": 64, "disk_gb": 360 }, + "ccx53": { "cores": 32, "memory_gb": 128, "disk_gb": 600 }, + "ccx63": { "cores": 48, "memory_gb": 192, "disk_gb": 960 } + }, + "availability": { + "fsn1": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"], + "ash": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"], + "hel1": ["cx22", "cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"], + "hil": ["cpx11", "cpx21", "cpx31", "cpx41", "ccx13", "ccx23", "ccx33"], + "nbg1": ["cx22", "cx32", "cx42", "cx52", "cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"], + "sin": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"] + } +} diff --git a/registry/Excellencedev/templates/hetzner-linux/main.tf b/registry/Excellencedev/templates/hetzner-linux/main.tf new file mode 100644 index 000000000..4b71e4561 --- /dev/null +++ b/registry/Excellencedev/templates/hetzner-linux/main.tf @@ -0,0 +1,183 @@ +terraform { + required_providers { + hcloud = { + source = "hetznercloud/hcloud" + } + coder = { + source = "coder/coder" + } + } +} + +variable "hcloud_token" { + sensitive = true +} + +provider "hcloud" { + token = var.hcloud_token +} + +# Available locations: https://docs.hetzner.com/cloud/general/locations/ +data "coder_parameter" "hcloud_location" { + name = "hcloud_location" + display_name = "Hetzner Location" + description = "Select the Hetzner Cloud location for your workspace." + type = "string" + default = "fsn1" + option { + name = "DE Falkenstein" + value = "fsn1" + } + option { + name = "US Ashburn, VA" + value = "ash" + } + option { + name = "US Hillsboro, OR" + value = "hil" + } + option { + name = "SG Singapore" + value = "sin" + } + option { + name = "DE Nuremberg" + value = "nbg1" + } + option { + name = "FI Helsinki" + value = "hel1" + } +} + +# Available server types: https://docs.hetzner.com/cloud/servers/overview/ +data "coder_parameter" "hcloud_server_type" { + name = "hcloud_server_type" + display_name = "Hetzner Server Type" + description = "Select the Hetzner Cloud server type for your workspace." + type = "string" + + dynamic "option" { + for_each = local.hcloud_server_type_options_for_selected_location + content { + name = option.value.name + value = option.value.value + } + } +} + +resource "hcloud_server" "dev" { + count = data.coder_workspace.me.start_count + name = "coder-${data.coder_workspace.me.name}-dev" + image = "ubuntu-24.04" + server_type = data.coder_parameter.hcloud_server_type.value + location = data.coder_parameter.hcloud_location.value + public_net { + ipv4_enabled = true + ipv6_enabled = true + } + user_data = templatefile("cloud-config.yaml.tftpl", { + username = lower(data.coder_workspace_owner.me.name) + home_volume_label = "coder-${data.coder_workspace.me.id}-home" + volume_id = hcloud_volume.home_volume.id + init_script = base64encode(coder_agent.main.init_script) + coder_agent_token = coder_agent.main.token + }) + labels = { + "coder_workspace_name" = data.coder_workspace.me.name, + "coder_workspace_owner" = data.coder_workspace_owner.me.name, + } +} + +resource "hcloud_volume" "home_volume" { + name = "coder-${data.coder_workspace.me.id}-home" + size = data.coder_parameter.home_volume_size.value + location = data.coder_parameter.hcloud_location.value + labels = { + "coder_workspace_name" = data.coder_workspace.me.name, + "coder_workspace_owner" = data.coder_workspace_owner.me.name, + } +} + +resource "hcloud_volume_attachment" "home_volume_attachment" { + count = data.coder_workspace.me.start_count + volume_id = hcloud_volume.home_volume.id + server_id = hcloud_server.dev[count.index].id + automount = false +} + +locals { + username = lower(data.coder_workspace_owner.me.name) + + # Data source: local JSON file under the module directory + # Check API for latest server types & availability: https://docs.hetzner.cloud/reference/cloud#server-types + hcloud_server_types_data = jsondecode(file("${path.module}/hetzner_server_types.json")) + hcloud_server_type_meta = local.hcloud_server_types_data.type_meta + hcloud_server_types_by_location = local.hcloud_server_types_data.availability + + hcloud_server_type_options_for_selected_location = [ + for type_name in lookup(local.hcloud_server_types_by_location, data.coder_parameter.hcloud_location.value, []) : { + name = format("%s (%d vCPU, %dGB RAM, %dGB)", upper(type_name), local.hcloud_server_type_meta[type_name].cores, local.hcloud_server_type_meta[type_name].memory_gb, local.hcloud_server_type_meta[type_name].disk_gb) + value = type_name + } + ] +} + +data "coder_provisioner" "me" {} + +provider "coder" {} + +data "coder_workspace" "me" {} + +data "coder_workspace_owner" "me" {} + +data "coder_parameter" "home_volume_size" { + name = "home_volume_size" + display_name = "Home volume size" + description = "How large would you like your home volume to be (in GB)?" + type = "number" + default = "20" + mutable = false + validation { + min = 1 + max = 100 # Adjust the max size as needed + } +} + +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/${local.username}" + } +} + +module "code-server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/code-server/coder" + + # This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production. + version = "~> 1.0" + + agent_id = coder_agent.main.id + order = 1 +} \ No newline at end of file