mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
4385cabf6d
Bumps the dogfood template to the refactored Claude Code and Codex modules and removes the Coder Tasks integration. Claude and Codex now use slim-window app buttons that launch each tool in its own tmux session. This replaces the task-specific `develop.sh` and `preview` apps that were only created for Coder Tasks workspaces. The PR also wires the OpenAI dogfood secret through the deployment template so Codex can fall back to template configured BYOK when AI Gateway is disabled. Tested with this template version: [https://dev.coder.com/templates/coder/coder/versions/outstanding_hermann97](<https://dev.coder.com/templates/coder/coder/versions/outstanding_hermann97>)
980 lines
31 KiB
Terraform
980 lines
31 KiB
Terraform
terraform {
|
|
required_providers {
|
|
coder = {
|
|
source = "coder/coder"
|
|
version = ">= 2.13.0"
|
|
}
|
|
docker = {
|
|
source = "kreuzwerker/docker"
|
|
version = "~> 4.0"
|
|
}
|
|
}
|
|
}
|
|
|
|
// This module is a terraform no-op. It contains 5mb worth of files to test
|
|
// Coder's behavior dealing with larger modules. This is included to test
|
|
// protobuf message size limits and the performance of module loading.
|
|
//
|
|
// In reality, modules might have accidental bloat from non-terraform files such
|
|
// as images & documentation.
|
|
module "large-5mb-module" {
|
|
source = "git::https://github.com/coder/large-module.git"
|
|
}
|
|
|
|
locals {
|
|
// These are cluster service addresses mapped to Tailscale nodes. Ask Dean or
|
|
// Kyle for help.
|
|
docker_host = {
|
|
"" = "tcp://rubinsky-pit-cdr-dev.tailscale.svc.cluster.local:2375"
|
|
"us-pittsburgh" = "tcp://rubinsky-pit-cdr-dev.tailscale.svc.cluster.local:2375"
|
|
// For legacy reasons, this host is labelled `eu-helsinki` but it's
|
|
// actually in Germany now.
|
|
"eu-helsinki" = "tcp://katerose-fsn-cdr-dev.tailscale.svc.cluster.local:2375"
|
|
"ap-sydney" = "tcp://wolfgang-syd-cdr-dev.tailscale.svc.cluster.local:2375"
|
|
"za-cpt" = "tcp://schonkopf-cpt-cdr-dev.tailscale.svc.cluster.local:2375"
|
|
}
|
|
|
|
repo_base_dir = data.coder_parameter.repo_base_dir.value == "~" ? "/home/coder" : replace(data.coder_parameter.repo_base_dir.value, "/^~\\//", "/home/coder/")
|
|
repo_dir = replace(try(module.git-clone[0].repo_dir, ""), "/^~\\//", "/home/coder/")
|
|
container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
|
|
|
// Derive a stable per-workspace hour and minute from the workspace ID
|
|
// so that cache cleanup crons don't all hit the filesystem at once.
|
|
cache_cleanup_hour = parseint(substr(data.coder_workspace.me.id, 0, 2), 16) % 24
|
|
cache_cleanup_minute = parseint(substr(data.coder_workspace.me.id, 2, 2), 16) % 60
|
|
}
|
|
|
|
data "coder_workspace_preset" "pittsburgh" {
|
|
name = "Pittsburgh"
|
|
default = true
|
|
description = "Development workspace hosted in United States with 2 prebuild instances"
|
|
icon = "/emojis/1f1fa-1f1f8.png"
|
|
parameters = {
|
|
(data.coder_parameter.region.name) = "us-pittsburgh"
|
|
(data.coder_parameter.image_type.name) = data.coder_parameter.image_type.default
|
|
(data.coder_parameter.repo_base_dir.name) = "~"
|
|
(data.coder_parameter.res_mon_memory_threshold.name) = 80
|
|
(data.coder_parameter.res_mon_volume_threshold.name) = 90
|
|
(data.coder_parameter.res_mon_volume_path.name) = "/home/coder"
|
|
}
|
|
prebuilds {
|
|
instances = 2
|
|
}
|
|
}
|
|
|
|
data "coder_workspace_preset" "cpt" {
|
|
name = "Cape Town"
|
|
description = "Development workspace hosted in South Africa with 1 prebuild instance"
|
|
icon = "/emojis/1f1ff-1f1e6.png"
|
|
parameters = {
|
|
(data.coder_parameter.region.name) = "za-cpt"
|
|
(data.coder_parameter.image_type.name) = data.coder_parameter.image_type.default
|
|
(data.coder_parameter.repo_base_dir.name) = "~"
|
|
(data.coder_parameter.res_mon_memory_threshold.name) = 80
|
|
(data.coder_parameter.res_mon_volume_threshold.name) = 90
|
|
(data.coder_parameter.res_mon_volume_path.name) = "/home/coder"
|
|
}
|
|
prebuilds {
|
|
instances = 1
|
|
}
|
|
}
|
|
|
|
data "coder_workspace_preset" "falkenstein" {
|
|
name = "Falkenstein"
|
|
description = "Development workspace hosted in Europe with 1 prebuild instance"
|
|
icon = "/emojis/1f1ea-1f1fa.png"
|
|
parameters = {
|
|
(data.coder_parameter.region.name) = "eu-helsinki"
|
|
(data.coder_parameter.image_type.name) = data.coder_parameter.image_type.default
|
|
(data.coder_parameter.repo_base_dir.name) = "~"
|
|
(data.coder_parameter.res_mon_memory_threshold.name) = 80
|
|
(data.coder_parameter.res_mon_volume_threshold.name) = 90
|
|
(data.coder_parameter.res_mon_volume_path.name) = "/home/coder"
|
|
}
|
|
prebuilds {
|
|
instances = 1
|
|
}
|
|
}
|
|
|
|
data "coder_workspace_preset" "sydney" {
|
|
name = "Sydney"
|
|
description = "Development workspace hosted in Australia with 1 prebuild instance"
|
|
icon = "/emojis/1f1e6-1f1fa.png"
|
|
parameters = {
|
|
(data.coder_parameter.region.name) = "ap-sydney"
|
|
(data.coder_parameter.image_type.name) = data.coder_parameter.image_type.default
|
|
(data.coder_parameter.repo_base_dir.name) = "~"
|
|
(data.coder_parameter.res_mon_memory_threshold.name) = 80
|
|
(data.coder_parameter.res_mon_volume_threshold.name) = 90
|
|
(data.coder_parameter.res_mon_volume_path.name) = "/home/coder"
|
|
}
|
|
prebuilds {
|
|
instances = 1
|
|
}
|
|
}
|
|
|
|
data "coder_parameter" "repo_base_dir" {
|
|
type = "string"
|
|
name = "Coder Repository Base Directory"
|
|
default = "~"
|
|
description = "The directory specified will be created (if missing) and [coder/coder](https://github.com/coder/coder) will be automatically cloned into [base directory]/coder 🪄."
|
|
mutable = true
|
|
}
|
|
|
|
locals {
|
|
image_tags = {
|
|
// Older style option values, where the option value was just supposed to
|
|
// be the exact name of the image on Docker hub. In practice, this is rather
|
|
// restrictive because the image_type parameter is immutable.
|
|
"codercom/oss-dogfood:latest" = "codercom/oss-dogfood:latest"
|
|
"codercom/oss-dogfood-nix:latest" = "codercom/oss-dogfood-nix:latest"
|
|
|
|
"ubuntu-latest" = "codercom/oss-dogfood:26.04"
|
|
}
|
|
}
|
|
|
|
data "coder_parameter" "image_type" {
|
|
type = "string"
|
|
name = "Coder Image"
|
|
default = "codercom/oss-dogfood:latest"
|
|
description = "The Docker image used to run your workspace."
|
|
option {
|
|
icon = "/icon/coder.svg"
|
|
name = "Ubuntu 26.04"
|
|
value = "ubuntu-latest"
|
|
}
|
|
option {
|
|
icon = "/icon/coder.svg"
|
|
name = "Ubuntu 22.04 (Legacy)"
|
|
value = "codercom/oss-dogfood:latest"
|
|
}
|
|
option {
|
|
icon = "/icon/nix.svg"
|
|
name = "Dogfood Nix (Experimental)"
|
|
value = "codercom/oss-dogfood-nix:latest"
|
|
}
|
|
}
|
|
|
|
locals {
|
|
default_regions = {
|
|
// keys should match group names
|
|
"north-america" : "us-pittsburgh"
|
|
"europe" : "eu-helsinki"
|
|
"australia" : "ap-sydney"
|
|
"africa" : "za-cpt"
|
|
}
|
|
|
|
user_groups = data.coder_workspace_owner.me.groups
|
|
user_region = coalescelist([
|
|
for g in local.user_groups :
|
|
local.default_regions[g] if contains(keys(local.default_regions), g)
|
|
], ["us-pittsburgh"])[0]
|
|
}
|
|
|
|
data "coder_parameter" "region" {
|
|
type = "string"
|
|
name = "Region"
|
|
icon = "/emojis/1f30e.png"
|
|
default = local.user_region
|
|
option {
|
|
icon = "/emojis/1f1fa-1f1f8.png"
|
|
name = "Pittsburgh"
|
|
value = "us-pittsburgh"
|
|
}
|
|
option {
|
|
icon = "/emojis/1f1e9-1f1ea.png"
|
|
name = "Falkenstein"
|
|
// For legacy reasons, this host is labelled `eu-helsinki` but it's
|
|
// actually in Germany now.
|
|
value = "eu-helsinki"
|
|
}
|
|
option {
|
|
icon = "/emojis/1f1e6-1f1fa.png"
|
|
name = "Sydney"
|
|
value = "ap-sydney"
|
|
}
|
|
option {
|
|
icon = "/emojis/1f1ff-1f1e6.png"
|
|
name = "Cape Town"
|
|
value = "za-cpt"
|
|
}
|
|
}
|
|
|
|
data "coder_parameter" "res_mon_memory_threshold" {
|
|
type = "number"
|
|
name = "Memory usage threshold"
|
|
default = 80
|
|
description = "The memory usage threshold used in resources monitoring to trigger notifications."
|
|
mutable = true
|
|
validation {
|
|
min = 0
|
|
max = 100
|
|
}
|
|
}
|
|
|
|
data "coder_parameter" "res_mon_volume_threshold" {
|
|
type = "number"
|
|
name = "Volume usage threshold"
|
|
default = 90
|
|
description = "The volume usage threshold used in resources monitoring to trigger notifications."
|
|
mutable = true
|
|
validation {
|
|
min = 0
|
|
max = 100
|
|
}
|
|
}
|
|
|
|
data "coder_parameter" "res_mon_volume_path" {
|
|
type = "string"
|
|
name = "Volume path"
|
|
default = "/home/coder"
|
|
description = "The path monitored in resources monitoring to trigger notifications."
|
|
mutable = true
|
|
}
|
|
|
|
data "coder_parameter" "devcontainer_autostart" {
|
|
type = "bool"
|
|
name = "Automatically start devcontainer for coder/coder"
|
|
default = false
|
|
description = "If enabled, a devcontainer will be automatically started for the [coder/coder](https://github.com/coder/coder) repository."
|
|
mutable = true
|
|
}
|
|
|
|
data "coder_parameter" "enable_ai_gateway" {
|
|
type = "bool"
|
|
name = "Use AI Gateway"
|
|
default = true
|
|
description = "If enabled, AI requests will be sent via AI Gateway."
|
|
mutable = true
|
|
}
|
|
|
|
# Only used if AI Gateway is disabled.
|
|
# dogfood/main.tf injects this value from a GH Actions secret;
|
|
# `coderd_template.dogfood` passes the value injected by .github/workflows/dogfood.yaml in `TF_VAR_CODER_DOGFOOD_ANTHROPIC_API_KEY` and `TF_VAR_CODER_DOGFOOD_OPENAI_API_KEY`.
|
|
variable "anthropic_api_key" {
|
|
type = string
|
|
description = "The API key used to authenticate with the Anthropic API, if AI Bridge is disabled."
|
|
default = ""
|
|
sensitive = true
|
|
}
|
|
|
|
variable "openai_api_key" {
|
|
type = string
|
|
description = "The API key used to authenticate with the OpenAI API, if AI Gateway is disabled."
|
|
default = ""
|
|
sensitive = true
|
|
}
|
|
|
|
provider "docker" {
|
|
host = lookup(local.docker_host, data.coder_parameter.region.value)
|
|
}
|
|
|
|
provider "coder" {}
|
|
|
|
data "coder_external_auth" "github" {
|
|
id = "github"
|
|
}
|
|
|
|
data "coder_workspace" "me" {}
|
|
data "coder_workspace_owner" "me" {}
|
|
data "coder_task" "me" {}
|
|
data "coder_workspace_tags" "tags" {
|
|
tags = {
|
|
"cluster" : "dogfood-v2"
|
|
"env" : "gke"
|
|
}
|
|
}
|
|
|
|
data "coder_workspace_tags" "prebuild" {
|
|
count = data.coder_workspace_owner.me.name == "prebuilds" ? 1 : 0
|
|
tags = {
|
|
"is_prebuild" = "true"
|
|
}
|
|
}
|
|
|
|
data "coder_parameter" "ide_choices" {
|
|
type = "list(string)"
|
|
name = "Select IDEs"
|
|
form_type = "multi-select"
|
|
mutable = true
|
|
description = "Choose one or more IDEs to enable in your workspace"
|
|
default = jsonencode(["vscode", "code-server", "cursor"])
|
|
option {
|
|
name = "VS Code Desktop"
|
|
value = "vscode"
|
|
icon = "/icon/code.svg"
|
|
}
|
|
option {
|
|
name = "code-server"
|
|
value = "code-server"
|
|
icon = "/icon/code.svg"
|
|
}
|
|
option {
|
|
name = "VS Code Web"
|
|
value = "vscode-web"
|
|
icon = "/icon/code.svg"
|
|
}
|
|
option {
|
|
name = "JetBrains IDEs"
|
|
value = "jetbrains"
|
|
icon = "/icon/jetbrains.svg"
|
|
}
|
|
option {
|
|
name = "Cursor"
|
|
value = "cursor"
|
|
icon = "/icon/cursor.svg"
|
|
}
|
|
option {
|
|
name = "Windsurf"
|
|
value = "windsurf"
|
|
icon = "/icon/windsurf.svg"
|
|
}
|
|
option {
|
|
name = "Zed"
|
|
value = "zed"
|
|
icon = "/icon/zed.svg"
|
|
}
|
|
}
|
|
|
|
data "coder_parameter" "vscode_channel" {
|
|
count = contains(jsondecode(data.coder_parameter.ide_choices.value), "vscode") ? 1 : 0
|
|
type = "string"
|
|
name = "VS Code Desktop channel"
|
|
description = "Choose the VS Code Desktop channel"
|
|
mutable = true
|
|
default = "stable"
|
|
option {
|
|
value = "stable"
|
|
name = "Stable"
|
|
icon = "/icon/code.svg"
|
|
}
|
|
option {
|
|
value = "insiders"
|
|
name = "Insiders"
|
|
icon = "/icon/code-insiders.svg"
|
|
}
|
|
}
|
|
|
|
module "slackme" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/coder/slackme/coder"
|
|
version = "1.0.33"
|
|
agent_id = coder_agent.dev.id
|
|
auth_provider_id = "slack"
|
|
}
|
|
|
|
module "dotfiles" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/coder/dotfiles/coder"
|
|
version = "1.4.1"
|
|
agent_id = coder_agent.dev.id
|
|
}
|
|
|
|
module "git-config" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/coder/git-config/coder"
|
|
version = "1.0.33"
|
|
agent_id = coder_agent.dev.id
|
|
# If you prefer to commit with a different email, this allows you to do so.
|
|
allow_email_change = true
|
|
}
|
|
|
|
module "git-clone" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/coder/git-clone/coder"
|
|
version = "1.2.3"
|
|
agent_id = coder_agent.dev.id
|
|
url = "https://github.com/coder/coder"
|
|
base_dir = local.repo_base_dir
|
|
post_clone_script = <<-EOT
|
|
#!/usr/bin/env bash
|
|
set -eux -o pipefail
|
|
coder exp sync start git-clone
|
|
coder exp sync complete git-clone
|
|
EOT
|
|
}
|
|
|
|
module "personalize" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/coder/personalize/coder"
|
|
version = "1.0.32"
|
|
agent_id = coder_agent.dev.id
|
|
}
|
|
|
|
module "mux" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "registry.coder.com/coder/mux/coder"
|
|
version = "1.4.3"
|
|
agent_id = coder_agent.dev.id
|
|
subdomain = true
|
|
display_name = "Mux"
|
|
add_project = local.repo_dir
|
|
install_version = "next"
|
|
package_manager = "bun"
|
|
restart_on_kill = true
|
|
max_restart_attempts = 10
|
|
}
|
|
|
|
module "code-server" {
|
|
count = contains(jsondecode(data.coder_parameter.ide_choices.value), "code-server") ? data.coder_workspace.me.start_count : 0
|
|
source = "dev.registry.coder.com/coder/code-server/coder"
|
|
version = "1.4.4"
|
|
agent_id = coder_agent.dev.id
|
|
folder = local.repo_dir
|
|
auto_install_extensions = true
|
|
group = "Web Editors"
|
|
}
|
|
|
|
module "vscode-web" {
|
|
count = contains(jsondecode(data.coder_parameter.ide_choices.value), "vscode-web") ? data.coder_workspace.me.start_count : 0
|
|
source = "dev.registry.coder.com/coder/vscode-web/coder"
|
|
version = "1.5.0"
|
|
agent_id = coder_agent.dev.id
|
|
folder = local.repo_dir
|
|
extensions = ["github.copilot"]
|
|
auto_install_extensions = true # will install extensions from the repos .vscode/extensions.json file
|
|
accept_license = true
|
|
group = "Web Editors"
|
|
}
|
|
|
|
module "jetbrains" {
|
|
count = contains(jsondecode(data.coder_parameter.ide_choices.value), "jetbrains") ? data.coder_workspace.me.start_count : 0
|
|
source = "dev.registry.coder.com/coder/jetbrains/coder"
|
|
version = "1.4.0"
|
|
agent_id = coder_agent.dev.id
|
|
agent_name = "dev"
|
|
folder = local.repo_dir
|
|
major_version = "latest"
|
|
tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app."
|
|
}
|
|
|
|
module "filebrowser" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/coder/filebrowser/coder"
|
|
version = "1.1.5"
|
|
agent_id = coder_agent.dev.id
|
|
agent_name = "dev"
|
|
}
|
|
|
|
module "coder-login" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/coder/coder-login/coder"
|
|
version = "1.1.1"
|
|
agent_id = coder_agent.dev.id
|
|
}
|
|
|
|
module "cursor" {
|
|
count = contains(jsondecode(data.coder_parameter.ide_choices.value), "cursor") ? data.coder_workspace.me.start_count : 0
|
|
source = "dev.registry.coder.com/coder/cursor/coder"
|
|
version = "1.4.1"
|
|
agent_id = coder_agent.dev.id
|
|
folder = local.repo_dir
|
|
}
|
|
|
|
module "windsurf" {
|
|
count = contains(jsondecode(data.coder_parameter.ide_choices.value), "windsurf") ? data.coder_workspace.me.start_count : 0
|
|
source = "dev.registry.coder.com/coder/windsurf/coder"
|
|
version = "1.3.1"
|
|
agent_id = coder_agent.dev.id
|
|
folder = local.repo_dir
|
|
}
|
|
|
|
module "zed" {
|
|
count = contains(jsondecode(data.coder_parameter.ide_choices.value), "zed") ? data.coder_workspace.me.start_count : 0
|
|
source = "dev.registry.coder.com/coder/zed/coder"
|
|
version = "1.1.4"
|
|
agent_id = coder_agent.dev.id
|
|
agent_name = "dev"
|
|
folder = local.repo_dir
|
|
}
|
|
|
|
module "devcontainers-cli" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/modules/devcontainers-cli/coder"
|
|
version = ">= 1.0.0"
|
|
agent_id = coder_agent.dev.id
|
|
}
|
|
|
|
module "portabledesktop" {
|
|
source = "dev.registry.coder.com/coder/portabledesktop/coder"
|
|
version = "0.1.0"
|
|
agent_id = coder_agent.dev.id
|
|
}
|
|
|
|
resource "coder_agent" "dev" {
|
|
arch = "amd64"
|
|
os = "linux"
|
|
dir = local.repo_dir
|
|
env = merge(
|
|
{
|
|
OIDC_TOKEN : data.coder_workspace_owner.me.oidc_access_token,
|
|
CODER_AGENT_EXP_MCP_CONFIG_FILES : "~/.mcp.json,.mcp.json",
|
|
},
|
|
data.coder_parameter.enable_ai_gateway.value ? {
|
|
ANTHROPIC_BASE_URL : "https://dev.coder.com/api/v2/aibridge/anthropic",
|
|
ANTHROPIC_AUTH_TOKEN : data.coder_workspace_owner.me.session_token,
|
|
OPENAI_BASE_URL : "https://dev.coder.com/api/v2/aibridge/openai/v1",
|
|
OPENAI_API_KEY : data.coder_workspace_owner.me.session_token,
|
|
} : {}
|
|
)
|
|
startup_script_behavior = "blocking"
|
|
|
|
display_apps {
|
|
vscode = contains(jsondecode(data.coder_parameter.ide_choices.value), "vscode") && try(data.coder_parameter.vscode_channel[0].value, "stable") == "stable"
|
|
vscode_insiders = contains(jsondecode(data.coder_parameter.ide_choices.value), "vscode") && try(data.coder_parameter.vscode_channel[0].value, "stable") == "insiders"
|
|
}
|
|
|
|
# The following metadata blocks are optional. They are used to display
|
|
# information about your workspace in the dashboard. You can remove them
|
|
# if you don't want to display any information.
|
|
metadata {
|
|
display_name = "CPU Usage"
|
|
key = "cpu_usage"
|
|
order = 0
|
|
script = "coder stat cpu"
|
|
interval = 10
|
|
timeout = 1
|
|
}
|
|
|
|
metadata {
|
|
display_name = "RAM Usage"
|
|
key = "ram_usage"
|
|
order = 1
|
|
script = "coder stat mem"
|
|
interval = 10
|
|
timeout = 1
|
|
}
|
|
|
|
metadata {
|
|
display_name = "/home Usage"
|
|
key = "home_usage"
|
|
order = 2
|
|
script = "sudo du -sh /home/coder | awk '{print $1}'"
|
|
interval = 3600 # 1h to avoid thrashing disk
|
|
timeout = 60 # Longer than this is likely problematic
|
|
}
|
|
|
|
metadata {
|
|
display_name = "/var/lib/docker Usage"
|
|
key = "var_lib_docker_usage"
|
|
order = 3
|
|
script = "sudo du -sh /var/lib/docker 2>/dev/null | awk '{print $1}'"
|
|
interval = 3600 # 1h to avoid thrashing disk
|
|
timeout = 60 # Longer than this is likely problematic
|
|
}
|
|
|
|
metadata {
|
|
display_name = "Word of the Day"
|
|
key = "word"
|
|
order = 4
|
|
script = <<EOT
|
|
#!/usr/bin/env bash
|
|
curl -o - --silent https://www.merriam-webster.com/word-of-the-day 2>&1 | awk ' $0 ~ "Word of the Day: [A-z]+" { print $5; exit }'
|
|
EOT
|
|
interval = 86400
|
|
timeout = 5
|
|
}
|
|
|
|
resources_monitoring {
|
|
memory {
|
|
enabled = true
|
|
threshold = data.coder_parameter.res_mon_memory_threshold.value
|
|
}
|
|
volume {
|
|
enabled = true
|
|
threshold = data.coder_parameter.res_mon_volume_threshold.value
|
|
path = data.coder_parameter.res_mon_volume_path.value
|
|
}
|
|
volume {
|
|
enabled = true
|
|
threshold = data.coder_parameter.res_mon_volume_threshold.value
|
|
path = "/var/lib/docker"
|
|
}
|
|
}
|
|
|
|
startup_script = <<-EOT
|
|
#!/usr/bin/env bash
|
|
set -eux -o pipefail
|
|
# Allow other scripts to wait for agent startup.
|
|
function cleanup() {
|
|
coder exp sync complete agent-startup
|
|
# Some folks will also use this for their personalize scripts.
|
|
touch /tmp/.coder-startup-script.done
|
|
}
|
|
trap cleanup EXIT
|
|
coder exp sync start agent-startup
|
|
|
|
# Authenticate GitHub CLI. `gh api user` is used instead of `gh auth
|
|
# status` because the latter exits non-zero when a stale token exists
|
|
# in ~/.config/gh/hosts.yml, even when a valid GITHUB_TOKEN is already
|
|
# present in the environment and gh commands work fine.
|
|
if ! gh api user --jq .login >/dev/null 2>&1; then
|
|
echo "Logging into GitHub CLI…"
|
|
if ! coder external-auth access-token github | gh auth login --hostname github.com --with-token; then
|
|
echo "GitHub CLI authentication failed; gh commands may not work."
|
|
fi
|
|
else
|
|
echo "GitHub CLI already has working credentials."
|
|
fi
|
|
# Configure Mux GitHub owner login for browser access (skip if
|
|
# already set). See: https://mux.coder.com/config/server-access
|
|
if [ ! -f ~/.mux/config.json ] || ! jq -e '.serverAuthGithubOwner' ~/.mux/config.json >/dev/null 2>&1; then
|
|
GH_USER=$(gh api user --jq .login 2>/dev/null || true)
|
|
if [ -n "$GH_USER" ]; then
|
|
mkdir -p ~/.mux
|
|
if [ -f ~/.mux/config.json ]; then
|
|
jq --arg owner "$GH_USER" '. + {serverAuthGithubOwner: $owner}' ~/.mux/config.json > /tmp/mux-config.json && mv /tmp/mux-config.json ~/.mux/config.json
|
|
else
|
|
jq -n --arg owner "$GH_USER" '{serverAuthGithubOwner: $owner}' > ~/.mux/config.json
|
|
fi
|
|
echo "Configured Mux GitHub owner login: $GH_USER"
|
|
fi
|
|
fi
|
|
|
|
# Increase the shutdown timeout of the docker service for improved cleanup.
|
|
# The 240 was picked as it's lower than the 300 seconds we set for the
|
|
# container shutdown grace period.
|
|
sudo sh -c 'jq ". += {\"shutdown-timeout\": 240}" /etc/docker/daemon.json > /tmp/daemon.json.new && mv /tmp/daemon.json.new /etc/docker/daemon.json'
|
|
# Start Docker service
|
|
sudo service docker start
|
|
EOT
|
|
|
|
shutdown_script = <<-EOT
|
|
#!/usr/bin/env bash
|
|
set -eux -o pipefail
|
|
|
|
# Clean up the Go build cache to prevent the home volume from
|
|
# accumulating waste and growing too large.
|
|
go clean -cache
|
|
|
|
# Clean up the coder build directory as this can get quite large
|
|
rm -rf "${local.repo_dir}/build"
|
|
|
|
# Clean up the unused resources to keep storage usage low.
|
|
#
|
|
# WARNING! This will remove:
|
|
# - all stopped containers
|
|
# - all networks not used by at least one container
|
|
# - all images without at least one container associated to them
|
|
# - all build cache
|
|
docker system prune -a -f
|
|
|
|
# Remove dangling named volumes that are older than KEEP_DAYS. Using
|
|
# 30 here as a conservative default (vacation, holidays, etc.).
|
|
KEEP_DAYS=30
|
|
docker volume ls -qf dangling=true \
|
|
| xargs -r docker volume inspect \
|
|
| jq -r --argjson days "$KEEP_DAYS" '.[] | select(.CreatedAt != null) | ((now - (.CreatedAt | fromdateiso8601)) / 86400 | floor) as $a | select($a >= $days) | "\($a)\t\(.Name)"' \
|
|
| while IFS=$'\t' read -r age name; do
|
|
echo "Removing volume $name ($age d)"
|
|
docker volume rm "$name" >/dev/null
|
|
done
|
|
|
|
# Stop the Docker service to prevent errors during workspace destroy.
|
|
sudo service docker stop
|
|
EOT
|
|
}
|
|
|
|
resource "coder_script" "install-deps" {
|
|
agent_id = coder_agent.dev.id
|
|
display_name = "Installing Dependencies"
|
|
run_on_start = true
|
|
start_blocks_login = false
|
|
script = <<EOT
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
trap 'coder exp sync complete install-deps' EXIT
|
|
coder exp sync want install-deps git-clone
|
|
coder exp sync start install-deps
|
|
|
|
# Install playwright dependencies
|
|
# We want to use the playwright version from site/package.json
|
|
cd "${local.repo_dir}" && make clean
|
|
cd "${local.repo_dir}/site" && pnpm install
|
|
EOT
|
|
}
|
|
|
|
resource "coder_script" "go-cache-cleanup-cron" {
|
|
agent_id = coder_agent.dev.id
|
|
display_name = "Go Build Cache Cleanup Cron"
|
|
icon = "${data.coder_workspace.me.access_url}/emojis/1f9f9.png" // 🧹
|
|
cron = "0 ${local.cache_cleanup_minute} ${local.cache_cleanup_hour} * * *"
|
|
script = <<-EOT
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
cache_dir=$(go env GOCACHE)
|
|
echo "Cleaning Go build cache entries not used in the last 2 days..."
|
|
before=$(du -s "$cache_dir" 2>/dev/null | awk '{print $1}')
|
|
find "$cache_dir" -type f -mtime +2 -delete
|
|
find "$cache_dir" -type d -empty -delete
|
|
after=$(du -s "$cache_dir" 2>/dev/null | awk '{print $1}')
|
|
freed=$(( (before - after) / 1024 ))
|
|
echo "Freed $${freed}MB from Go build cache."
|
|
EOT
|
|
}
|
|
|
|
resource "coder_devcontainer" "coder" {
|
|
count = data.coder_parameter.devcontainer_autostart.value ? data.coder_workspace.me.start_count : 0
|
|
agent_id = coder_agent.dev.id
|
|
workspace_folder = local.repo_dir
|
|
}
|
|
|
|
# Add a cost so we get some quota usage in dev.coder.com
|
|
resource "coder_metadata" "home_volume" {
|
|
resource_id = docker_volume.home_volume.id
|
|
daily_cost = 1
|
|
}
|
|
|
|
resource "docker_volume" "home_volume" {
|
|
name = "coder-${data.coder_workspace.me.id}-home"
|
|
# Protect the volume from being deleted due to changes in attributes.
|
|
lifecycle {
|
|
ignore_changes = all
|
|
}
|
|
# Add labels in Docker to keep track of orphan resources.
|
|
labels {
|
|
label = "coder.owner"
|
|
value = data.coder_workspace_owner.me.name
|
|
}
|
|
labels {
|
|
label = "coder.owner_id"
|
|
value = data.coder_workspace_owner.me.id
|
|
}
|
|
labels {
|
|
label = "coder.workspace_id"
|
|
value = data.coder_workspace.me.id
|
|
}
|
|
# This field becomes outdated if the workspace is renamed but can
|
|
# be useful for debugging or cleaning out dangling volumes.
|
|
labels {
|
|
label = "coder.workspace_name_at_creation"
|
|
value = data.coder_workspace.me.name
|
|
}
|
|
}
|
|
|
|
resource "coder_metadata" "docker_volume" {
|
|
resource_id = docker_volume.docker_volume.id
|
|
hide = true # Hide it as it is not useful to see in the UI.
|
|
}
|
|
|
|
resource "docker_volume" "docker_volume" {
|
|
name = "coder-${data.coder_workspace.me.id}-docker"
|
|
# Protect the volume from being deleted due to changes in attributes.
|
|
lifecycle {
|
|
ignore_changes = all
|
|
}
|
|
# Add labels in Docker to keep track of orphan resources.
|
|
labels {
|
|
label = "coder.owner"
|
|
value = data.coder_workspace_owner.me.name
|
|
}
|
|
labels {
|
|
label = "coder.owner_id"
|
|
value = data.coder_workspace_owner.me.id
|
|
}
|
|
labels {
|
|
label = "coder.workspace_id"
|
|
value = data.coder_workspace.me.id
|
|
}
|
|
# This field becomes outdated if the workspace is renamed but can
|
|
# be useful for debugging or cleaning out dangling volumes.
|
|
labels {
|
|
label = "coder.workspace_name_at_creation"
|
|
value = data.coder_workspace.me.name
|
|
}
|
|
}
|
|
|
|
data "docker_registry_image" "dogfood" {
|
|
name = local.image_tags[data.coder_parameter.image_type.value]
|
|
}
|
|
|
|
resource "docker_image" "dogfood" {
|
|
name = "${local.image_tags[data.coder_parameter.image_type.value]}@${data.docker_registry_image.dogfood.sha256_digest}"
|
|
pull_triggers = [
|
|
data.docker_registry_image.dogfood.sha256_digest,
|
|
sha1(join("", [for f in fileset(path.module, "files/*") : filesha1(f)])),
|
|
filesha1("ubuntu-22.04/Dockerfile"),
|
|
filesha1("ubuntu-26.04/Dockerfile"),
|
|
filesha1("nix.hash"),
|
|
]
|
|
keep_locally = true
|
|
}
|
|
|
|
resource "docker_container" "workspace" {
|
|
lifecycle {
|
|
// Ignore changes that would invalidate prebuilds
|
|
ignore_changes = [
|
|
name,
|
|
hostname,
|
|
labels,
|
|
env,
|
|
entrypoint
|
|
]
|
|
}
|
|
count = data.coder_workspace.me.start_count
|
|
image = docker_image.dogfood.name
|
|
name = local.container_name
|
|
# Hostname makes the shell more user friendly: coder@my-workspace:~$
|
|
hostname = data.coder_workspace.me.name
|
|
# Use the docker gateway if the access URL is 127.0.0.1
|
|
entrypoint = ["sh", "-c", coder_agent.dev.init_script]
|
|
# CPU limits are unnecessary since Docker will load balance automatically
|
|
memory = data.coder_workspace_owner.me.name == "code-asher" ? 65536 : 32768
|
|
runtime = "sysbox-runc"
|
|
|
|
# Ensure the workspace is given time to:
|
|
# - Execute shutdown scripts
|
|
# - Stop the in workspace Docker daemon
|
|
# - Stop the container, especially when using devcontainers,
|
|
# deleting the overlay filesystem can take a while.
|
|
destroy_grace_seconds = 300
|
|
stop_timeout = 300
|
|
stop_signal = "SIGINT"
|
|
|
|
env = [
|
|
"CODER_AGENT_TOKEN=${coder_agent.dev.token}",
|
|
"USE_CAP_NET_ADMIN=true",
|
|
"CODER_PROC_PRIO_MGMT=1",
|
|
"CODER_PROC_OOM_SCORE=10",
|
|
"CODER_PROC_NICE_SCORE=1",
|
|
"CODER_AGENT_DEVCONTAINERS_ENABLE=1",
|
|
]
|
|
host {
|
|
host = "host.docker.internal"
|
|
ip = "host-gateway"
|
|
}
|
|
volumes {
|
|
container_path = "/home/coder/"
|
|
volume_name = docker_volume.home_volume.name
|
|
read_only = false
|
|
}
|
|
volumes {
|
|
container_path = "/var/lib/docker/"
|
|
volume_name = docker_volume.docker_volume.name
|
|
read_only = false
|
|
}
|
|
capabilities {
|
|
add = ["CAP_NET_ADMIN", "CAP_SYS_NICE"]
|
|
}
|
|
# Add labels in Docker to keep track of orphan resources.
|
|
labels {
|
|
label = "coder.owner"
|
|
value = data.coder_workspace_owner.me.name
|
|
}
|
|
labels {
|
|
label = "coder.owner_id"
|
|
value = data.coder_workspace_owner.me.id
|
|
}
|
|
labels {
|
|
label = "coder.workspace_id"
|
|
value = data.coder_workspace.me.id
|
|
}
|
|
labels {
|
|
label = "coder.workspace_name"
|
|
value = data.coder_workspace.me.name
|
|
}
|
|
}
|
|
|
|
resource "coder_metadata" "container_info" {
|
|
count = data.coder_workspace.me.start_count
|
|
resource_id = docker_container.workspace[0].id
|
|
item {
|
|
key = "memory"
|
|
value = docker_container.workspace[0].memory
|
|
}
|
|
item {
|
|
key = "runtime"
|
|
value = docker_container.workspace[0].runtime
|
|
}
|
|
item {
|
|
key = "region"
|
|
value = data.coder_parameter.region.option[index(data.coder_parameter.region.option.*.value, data.coder_parameter.region.value)].name
|
|
}
|
|
item {
|
|
key = "ai_task"
|
|
value = data.coder_task.me.enabled ? "yes" : "no"
|
|
}
|
|
}
|
|
|
|
resource "coder_script" "boundary_config_setup" {
|
|
agent_id = coder_agent.dev.id
|
|
display_name = "Boundary Setup Configuration"
|
|
run_on_start = true
|
|
|
|
script = <<-EOF
|
|
#!/bin/sh
|
|
|
|
trap 'coder exp sync complete boundary-config-setup' EXIT
|
|
coder exp sync start boundary-config-setup
|
|
|
|
mkdir -p ~/.config/coder_boundary
|
|
echo '${base64encode(file("${path.module}/boundary-config.yaml"))}' | base64 -d > ~/.config/coder_boundary/config.yaml
|
|
chmod 600 ~/.config/coder_boundary/config.yaml
|
|
EOF
|
|
}
|
|
|
|
module "claude-code" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "dev.registry.coder.com/coder/claude-code/coder"
|
|
version = "5.1.0"
|
|
enable_ai_gateway = data.coder_parameter.enable_ai_gateway.value
|
|
anthropic_api_key = data.coder_parameter.enable_ai_gateway.value ? "" : var.anthropic_api_key
|
|
agent_id = coder_agent.dev.id
|
|
workdir = local.repo_dir
|
|
mcp = <<-EOF
|
|
{
|
|
"mcpServers": {
|
|
"playwright": {
|
|
"command": "npx",
|
|
"args": ["--", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"]
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
}
|
|
|
|
resource "coder_app" "claude" {
|
|
agent_id = coder_agent.dev.id
|
|
slug = "claude"
|
|
display_name = "Claude Code"
|
|
icon = "/icon/claude.svg"
|
|
open_in = "slim-window"
|
|
command = <<-EOT
|
|
#!/bin/bash
|
|
set -e
|
|
cd "${local.repo_dir}"
|
|
exec tmux new-session -A -s claude claude
|
|
EOT
|
|
}
|
|
|
|
module "codex" {
|
|
source = "dev.registry.coder.com/coder-labs/codex/coder"
|
|
version = "5.0.0"
|
|
agent_id = coder_agent.dev.id
|
|
workdir = local.repo_dir
|
|
enable_ai_gateway = data.coder_parameter.enable_ai_gateway.value
|
|
openai_api_key = data.coder_parameter.enable_ai_gateway.value ? "" : var.openai_api_key
|
|
mcp = <<-EOT
|
|
[mcp_servers.playwright]
|
|
command = "npx"
|
|
args = ["--", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"]
|
|
type = "stdio"
|
|
EOT
|
|
}
|
|
|
|
resource "coder_app" "codex" {
|
|
agent_id = coder_agent.dev.id
|
|
slug = "codex"
|
|
display_name = "Codex"
|
|
icon = "/icon/openai-codex.svg"
|
|
open_in = "slim-window"
|
|
command = <<-EOT
|
|
#!/bin/bash
|
|
set -e
|
|
cd "${local.repo_dir}"
|
|
exec tmux new-session -A -s codex codex
|
|
EOT
|
|
}
|