Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
57f5238
feat(registry/coder/modules/coder-utils): make install_script and sta…
Apr 13, 2026
fadaee8
fix(registry/coder/modules/coder-utils): add mkdir -p to all script b…
Apr 13, 2026
8729122
fix(registry/coder/modules/coder-utils): return empty string instead …
Apr 13, 2026
9127a11
docs(registry/coder/modules/coder-utils): bump version to 1.1.0
Apr 13, 2026
f203a78
refactor(registry/coder/modules/coder-utils): make install_script req…
Apr 13, 2026
8a9ab44
refactor(registry/coder/modules/coder-utils): move install sync deps …
Apr 13, 2026
0d51427
test(registry/coder/modules/coder-utils): add install sync deps test …
Apr 13, 2026
e942c7c
fix(registry/coder/modules/coder-utils): remove redundant mkdir from …
Apr 13, 2026
e0a4670
refactor(coder/modules/coder-utils): rename module_dir_name to module…
35C4n0r Apr 14, 2026
22966c5
fix(terraform): update required_version to support Terraform 1.0 and …
35C4n0r Apr 14, 2026
8216a71
chore: update test
35C4n0r Apr 14, 2026
94f1d3a
refactor(registry/coder/modules/coder-utils): consolidate outputs int…
Apr 14, 2026
6678b04
feat(coder-utils): add display_name_prefix and icon variables
matifali Apr 22, 2026
dda6063
test(coder-utils): assert optional scripts absent when unset
matifali Apr 22, 2026
178c609
feat(coder-utils): add scripts output with filtered run-order list
matifali Apr 22, 2026
af2931e
fix(coder-utils): tee script output to log file and stdout
matifali Apr 22, 2026
ff51dae
refactor(coder-utils): drop script_names output
matifali Apr 22, 2026
1dde32c
Merge branch 'main' into feat/coder-utils-optional-install-start
matifali Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions registry/coder/modules/coder-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ The Coder Utils module is a building block for modules that need to run multiple
```tf
module "coder_utils" {
source = "registry.coder.com/coder/coder-utils/coder"
version = "1.0.1"
version = "1.1.0"

agent_id = coder_agent.main.id
agent_name = "myagent"
module_dir_name = ".my-module"
agent_id = coder_agent.main.id
agent_name = "myagent"
module_directory = ".my-module"

pre_install_script = <<-EOT
#!/bin/bash
Expand Down Expand Up @@ -56,10 +56,30 @@ module "coder_utils" {

The module orchestrates scripts in the following order:

1. **Log File Creation** - Creates module directory and log files
2. **Pre-Install Script** (optional) - Runs before installation
3. **Install Script** - Main installation
4. **Post-Install Script** (optional) - Runs after installation
5. **Start Script** - Starts the application
1. **Pre-Install Script** (optional) - Runs before installation
2. **Install Script** (required) - Main installation
3. **Post-Install Script** (optional) - Runs after installation
4. **Start Script** (optional) - Starts the application

Each script waits for its prerequisites to complete before running using `coder exp sync` dependency management.

## Customizing Script Display

By default each `coder_script` renders in the Coder UI as plain "Install Script", "Pre-Install Script", etc. Downstream modules can brand them:

```tf
module "coder_utils" {
source = "registry.coder.com/coder/coder-utils/coder"
version = "1.1.0"

agent_id = coder_agent.main.id
agent_name = "myagent"
module_directory = ".my-module"
install_script = "echo installing"

display_name_prefix = "Claude Code" # yields "Claude Code: Install Script", etc.
icon = "/icon/claude.svg"
}
```

Both variables are optional. `display_name_prefix` defaults to `""` (no prefix), and `icon` defaults to `null` (use the Coder provider's default).
4 changes: 2 additions & 2 deletions registry/coder/modules/coder-utils/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe("coder-utils", async () => {
testRequiredVariables(import.meta.dir, {
agent_id: "test-agent-id",
agent_name: "test-agent",
module_dir_name: ".test-module",
start_script: "echo 'start'",
module_directory: ".test-module",
install_script: "echo 'install'",
});
});
115 changes: 66 additions & 49 deletions registry/coder/modules/coder-utils/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ variable "pre_install_script" {
variable "install_script" {
type = string
description = "Script to install the agent used by AgentAPI."
default = null
}

variable "post_install_script" {
Expand All @@ -41,6 +40,7 @@ variable "post_install_script" {
variable "start_script" {
type = string
description = "Script that starts AgentAPI."
default = null
}

variable "agent_name" {
Expand All @@ -49,84 +49,107 @@ variable "agent_name" {

}

variable "module_dir_name" {
variable "module_directory" {
type = string
description = "The module's working directory for scripts and logs."
}

variable "display_name_prefix" {
type = string
description = "The name of the module directory."
description = "Prefix for each coder_script display_name. Example: setting 'Claude Code' yields 'Claude Code: Install Script', 'Claude Code: Pre-Install Script', etc. When unset, scripts show as plain 'Install Script'."
default = ""
}
Comment thread
matifali marked this conversation as resolved.

variable "icon" {
type = string
description = "Icon shown in the Coder UI for every coder_script this module creates. Falls back to the Coder provider's default when unset."
default = null
}

locals {
encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : ""
encoded_install_script = var.install_script != null ? base64encode(var.install_script) : ""
encoded_install_script = base64encode(var.install_script)
encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : ""
encoded_start_script = base64encode(var.start_script)
encoded_start_script = var.start_script != null ? base64encode(var.start_script) : ""

pre_install_script_name = "${var.agent_name}-pre_install_script"
install_script_name = "${var.agent_name}-install_script"
post_install_script_name = "${var.agent_name}-post_install_script"
start_script_name = "${var.agent_name}-start_script"

module_dir_path = "$HOME/${var.module_dir_name}"
pre_install_path = "${var.module_directory}/pre_install.sh"
install_path = "${var.module_directory}/install.sh"
post_install_path = "${var.module_directory}/post_install.sh"
start_path = "${var.module_directory}/start.sh"

pre_install_path = "${local.module_dir_path}/pre_install.sh"
install_path = "${local.module_dir_path}/install.sh"
post_install_path = "${local.module_dir_path}/post_install.sh"
start_path = "${local.module_dir_path}/start.sh"
pre_install_log_path = "${var.module_directory}/pre_install.log"
install_log_path = "${var.module_directory}/install.log"
post_install_log_path = "${var.module_directory}/post_install.log"
start_log_path = "${var.module_directory}/start.log"

pre_install_log_path = "${local.module_dir_path}/pre_install.log"
install_log_path = "${local.module_dir_path}/install.log"
post_install_log_path = "${local.module_dir_path}/post_install.log"
start_log_path = "${local.module_dir_path}/start.log"
install_sync_deps = var.pre_install_script != null ? local.pre_install_script_name : null

start_sync_deps = (
var.post_install_script != null
? "${local.install_script_name} ${local.post_install_script_name}"
: local.install_script_name
)

display_name_prefix = var.display_name_prefix != "" ? "${var.display_name_prefix}: " : ""
}

resource "coder_script" "pre_install_script" {
count = var.pre_install_script == null ? 0 : 1
agent_id = var.agent_id
display_name = "Pre-Install Script"
display_name = "${local.display_name_prefix}Pre-Install Script"
icon = var.icon
run_on_start = true
script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail

mkdir -p ${local.module_dir_path}
mkdir -p ${var.module_directory}

trap 'coder exp sync complete ${local.pre_install_script_name}' EXIT
coder exp sync start ${local.pre_install_script_name}

echo -n '${local.encoded_pre_install_script}' | base64 -d > ${local.pre_install_path}
chmod +x ${local.pre_install_path}

${local.pre_install_path} > ${local.pre_install_log_path} 2>&1
${local.pre_install_path} 2>&1 | tee ${local.pre_install_log_path}
EOT
}

resource "coder_script" "install_script" {
agent_id = var.agent_id
display_name = "Install Script"
display_name = "${local.display_name_prefix}Install Script"
icon = var.icon
run_on_start = true
script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail

mkdir -p ${local.module_dir_path}
mkdir -p ${var.module_directory}

trap 'coder exp sync complete ${local.install_script_name}' EXIT
%{if var.pre_install_script != null~}
coder exp sync want ${local.install_script_name} ${local.pre_install_script_name}
%{if local.install_sync_deps != null~}
coder exp sync want ${local.install_script_name} ${local.install_sync_deps}
%{endif~}
coder exp sync start ${local.install_script_name}
echo -n '${local.encoded_install_script}' | base64 -d > ${local.install_path}
chmod +x ${local.install_path}

${local.install_path} > ${local.install_log_path} 2>&1
${local.install_path} 2>&1 | tee ${local.install_log_path}
EOT
}

resource "coder_script" "post_install_script" {
count = var.post_install_script != null ? 1 : 0
agent_id = var.agent_id
display_name = "Post-Install Script"
display_name = "${local.display_name_prefix}Post-Install Script"
icon = var.icon
run_on_start = true
script = <<-EOT
#!/bin/bash
Expand All @@ -140,13 +163,15 @@ resource "coder_script" "post_install_script" {
echo -n '${local.encoded_post_install_script}' | base64 -d > ${local.post_install_path}
chmod +x ${local.post_install_path}

${local.post_install_path} > ${local.post_install_log_path} 2>&1
${local.post_install_path} 2>&1 | tee ${local.post_install_log_path}
EOT
}

resource "coder_script" "start_script" {
count = var.start_script != null ? 1 : 0
agent_id = var.agent_id
display_name = "Start Script"
display_name = "${local.display_name_prefix}Start Script"
icon = var.icon
run_on_start = true
script = <<-EOT
#!/bin/bash
Expand All @@ -155,36 +180,28 @@ resource "coder_script" "start_script" {

trap 'coder exp sync complete ${local.start_script_name}' EXIT

%{if var.post_install_script != null~}
coder exp sync want ${local.start_script_name} ${local.install_script_name} ${local.post_install_script_name}
%{else~}
coder exp sync want ${local.start_script_name} ${local.install_script_name}
%{endif~}
coder exp sync want ${local.start_script_name} ${local.start_sync_deps}
coder exp sync start ${local.start_script_name}

echo -n '${local.encoded_start_script}' | base64 -d > ${local.start_path}
chmod +x ${local.start_path}

${local.start_path} > ${local.start_log_path} 2>&1
${local.start_path} 2>&1 | tee ${local.start_log_path}
EOT
}

output "pre_install_script_name" {
description = "The name of the pre-install script for sync."
value = local.pre_install_script_name
}

output "install_script_name" {
description = "The name of the install script for sync."
value = local.install_script_name
}

output "post_install_script_name" {
description = "The name of the post-install script for sync."
value = local.post_install_script_name
# Filtered, run-order list of the `coder exp sync` names for every
# coder_script this module actually creates. Absent scripts (pre/post/start
# when their inputs are null) are omitted entirely, not padded with empty
# strings. Downstream modules can use this with
# `coder exp sync want <self> <each of these>` to serialize their own
# scripts behind the install pipeline.
output "scripts" {
description = "Ordered list of `coder exp sync` names for the coder_script resources this module creates, in the run order it enforces (pre_install, install, post_install, start). Scripts that were not configured are absent from the list."
value = concat(
var.pre_install_script != null ? [local.pre_install_script_name] : [],
[local.install_script_name],
var.post_install_script != null ? [local.post_install_script_name] : [],
var.start_script != null ? [local.start_script_name] : [],
)
}

output "start_script_name" {
description = "The name of the start script for sync."
value = local.start_script_name
}
Loading
Loading