From ae32a7dd63d194b05ccbdc2e2d7465cfd6ba9cc8 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 10 Jul 2026 11:12:24 +0000 Subject: [PATCH 01/21] feat(registry/coder/modules/boo): add boo terminal multiplexer module Adds a new Terraform module for coder/boo, a GNU screen-style terminal multiplexer built on libghostty. The module accepts a map of session names to commands and creates one coder_app and one coder_script per session, all serialized after a shared install pipeline via coder-utils. - Install script: idempotent boo install via official shell installer; skips reinstall when boo_version=latest and any version is present, or when the pinned version already matches. - Start script: per-session, waits for install phase via coder exp sync, creates the session if absent, logs to ~/.coder-modules/coder/boo/logs//start.log. - coder_app command waits for the corresponding session start script to complete via coder exp sync want before running boo attach. - Ghost SVG icon added to .icons/boo.svg. --- .icons/boo.svg | 12 + registry/coder/modules/boo/README.md | 154 ++++++++++++ registry/coder/modules/boo/main.tf | 142 +++++++++++ registry/coder/modules/boo/main.tftest.hcl | 233 ++++++++++++++++++ .../modules/boo/scripts/install.sh.tftpl | 39 +++ .../coder/modules/boo/scripts/start.sh.tftpl | 31 +++ 6 files changed, 611 insertions(+) create mode 100644 .icons/boo.svg create mode 100644 registry/coder/modules/boo/README.md create mode 100644 registry/coder/modules/boo/main.tf create mode 100644 registry/coder/modules/boo/main.tftest.hcl create mode 100644 registry/coder/modules/boo/scripts/install.sh.tftpl create mode 100644 registry/coder/modules/boo/scripts/start.sh.tftpl diff --git a/.icons/boo.svg b/.icons/boo.svg new file mode 100644 index 000000000..a0de3d745 --- /dev/null +++ b/.icons/boo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/registry/coder/modules/boo/README.md b/registry/coder/modules/boo/README.md new file mode 100644 index 000000000..d37c13d66 --- /dev/null +++ b/registry/coder/modules/boo/README.md @@ -0,0 +1,154 @@ +--- +display_name: Boo +description: Run commands in persistent boo terminal sessions, one app per session. +icon: ../../../../.icons/boo.svg +verified: false +tags: [terminal, multiplexer, session, boo] +--- + +# Boo + +Install [boo](https://github.com/coder/boo) and run commands in persistent, named terminal sessions. Boo is a GNU screen-style terminal multiplexer built on [libghostty](https://github.com/ghostty-org/ghostty) (Zig). Pass a map of session names to commands and the module creates one `coder_app` and one start script per session, all serialized after the install pipeline. + +```tf +module "boo" { + source = "registry.coder.com/coder/boo/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + sessions = { + shell = "bash" + } +} +``` + +## Usage + +### Multiple sessions + +Create separate persistent sessions for a dev server and an interactive shell, each with its own dashboard app. + +```tf +module "boo" { + source = "registry.coder.com/coder/boo/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + folder = "/home/coder/project" + sessions = { + server = "npm run dev" + shell = "bash" + } +} +``` + +This creates: + +- `coder_app` slugs `boo-server` and `boo-shell` +- Display names `Boo: server` and `Boo: shell` +- Logs at `~/.coder-modules/coder/boo/logs/server/start.log` and `.../shell/start.log` + +### Pin a specific boo version + +```tf +module "boo" { + source = "registry.coder.com/coder/boo/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + boo_version = "v0.6.4" + sessions = { shell = "bash" } +} +``` + +### Skip installation (boo pre-installed in image) + +```tf +module "boo" { + source = "registry.coder.com/coder/boo/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + install_boo = false + sessions = { shell = "bash" } +} +``` + +### Customize app appearance + +```tf +module "boo" { + source = "registry.coder.com/coder/boo/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + display_name = "Terminal" + slug = "term" + icon = "/icon/terminal.svg" + order = 1 + group = "Development" + sessions = { shell = "bash" } +} +``` + +Apps are named `Terminal: shell` with slug `term-shell`. + +### Use pre/post install hooks + +```tf +module "boo" { + source = "registry.coder.com/coder/boo/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + pre_install_script = "echo 'Preparing environment...'" + post_install_script = "echo 'boo ready'" + sessions = { shell = "bash" } +} +``` + +### Serialize another module behind boo + +Use `output.scripts` to wait for all boo sessions to start before running downstream work. + +```tf +module "boo" { + source = "registry.coder.com/coder/boo/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + sessions = { server = "npm run dev", shell = "bash" } +} + +resource "coder_script" "after_boo" { + agent_id = coder_agent.main.id + display_name = "After Boo" + run_on_start = true + script = <<-EOT + #!/bin/bash + coder exp sync want after-boo ${join(" ", module.boo.scripts)} + coder exp sync start after-boo + trap 'coder exp sync complete after-boo' EXIT + echo "All boo sessions are up" + EOT +} +``` + +## Naming + +App slugs and display names are derived from session names: + +| `slug` (default `"boo"`) | session name | app slug | display name | +| ------------------------ | -------------- | ------------------- | -------------------- | +| `"boo"` | `"shell"` | `"boo-shell"` | `"Boo: shell"` | +| `"term"` | `"dev_server"` | `"term-dev-server"` | `"term: dev_server"` | + +Session names must be valid as Terraform map keys. Underscores in session names are replaced with hyphens in app slugs. + +## Troubleshooting + +Logs are written per session under `~/.coder-modules/coder/boo/logs/`: + +``` +~/.coder-modules/coder/boo/logs/ +├── install.log +├── / +│ └── start.log +``` + +Check `install.log` for installation errors. Check `/start.log` for session creation errors. + +If an app does not connect, verify the session exists by running `boo ls` in a terminal. diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf new file mode 100644 index 000000000..0019b0f8f --- /dev/null +++ b/registry/coder/modules/boo/main.tf @@ -0,0 +1,142 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.13" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "sessions" { + type = map(string) + description = "Map of session names to commands. A boo session and coder_app are created for each entry." +} + +variable "folder" { + type = string + description = "The working directory for boo sessions." + default = "/home/coder" +} + +variable "install_boo" { + type = bool + description = "Whether to install boo." + default = true +} + +variable "boo_version" { + type = string + description = "The version of boo to install. Use 'latest' to accept any installed version or always install the latest release." + default = "latest" +} + +variable "display_name" { + type = string + description = "The display name prefix for boo apps. Each app is shown as ': '." + default = "Boo" +} + +variable "slug" { + type = string + description = "The slug prefix for boo apps. Each app slug is '-'." + default = "boo" +} + +variable "icon" { + type = string + description = "The icon to use for boo apps and scripts." + default = "/icon/boo.svg" +} + +variable "order" { + type = number + description = "The order determines the position of apps in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)." + default = null +} + +variable "group" { + type = string + description = "The name of a group that boo apps belong to." + default = null +} + +variable "pre_install_script" { + type = string + description = "Custom script to run before installing boo." + default = null +} + +variable "post_install_script" { + type = string + description = "Custom script to run after installing boo." + default = null +} + +locals { + module_dir = "$HOME/.coder-modules/coder/boo" + + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_INSTALL_BOO = tostring(var.install_boo) + ARG_BOO_VERSION = var.boo_version + }) + + # Last sync name from the install pipeline; per-session start scripts wait on this. + install_phase_last = module.coder_utils.scripts[length(module.coder_utils.scripts) - 1] +} + +module "coder_utils" { + source = "registry.coder.com/coder/coder-utils/coder" + version = "0.0.1" + + agent_id = var.agent_id + module_directory = local.module_dir + display_name_prefix = "Boo" + icon = var.icon + pre_install_script = var.pre_install_script + post_install_script = var.post_install_script + install_script = local.install_script +} + +resource "coder_script" "boo_start" { + for_each = var.sessions + + agent_id = var.agent_id + display_name = "${var.display_name}: Start ${each.key}" + icon = var.icon + run_on_start = true + script = templatefile("${path.module}/scripts/start.sh.tftpl", { + ARG_SESSION_NAME = each.key + ARG_FOLDER = var.folder + ARG_COMMAND = base64encode(each.value) + ARG_LOG_PATH = "${local.module_dir}/logs/${each.key}/start.log" + ARG_SYNC_NAME = "coder-boo-${each.key}-start_script" + ARG_INSTALL_SYNC = local.install_phase_last + }) +} + +resource "coder_app" "boo" { + for_each = var.sessions + + agent_id = var.agent_id + slug = "${var.slug}-${replace(each.key, "_", "-")}" + display_name = "${var.display_name}: ${each.key}" + icon = var.icon + command = "coder exp sync want ${var.slug}-${replace(each.key, "_", "-")} coder-boo-${each.key}-start_script && export PATH=\"$HOME/.local/bin:$PATH\" && boo attach ${each.key}" + order = var.order + group = var.group +} + +output "scripts" { + description = "Ordered list of coder exp sync names for the coder_script resources this module creates, in run order. Includes install phase scripts followed by per-session start scripts (sorted by session name)." + value = concat( + module.coder_utils.scripts, + [for name in sort(keys(var.sessions)) : "coder-boo-${name}-start_script"] + ) +} diff --git a/registry/coder/modules/boo/main.tftest.hcl b/registry/coder/modules/boo/main.tftest.hcl new file mode 100644 index 000000000..ee1f75f23 --- /dev/null +++ b/registry/coder/modules/boo/main.tftest.hcl @@ -0,0 +1,233 @@ +run "test_defaults" { + command = plan + + variables { + agent_id = "test-agent-defaults" + sessions = { main = "bash" } + } + + assert { + condition = var.folder == "/home/coder" + error_message = "folder should default to '/home/coder'" + } + + assert { + condition = var.install_boo == true + error_message = "install_boo should default to true" + } + + assert { + condition = var.boo_version == "latest" + error_message = "boo_version should default to 'latest'" + } + + assert { + condition = var.display_name == "Boo" + error_message = "display_name should default to 'Boo'" + } + + assert { + condition = var.slug == "boo" + error_message = "slug should default to 'boo'" + } + + assert { + condition = var.icon == "/icon/boo.svg" + error_message = "icon should default to '/icon/boo.svg'" + } + + assert { + condition = var.order == null + error_message = "order should default to null" + } + + assert { + condition = var.group == null + error_message = "group should default to null" + } +} + +run "test_single_session" { + command = plan + + variables { + agent_id = "test-agent-single" + sessions = { dev = "make dev" } + folder = "/home/coder/project" + } + + assert { + condition = var.sessions["dev"] == "make dev" + error_message = "sessions map should contain the correct command for key 'dev'" + } + + assert { + condition = var.folder == "/home/coder/project" + error_message = "folder should be '/home/coder/project'" + } +} + +run "test_multiple_sessions" { + command = plan + + variables { + agent_id = "test-agent-multi" + sessions = { + server = "npm run dev" + worker = "npm run worker" + shell = "bash" + } + } + + assert { + condition = length(var.sessions) == 3 + error_message = "sessions should contain 3 entries" + } + + assert { + condition = var.sessions["server"] == "npm run dev" + error_message = "sessions['server'] should be 'npm run dev'" + } + + assert { + condition = var.sessions["worker"] == "npm run worker" + error_message = "sessions['worker'] should be 'npm run worker'" + } +} + +run "test_skip_install" { + command = plan + + variables { + agent_id = "test-agent-skip" + sessions = { main = "bash" } + install_boo = false + } + + assert { + condition = var.install_boo == false + error_message = "install_boo should be false" + } +} + +run "test_pinned_version" { + command = plan + + variables { + agent_id = "test-agent-pinned" + sessions = { main = "bash" } + boo_version = "v0.6.4" + } + + assert { + condition = var.boo_version == "v0.6.4" + error_message = "boo_version should be 'v0.6.4'" + } +} + +run "test_custom_app" { + command = plan + + variables { + agent_id = "test-agent-app" + sessions = { main = "bash" } + slug = "my-boo" + display_name = "My Boo" + icon = "/icon/custom.svg" + order = 10 + group = "terminals" + } + + assert { + condition = var.slug == "my-boo" + error_message = "slug should be 'my-boo'" + } + + assert { + condition = var.display_name == "My Boo" + error_message = "display_name should be 'My Boo'" + } + + assert { + condition = var.icon == "/icon/custom.svg" + error_message = "icon should be '/icon/custom.svg'" + } + + assert { + condition = var.order == 10 + error_message = "order should be 10" + } + + assert { + condition = var.group == "terminals" + error_message = "group should be 'terminals'" + } +} + +run "test_hooks" { + command = plan + + variables { + agent_id = "test-agent-hooks" + sessions = { main = "bash" } + pre_install_script = "echo 'pre-install'" + post_install_script = "echo 'post-install'" + } + + assert { + condition = var.pre_install_script == "echo 'pre-install'" + error_message = "pre_install_script should be set correctly" + } + + assert { + condition = var.post_install_script == "echo 'post-install'" + error_message = "post_install_script should be set correctly" + } +} + +run "test_scripts_output_single_session" { + command = plan + + variables { + agent_id = "test-agent-output-single" + sessions = { main = "bash" } + } + + assert { + condition = output.scripts == ["coder-boo-install_script", "coder-boo-main-start_script"] + error_message = "scripts output should list install_script then session start_script" + } +} + +run "test_scripts_output_multi_session" { + command = plan + + variables { + agent_id = "test-agent-output-multi" + sessions = { + alpha = "bash" + beta = "bash" + } + } + + assert { + condition = output.scripts == ["coder-boo-install_script", "coder-boo-alpha-start_script", "coder-boo-beta-start_script"] + error_message = "scripts output should list install_script then session start_scripts sorted by name" + } +} + +run "test_scripts_output_with_hooks" { + command = plan + + variables { + agent_id = "test-agent-output-hooks" + sessions = { main = "bash" } + pre_install_script = "echo pre" + post_install_script = "echo post" + } + + assert { + condition = output.scripts == ["coder-boo-pre_install_script", "coder-boo-install_script", "coder-boo-post_install_script", "coder-boo-main-start_script"] + error_message = "scripts output should list all four scripts in run order" + } +} diff --git a/registry/coder/modules/boo/scripts/install.sh.tftpl b/registry/coder/modules/boo/scripts/install.sh.tftpl new file mode 100644 index 000000000..d565e2f1b --- /dev/null +++ b/registry/coder/modules/boo/scripts/install.sh.tftpl @@ -0,0 +1,39 @@ +#!/bin/bash +set -o errexit +set -o pipefail + +INSTALL_BOO="${ARG_INSTALL_BOO}" +BOO_VERSION="${ARG_BOO_VERSION}" + +if [ "$INSTALL_BOO" != "true" ]; then + echo "Skipping boo installation (install_boo=false)" + exit 0 +fi + +export PATH="$HOME/.local/bin:$PATH" + +if command -v boo &>/dev/null; then + INSTALLED_VERSION=$(boo -V 2>&1 | tr -d 'v\n' || true) + + if [ "$BOO_VERSION" = "latest" ]; then + echo "boo $${INSTALLED_VERSION} already installed; skipping (boo_version=latest)" + exit 0 + fi + + REQUESTED_VERSION=$(echo -n "$BOO_VERSION" | sed 's/^v//') + if [ "$INSTALLED_VERSION" = "$REQUESTED_VERSION" ]; then + echo "boo $${INSTALLED_VERSION} already installed and matches requested version; skipping" + exit 0 + fi + + echo "boo $${INSTALLED_VERSION} installed but $${REQUESTED_VERSION} requested; reinstalling" +fi + +export BOO_INSTALL_DIR="$HOME/.local/bin" +if [ "$BOO_VERSION" != "latest" ]; then + export BOO_VERSION=$(echo -n "$BOO_VERSION" | sed 's/^v//') +fi + +echo "Installing boo..." +curl -fsSL https://raw.githubusercontent.com/coder/boo/main/install.sh | sh +echo "boo installation complete" diff --git a/registry/coder/modules/boo/scripts/start.sh.tftpl b/registry/coder/modules/boo/scripts/start.sh.tftpl new file mode 100644 index 000000000..9f61198c7 --- /dev/null +++ b/registry/coder/modules/boo/scripts/start.sh.tftpl @@ -0,0 +1,31 @@ +#!/bin/bash +set -o errexit +set -o pipefail + +SESSION_NAME="${ARG_SESSION_NAME}" +FOLDER="${ARG_FOLDER}" +COMMAND=$(echo -n '${ARG_COMMAND}' | base64 -d) +LOG_PATH="${ARG_LOG_PATH}" +SYNC_NAME="${ARG_SYNC_NAME}" + +mkdir -p "$(dirname "$LOG_PATH")" + +trap 'coder exp sync complete "$SYNC_NAME"' EXIT +coder exp sync want "$SYNC_NAME" "${ARG_INSTALL_SYNC}" +coder exp sync start "$SYNC_NAME" + +( + export PATH="$HOME/.local/bin:$PATH" + + mkdir -p "$FOLDER" + cd "$FOLDER" + + if boo ls 2>/dev/null | grep -qF "$SESSION_NAME"; then + echo "boo session '$${SESSION_NAME}' already exists; skipping creation" + exit 0 + fi + + echo "Creating boo session '$${SESSION_NAME}'..." + boo new "$SESSION_NAME" -d -- bash -c "$COMMAND" + echo "boo session '$${SESSION_NAME}' created" +) 2>&1 | tee "$LOG_PATH" From 12969f10f6c06474a82be2add25b3745ed2e6d78 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Mon, 13 Jul 2026 18:17:47 +0000 Subject: [PATCH 02/21] debug(registry/coder/modules/boo): add [TEMP] logs to start script Add diagnostic logs to help trace session lifecycle issues: - command preview and length to verify base64 decode - boo binary path and version - boo ls before create, immediately after, and 3s after boo new to detect sessions that are created then immediately cleaned up because the inner process exits --- .../coder/modules/boo/scripts/start.sh.tftpl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/registry/coder/modules/boo/scripts/start.sh.tftpl b/registry/coder/modules/boo/scripts/start.sh.tftpl index 9f61198c7..ec12858fe 100644 --- a/registry/coder/modules/boo/scripts/start.sh.tftpl +++ b/registry/coder/modules/boo/scripts/start.sh.tftpl @@ -17,9 +17,19 @@ coder exp sync start "$SYNC_NAME" ( export PATH="$HOME/.local/bin:$PATH" + echo "[TEMP] session: $SESSION_NAME" + echo "[TEMP] folder: $FOLDER" + echo "[TEMP] command length: $${#COMMAND} chars" + echo "[TEMP] command preview: $(printf '%s' "$COMMAND" | head -c 120 | tr '\n' ' ')" + echo "[TEMP] boo binary: $(command -v boo 2>/dev/null || echo NOT_FOUND)" + echo "[TEMP] boo version: $(boo -V 2>&1 || echo 'boo -V failed')" + mkdir -p "$FOLDER" cd "$FOLDER" + echo "[TEMP] boo ls before create:" + boo ls 2>&1 || true + if boo ls 2>/dev/null | grep -qF "$SESSION_NAME"; then echo "boo session '$${SESSION_NAME}' already exists; skipping creation" exit 0 @@ -27,5 +37,14 @@ coder exp sync start "$SYNC_NAME" echo "Creating boo session '$${SESSION_NAME}'..." boo new "$SESSION_NAME" -d -- bash -c "$COMMAND" + echo "[TEMP] boo new returned exit 0" + + echo "[TEMP] boo ls immediately after create:" + boo ls 2>&1 || true + + sleep 3 + echo "[TEMP] boo ls 3s after create:" + boo ls 2>&1 || true + echo "boo session '$${SESSION_NAME}' created" ) 2>&1 | tee "$LOG_PATH" From 74cc3759e3537b5d27967f4404e42736fb80f33a Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Tue, 14 Jul 2026 03:22:29 +0000 Subject: [PATCH 03/21] wip --- registry/coder/modules/boo/main.tf | 43 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 0019b0f8f..9a2366cba 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -104,22 +104,22 @@ module "coder_utils" { install_script = local.install_script } -resource "coder_script" "boo_start" { - for_each = var.sessions - - agent_id = var.agent_id - display_name = "${var.display_name}: Start ${each.key}" - icon = var.icon - run_on_start = true - script = templatefile("${path.module}/scripts/start.sh.tftpl", { - ARG_SESSION_NAME = each.key - ARG_FOLDER = var.folder - ARG_COMMAND = base64encode(each.value) - ARG_LOG_PATH = "${local.module_dir}/logs/${each.key}/start.log" - ARG_SYNC_NAME = "coder-boo-${each.key}-start_script" - ARG_INSTALL_SYNC = local.install_phase_last - }) -} +# resource "coder_script" "boo_start" { +# for_each = var.sessions + +# agent_id = var.agent_id +# display_name = "${var.display_name}: Start ${each.key}" +# icon = var.icon +# run_on_start = true +# script = templatefile("${path.module}/scripts/start.sh.tftpl", { +# ARG_SESSION_NAME = each.key +# ARG_FOLDER = var.folder +# ARG_COMMAND = base64encode(each.value) +# ARG_LOG_PATH = "${local.module_dir}/logs/${each.key}/start.log" +# ARG_SYNC_NAME = "coder-boo-${each.key}-start_script" +# ARG_INSTALL_SYNC = local.install_phase_last +# }) +# } resource "coder_app" "boo" { for_each = var.sessions @@ -128,7 +128,16 @@ resource "coder_app" "boo" { slug = "${var.slug}-${replace(each.key, "_", "-")}" display_name = "${var.display_name}: ${each.key}" icon = var.icon - command = "coder exp sync want ${var.slug}-${replace(each.key, "_", "-")} coder-boo-${each.key}-start_script && export PATH=\"$HOME/.local/bin:$PATH\" && boo attach ${each.key}" + command = <<-EOT + #!/bin/bash + export PATH=\"$HOME/.local/bin:$PATH\" + if boo peek '${each.key}' >/dev/null 2>&1; then + boo attach '${each.key}' + else + boo new '${each.key}' + '${each.value}' + fi + EOT order = var.order group = var.group } From 148a48c973407d73444a3418df19602299d81c53 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Tue, 14 Jul 2026 03:40:47 +0000 Subject: [PATCH 04/21] wip --- registry/coder/modules/boo/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 9a2366cba..35d48de6b 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -130,7 +130,7 @@ resource "coder_app" "boo" { icon = var.icon command = <<-EOT #!/bin/bash - export PATH=\"$HOME/.local/bin:$PATH\" + export PATH="$HOME/.local/bin:$PATH" if boo peek '${each.key}' >/dev/null 2>&1; then boo attach '${each.key}' else From 550069513ab3cbc515b5548e091a03dcec355b9b Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Tue, 14 Jul 2026 06:03:56 +0000 Subject: [PATCH 05/21] wip --- registry/coder/modules/boo/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 35d48de6b..505947cd4 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -134,8 +134,8 @@ resource "coder_app" "boo" { if boo peek '${each.key}' >/dev/null 2>&1; then boo attach '${each.key}' else - boo new '${each.key}' - '${each.value}' + boo new '${each.key}' -- bash -c "${each.value}" + fi EOT order = var.order From 593d814f3038dbcc187ae67bf9c44a966e51068c Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Tue, 14 Jul 2026 06:29:12 +0000 Subject: [PATCH 06/21] wip --- registry/coder/modules/boo/main.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 505947cd4..4d663f216 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -134,8 +134,7 @@ resource "coder_app" "boo" { if boo peek '${each.key}' >/dev/null 2>&1; then boo attach '${each.key}' else - boo new '${each.key}' -- bash -c "${each.value}" - + boo new '${each.key}' -- bash -c "${each.value} 2>&1 | tee ${local.module_dir}/logs/${each.key}/start.log" fi EOT order = var.order From 4174e1f88ccb78e9e7226ff86d653889a641e63b Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Tue, 14 Jul 2026 06:40:49 +0000 Subject: [PATCH 07/21] wip --- registry/coder/modules/boo/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 4d663f216..f041d9dd6 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -134,6 +134,7 @@ resource "coder_app" "boo" { if boo peek '${each.key}' >/dev/null 2>&1; then boo attach '${each.key}' else + mkdir -p "${local.module_dir}/logs/${each.key}" boo new '${each.key}' -- bash -c "${each.value} 2>&1 | tee ${local.module_dir}/logs/${each.key}/start.log" fi EOT From 32de9ffccf379fb8dfc573f4cae74e0f9ce25d30 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Wed, 15 Jul 2026 13:42:39 +0000 Subject: [PATCH 08/21] wip --- registry/coder/modules/boo/main.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index f041d9dd6..cc43a3523 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -135,7 +135,8 @@ resource "coder_app" "boo" { boo attach '${each.key}' else mkdir -p "${local.module_dir}/logs/${each.key}" - boo new '${each.key}' -- bash -c "${each.value} 2>&1 | tee ${local.module_dir}/logs/${each.key}/start.log" + boo new '${each.key}' -d + boo send '${each.key}' --text "bash -c \"${each.value} 2>&1 | tee ${local.module_dir}/logs/${each.key}/start.log"\" --enter fi EOT order = var.order From 042ca6f274b238dbdd9272fa4755da2aa3ff4b2a Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Wed, 15 Jul 2026 13:50:24 +0000 Subject: [PATCH 09/21] wip --- registry/coder/modules/boo/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index cc43a3523..f5c1695c0 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -137,6 +137,7 @@ resource "coder_app" "boo" { mkdir -p "${local.module_dir}/logs/${each.key}" boo new '${each.key}' -d boo send '${each.key}' --text "bash -c \"${each.value} 2>&1 | tee ${local.module_dir}/logs/${each.key}/start.log"\" --enter + boo attach '${each.key}' fi EOT order = var.order From 7870d93115ab89b5b0b0fa94691a7dbb963ab4aa Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Wed, 15 Jul 2026 14:28:41 +0000 Subject: [PATCH 10/21] wip --- registry/coder/modules/boo/main.tf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index f5c1695c0..2ef26910b 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -135,8 +135,11 @@ resource "coder_app" "boo" { boo attach '${each.key}' else mkdir -p "${local.module_dir}/logs/${each.key}" + SCRIPT="${local.module_dir}/logs/${each.key}/start.sh" + printf '%s' '${base64encode(each.value)}' | base64 -d > "$SCRIPT" + chmod +x "$SCRIPT" boo new '${each.key}' -d - boo send '${each.key}' --text "bash -c \"${each.value} 2>&1 | tee ${local.module_dir}/logs/${each.key}/start.log"\" --enter + boo send '${each.key}' --text "$SCRIPT 2>&1 | tee ${local.module_dir}/logs/${each.key}/start.log" --enter boo attach '${each.key}' fi EOT From ee1d9d72674da91d0aabc45927cd140835c8f1b3 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Wed, 15 Jul 2026 17:31:41 +0000 Subject: [PATCH 11/21] wip --- registry/coder/modules/boo/main.tf | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 2ef26910b..f8417a145 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -104,23 +104,6 @@ module "coder_utils" { install_script = local.install_script } -# resource "coder_script" "boo_start" { -# for_each = var.sessions - -# agent_id = var.agent_id -# display_name = "${var.display_name}: Start ${each.key}" -# icon = var.icon -# run_on_start = true -# script = templatefile("${path.module}/scripts/start.sh.tftpl", { -# ARG_SESSION_NAME = each.key -# ARG_FOLDER = var.folder -# ARG_COMMAND = base64encode(each.value) -# ARG_LOG_PATH = "${local.module_dir}/logs/${each.key}/start.log" -# ARG_SYNC_NAME = "coder-boo-${each.key}-start_script" -# ARG_INSTALL_SYNC = local.install_phase_last -# }) -# } - resource "coder_app" "boo" { for_each = var.sessions @@ -134,12 +117,13 @@ resource "coder_app" "boo" { if boo peek '${each.key}' >/dev/null 2>&1; then boo attach '${each.key}' else - mkdir -p "${local.module_dir}/logs/${each.key}" - SCRIPT="${local.module_dir}/logs/${each.key}/start.sh" + SESSION_DIR="${local.module_dir}/${each.key}" + mkdir -p "$SESSION_DIR/scripts" "$SESSION_DIR/logs" + SCRIPT="$SESSION_DIR/scripts/start.sh" printf '%s' '${base64encode(each.value)}' | base64 -d > "$SCRIPT" chmod +x "$SCRIPT" boo new '${each.key}' -d - boo send '${each.key}' --text "$SCRIPT 2>&1 | tee ${local.module_dir}/logs/${each.key}/start.log" --enter + boo send '${each.key}' --text "$SCRIPT 2>&1 | tee $SESSION_DIR/logs/start.log" --enter boo attach '${each.key}' fi EOT From 783a0fa933e69712e513be0fed9cd07df2e1087f Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 10:09:18 +0000 Subject: [PATCH 12/21] wip --- registry/coder/modules/boo/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index f8417a145..d78bf8531 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -123,6 +123,7 @@ resource "coder_app" "boo" { printf '%s' '${base64encode(each.value)}' | base64 -d > "$SCRIPT" chmod +x "$SCRIPT" boo new '${each.key}' -d + boo wait '${each.key}' --idle boo send '${each.key}' --text "$SCRIPT 2>&1 | tee $SESSION_DIR/logs/start.log" --enter boo attach '${each.key}' fi From 8a165a0c2d33b7583f9e2feb1ed4fdaa271ea73d Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 10:22:55 +0000 Subject: [PATCH 13/21] wip --- registry/coder/modules/boo/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index d78bf8531..f7d965931 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -118,13 +118,13 @@ resource "coder_app" "boo" { boo attach '${each.key}' else SESSION_DIR="${local.module_dir}/${each.key}" - mkdir -p "$SESSION_DIR/scripts" "$SESSION_DIR/logs" + mkdir -p "$SESSION_DIR/scripts" SCRIPT="$SESSION_DIR/scripts/start.sh" printf '%s' '${base64encode(each.value)}' | base64 -d > "$SCRIPT" chmod +x "$SCRIPT" boo new '${each.key}' -d boo wait '${each.key}' --idle - boo send '${each.key}' --text "$SCRIPT 2>&1 | tee $SESSION_DIR/logs/start.log" --enter + boo send '${each.key}' --text "$SCRIPT" --enter boo attach '${each.key}' fi EOT From 5774ad43cb696943e56fb6ac5ea7594390cae6b2 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 13:15:37 +0000 Subject: [PATCH 14/21] feat: add tests --- registry/coder/modules/boo/README.md | 49 ++-- registry/coder/modules/boo/main.test.ts | 249 +++++++++++++++++++++ registry/coder/modules/boo/main.tf | 16 +- registry/coder/modules/boo/main.tftest.hcl | 23 +- 4 files changed, 291 insertions(+), 46 deletions(-) create mode 100644 registry/coder/modules/boo/main.test.ts diff --git a/registry/coder/modules/boo/README.md b/registry/coder/modules/boo/README.md index d37c13d66..ffb3e3f7f 100644 --- a/registry/coder/modules/boo/README.md +++ b/registry/coder/modules/boo/README.md @@ -8,7 +8,7 @@ tags: [terminal, multiplexer, session, boo] # Boo -Install [boo](https://github.com/coder/boo) and run commands in persistent, named terminal sessions. Boo is a GNU screen-style terminal multiplexer built on [libghostty](https://github.com/ghostty-org/ghostty) (Zig). Pass a map of session names to commands and the module creates one `coder_app` and one start script per session, all serialized after the install pipeline. +Install [boo](https://github.com/coder/boo) and run commands in persistent, named terminal sessions. Boo is a GNU screen-style terminal multiplexer built on [libghostty](https://github.com/ghostty-org/ghostty) (Zig). Pass a map of session names to commands and the module creates one `coder_app` per session. Clicking an app creates the session and attaches to it; clicking again reattaches to the running session. ```tf module "boo" { @@ -32,7 +32,6 @@ module "boo" { source = "registry.coder.com/coder/boo/coder" version = "1.0.0" agent_id = coder_agent.main.id - folder = "/home/coder/project" sessions = { server = "npm run dev" shell = "bash" @@ -44,7 +43,27 @@ This creates: - `coder_app` slugs `boo-server` and `boo-shell` - Display names `Boo: server` and `Boo: shell` -- Logs at `~/.coder-modules/coder/boo/logs/server/start.log` and `.../shell/start.log` + +### Multi-line commands + +Session commands can be full shell scripts. The script is written to `~/.coder-modules/coder/boo//scripts/start.sh` and executed inside the boo session. + +```tf +module "boo" { + source = "registry.coder.com/coder/boo/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + sessions = { + watcher = <<-EOT + #!/bin/bash + while true; do + echo "$(date): watching..." + sleep 10 + done + EOT + } +} +``` ### Pin a specific boo version @@ -101,16 +120,16 @@ module "boo" { } ``` -### Serialize another module behind boo +### Serialize another module behind the boo install -Use `output.scripts` to wait for all boo sessions to start before running downstream work. +Use `output.scripts` to wait for the boo install pipeline to complete before running downstream work. ```tf module "boo" { source = "registry.coder.com/coder/boo/coder" version = "1.0.0" agent_id = coder_agent.main.id - sessions = { server = "npm run dev", shell = "bash" } + sessions = { shell = "bash" } } resource "coder_script" "after_boo" { @@ -122,7 +141,7 @@ resource "coder_script" "after_boo" { coder exp sync want after-boo ${join(" ", module.boo.scripts)} coder exp sync start after-boo trap 'coder exp sync complete after-boo' EXIT - echo "All boo sessions are up" + echo "boo install complete" EOT } ``` @@ -140,15 +159,15 @@ Session names must be valid as Terraform map keys. Underscores in session names ## Troubleshooting -Logs are written per session under `~/.coder-modules/coder/boo/logs/`: +The install log is written under `~/.coder-modules/coder/boo/logs/`. Session scripts are written to `~/.coder-modules/coder/boo//scripts/start.sh`. ``` -~/.coder-modules/coder/boo/logs/ -├── install.log -├── / -│ └── start.log +~/.coder-modules/coder/boo/ +├── logs/ +│ └── install.log +└── / + └── scripts/ + └── start.sh ``` -Check `install.log` for installation errors. Check `/start.log` for session creation errors. - -If an app does not connect, verify the session exists by running `boo ls` in a terminal. +Check `install.log` for installation errors. If an app does not connect, verify the session exists by running `boo ls` in a terminal. diff --git a/registry/coder/modules/boo/main.test.ts b/registry/coder/modules/boo/main.test.ts new file mode 100644 index 000000000..a736900e5 --- /dev/null +++ b/registry/coder/modules/boo/main.test.ts @@ -0,0 +1,249 @@ +import { + test, + afterEach, + describe, + setDefaultTimeout, + beforeAll, + expect, +} from "bun:test"; +import { + execContainer, + removeContainer, + runContainer, + runTerraformApply, + runTerraformInit, + TerraformState, +} from "~test"; +import { writeExecutable } from "../agentapi/test-util"; + +interface ModuleScripts { + pre_install?: string; + install: string; + post_install?: string; +} + +const SCRIPT_SUFFIXES = [ + "Pre-Install Script", + "Install Script", + "Post-Install Script", +] as const; + +const collectScripts = (state: TerraformState): ModuleScripts => { + const byDisplayName: Record = {}; + for (const resource of state.resources) { + if (resource.type !== "coder_script") continue; + for (const instance of resource.instances) { + const attrs = instance.attributes as Record; + const displayName = attrs.display_name as string | undefined; + const script = attrs.script as string | undefined; + if (displayName && script) { + byDisplayName[displayName] = script; + } + } + } + const scripts: Partial = {}; + for (const suffix of SCRIPT_SUFFIXES) { + const key = `Boo: ${suffix}`; + if (!(key in byDisplayName)) continue; + switch (suffix) { + case "Pre-Install Script": + scripts.pre_install = byDisplayName[key]; + break; + case "Install Script": + scripts.install = byDisplayName[key]; + break; + case "Post-Install Script": + scripts.post_install = byDisplayName[key]; + break; + } + } + if (!scripts.install) { + throw new Error("install script not found in terraform state"); + } + return scripts as ModuleScripts; +}; + +const findAppBySlug = ( + state: TerraformState, + slug: string, +): Record => { + for (const resource of state.resources) { + if (resource.type !== "coder_app") continue; + for (const instance of resource.instances) { + const attrs = instance.attributes as Record; + if (attrs.slug === slug) return attrs; + } + } + throw new Error(`coder_app with slug '${slug}' not found in terraform state`); +}; + +const countApps = (state: TerraformState): number => { + let count = 0; + for (const resource of state.resources) { + if (resource.type === "coder_app") { + count += resource.instances.length; + } + } + return count; +}; + +let cleanupFunctions: (() => Promise)[] = []; +const registerCleanup = (cleanup: () => Promise) => { + cleanupFunctions.push(cleanup); +}; +afterEach(async () => { + const fns = cleanupFunctions.slice().reverse(); + cleanupFunctions = []; + for (const fn of fns) { + try { + await fn(); + } catch (error) { + console.error("Error during cleanup:", error); + } + } +}); + +const setup = async (moduleVariables?: Record) => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + sessions: '{"main":"bash"}', + install_boo: "false", + ...moduleVariables, + }); + const scripts = collectScripts(state); + const id = await runContainer("codercom/enterprise-node:latest"); + registerCleanup(async () => { + if (process.env["DEBUG"] === "true" || process.env["DEBUG"] === "1") return; + await removeContainer(id); + }); + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/coder", + content: "#!/bin/bash\nexit 0\n", + }); + return { id, state, scripts }; +}; + +const runScripts = async (id: string, scripts: ModuleScripts) => { + const ordered: [string, string | undefined][] = [ + ["pre_install", scripts.pre_install], + ["install", scripts.install], + ["post_install", scripts.post_install], + ]; + for (const [name, script] of ordered) { + if (!script) continue; + const target = `/tmp/boo-${name}.sh`; + await writeExecutable({ + containerId: id, + filePath: target, + content: script, + }); + const resp = await execContainer(id, ["bash", "-c", target]); + if (resp.exitCode !== 0) { + throw new Error( + `${name} script exited ${resp.exitCode}:\n${resp.stdout}\n${resp.stderr}`, + ); + } + } +}; + +setDefaultTimeout(60 * 1000); + +describe("boo", async () => { + beforeAll(async () => { + await runTerraformInit(import.meta.dir); + }); + + test("defaults", async () => { + const { state } = await setup(); + const app = findAppBySlug(state, "boo-main"); + expect(app.display_name).toBe("Boo: main"); + expect(app.icon).toBe("/icon/boo.svg"); + expect(app.order).toBeNull(); + expect(app.group).toBeNull(); + }); + + test("multiple-sessions-create-multiple-apps", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + sessions: '{"alpha":"bash","beta":"vim"}', + }); + expect(countApps(state)).toBe(2); + const alpha = findAppBySlug(state, "boo-alpha"); + const beta = findAppBySlug(state, "boo-beta"); + expect(alpha.display_name).toBe("Boo: alpha"); + expect(beta.display_name).toBe("Boo: beta"); + }); + + test("underscore-in-session-name-becomes-hyphen-in-slug", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + sessions: '{"my_session":"bash"}', + }); + const app = findAppBySlug(state, "boo-my-session"); + expect(app.slug).toBe("boo-my-session"); + expect(app.display_name).toBe("Boo: my_session"); + }); + + test("custom-slug-and-display-name", async () => { + const { state } = await setup({ slug: "term", display_name: "Terminal" }); + const app = findAppBySlug(state, "term-main"); + expect(app.display_name).toBe("Terminal: main"); + }); + + test("order-and-group", async () => { + const { state } = await setup({ order: "5", group: "ai-tools" }); + const app = findAppBySlug(state, "boo-main"); + expect(app.order).toBe(5); + expect(app.group).toBe("ai-tools"); + }); + + test("install-skipped-when-install-boo-false", async () => { + const { id, scripts } = await setup({ install_boo: "false" }); + await runScripts(id, scripts); + const resp = await execContainer(id, [ + "bash", + "-c", + "command -v boo && echo FOUND || echo ABSENT", + ]); + expect(resp.stdout.trim()).toBe("ABSENT"); + }); + + test("install-skipped-when-boo-already-installed", async () => { + const { id, scripts } = await setup({ install_boo: "true" }); + await execContainer(id, ["mkdir", "-p", "/home/coder/.local/bin"]); + await writeExecutable({ + containerId: id, + filePath: "/home/coder/.local/bin/boo", + content: + '#!/bin/bash\nif [ "$1" = "-V" ]; then echo "0.6.4"; fi\nexit 0\n', + }); + const resp = await execContainer(id, [ + "bash", + "-c", + `export PATH="$HOME/.local/bin:$PATH" && /tmp/boo-install.sh 2>&1 || true`, + ]); + // Write and run the install script directly to capture output + await writeExecutable({ + containerId: id, + filePath: "/tmp/boo-install.sh", + content: scripts.install, + }); + const result = await execContainer(id, [ + "bash", + "-c", + 'export PATH="$HOME/.local/bin:$PATH" && /tmp/boo-install.sh', + ]); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("already installed"); + }); + + test("pre-post-install-scripts", async () => { + const { scripts } = await setup({ + pre_install_script: "#!/bin/bash\necho 'boo-pre-install'", + post_install_script: "#!/bin/bash\necho 'boo-post-install'", + }); + expect(scripts.pre_install).toBeDefined(); + expect(scripts.post_install).toBeDefined(); + }); +}); diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index f7d965931..78ef401d0 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -19,12 +19,6 @@ variable "sessions" { description = "Map of session names to commands. A boo session and coder_app are created for each entry." } -variable "folder" { - type = string - description = "The working directory for boo sessions." - default = "/home/coder" -} - variable "install_boo" { type = bool description = "Whether to install boo." @@ -86,9 +80,6 @@ locals { ARG_INSTALL_BOO = tostring(var.install_boo) ARG_BOO_VERSION = var.boo_version }) - - # Last sync name from the install pipeline; per-session start scripts wait on this. - install_phase_last = module.coder_utils.scripts[length(module.coder_utils.scripts) - 1] } module "coder_utils" { @@ -133,9 +124,6 @@ resource "coder_app" "boo" { } output "scripts" { - description = "Ordered list of coder exp sync names for the coder_script resources this module creates, in run order. Includes install phase scripts followed by per-session start scripts (sorted by session name)." - value = concat( - module.coder_utils.scripts, - [for name in sort(keys(var.sessions)) : "coder-boo-${name}-start_script"] - ) + description = "Ordered list of coder exp sync names for the install pipeline scripts, in run order." + value = module.coder_utils.scripts } diff --git a/registry/coder/modules/boo/main.tftest.hcl b/registry/coder/modules/boo/main.tftest.hcl index ee1f75f23..6bbdd679a 100644 --- a/registry/coder/modules/boo/main.tftest.hcl +++ b/registry/coder/modules/boo/main.tftest.hcl @@ -6,11 +6,6 @@ run "test_defaults" { sessions = { main = "bash" } } - assert { - condition = var.folder == "/home/coder" - error_message = "folder should default to '/home/coder'" - } - assert { condition = var.install_boo == true error_message = "install_boo should default to true" @@ -53,18 +48,12 @@ run "test_single_session" { variables { agent_id = "test-agent-single" sessions = { dev = "make dev" } - folder = "/home/coder/project" } assert { condition = var.sessions["dev"] == "make dev" error_message = "sessions map should contain the correct command for key 'dev'" } - - assert { - condition = var.folder == "/home/coder/project" - error_message = "folder should be '/home/coder/project'" - } } run "test_multiple_sessions" { @@ -194,8 +183,8 @@ run "test_scripts_output_single_session" { } assert { - condition = output.scripts == ["coder-boo-install_script", "coder-boo-main-start_script"] - error_message = "scripts output should list install_script then session start_script" + condition = output.scripts == ["coder-boo-install_script"] + error_message = "scripts output should list install_script" } } @@ -211,8 +200,8 @@ run "test_scripts_output_multi_session" { } assert { - condition = output.scripts == ["coder-boo-install_script", "coder-boo-alpha-start_script", "coder-boo-beta-start_script"] - error_message = "scripts output should list install_script then session start_scripts sorted by name" + condition = output.scripts == ["coder-boo-install_script"] + error_message = "scripts output should list install_script" } } @@ -227,7 +216,7 @@ run "test_scripts_output_with_hooks" { } assert { - condition = output.scripts == ["coder-boo-pre_install_script", "coder-boo-install_script", "coder-boo-post_install_script", "coder-boo-main-start_script"] - error_message = "scripts output should list all four scripts in run order" + condition = output.scripts == ["coder-boo-pre_install_script", "coder-boo-install_script", "coder-boo-post_install_script"] + error_message = "scripts output should list pre_install, install, and post_install scripts in run order" } } From 04b626935731a639bfca3991a8851c15a78715a1 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 13:15:57 +0000 Subject: [PATCH 15/21] del: remove unwanted files --- .../coder/modules/boo/scripts/start.sh.tftpl | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 registry/coder/modules/boo/scripts/start.sh.tftpl diff --git a/registry/coder/modules/boo/scripts/start.sh.tftpl b/registry/coder/modules/boo/scripts/start.sh.tftpl deleted file mode 100644 index ec12858fe..000000000 --- a/registry/coder/modules/boo/scripts/start.sh.tftpl +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -set -o errexit -set -o pipefail - -SESSION_NAME="${ARG_SESSION_NAME}" -FOLDER="${ARG_FOLDER}" -COMMAND=$(echo -n '${ARG_COMMAND}' | base64 -d) -LOG_PATH="${ARG_LOG_PATH}" -SYNC_NAME="${ARG_SYNC_NAME}" - -mkdir -p "$(dirname "$LOG_PATH")" - -trap 'coder exp sync complete "$SYNC_NAME"' EXIT -coder exp sync want "$SYNC_NAME" "${ARG_INSTALL_SYNC}" -coder exp sync start "$SYNC_NAME" - -( - export PATH="$HOME/.local/bin:$PATH" - - echo "[TEMP] session: $SESSION_NAME" - echo "[TEMP] folder: $FOLDER" - echo "[TEMP] command length: $${#COMMAND} chars" - echo "[TEMP] command preview: $(printf '%s' "$COMMAND" | head -c 120 | tr '\n' ' ')" - echo "[TEMP] boo binary: $(command -v boo 2>/dev/null || echo NOT_FOUND)" - echo "[TEMP] boo version: $(boo -V 2>&1 || echo 'boo -V failed')" - - mkdir -p "$FOLDER" - cd "$FOLDER" - - echo "[TEMP] boo ls before create:" - boo ls 2>&1 || true - - if boo ls 2>/dev/null | grep -qF "$SESSION_NAME"; then - echo "boo session '$${SESSION_NAME}' already exists; skipping creation" - exit 0 - fi - - echo "Creating boo session '$${SESSION_NAME}'..." - boo new "$SESSION_NAME" -d -- bash -c "$COMMAND" - echo "[TEMP] boo new returned exit 0" - - echo "[TEMP] boo ls immediately after create:" - boo ls 2>&1 || true - - sleep 3 - echo "[TEMP] boo ls 3s after create:" - boo ls 2>&1 || true - - echo "boo session '$${SESSION_NAME}' created" -) 2>&1 | tee "$LOG_PATH" From 8ef6f99f15a42d2a359ef572670e77db814787df Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 16:09:40 +0000 Subject: [PATCH 16/21] feat: slug updates --- registry/coder/modules/boo/README.md | 60 ++++------------------ registry/coder/modules/boo/main.test.ts | 21 ++++++++ registry/coder/modules/boo/main.tf | 10 +++- registry/coder/modules/boo/main.tftest.hcl | 52 ++++++++++++++++++- 4 files changed, 90 insertions(+), 53 deletions(-) diff --git a/registry/coder/modules/boo/README.md b/registry/coder/modules/boo/README.md index ffb3e3f7f..444f2067c 100644 --- a/registry/coder/modules/boo/README.md +++ b/registry/coder/modules/boo/README.md @@ -15,9 +15,6 @@ module "boo" { source = "registry.coder.com/coder/boo/coder" version = "1.0.0" agent_id = coder_agent.main.id - sessions = { - shell = "bash" - } } ``` @@ -25,7 +22,7 @@ module "boo" { ### Multiple sessions -Create separate persistent sessions for a dev server and an interactive shell, each with its own dashboard app. +Create separate persistent sessions for a dev server and an interactive shell, each with its own coder_app. ```tf module "boo" { @@ -65,47 +62,7 @@ module "boo" { } ``` -### Pin a specific boo version - -```tf -module "boo" { - source = "registry.coder.com/coder/boo/coder" - version = "1.0.0" - agent_id = coder_agent.main.id - boo_version = "v0.6.4" - sessions = { shell = "bash" } -} -``` - -### Skip installation (boo pre-installed in image) - -```tf -module "boo" { - source = "registry.coder.com/coder/boo/coder" - version = "1.0.0" - agent_id = coder_agent.main.id - install_boo = false - sessions = { shell = "bash" } -} -``` - -### Customize app appearance - -```tf -module "boo" { - source = "registry.coder.com/coder/boo/coder" - version = "1.0.0" - agent_id = coder_agent.main.id - display_name = "Terminal" - slug = "term" - icon = "/icon/terminal.svg" - order = 1 - group = "Development" - sessions = { shell = "bash" } -} -``` - -Apps are named `Terminal: shell` with slug `term-shell`. +Apps are named `Boo: watcher` with slug `boo-watcher`. ### Use pre/post install hooks @@ -148,14 +105,15 @@ resource "coder_script" "after_boo" { ## Naming -App slugs and display names are derived from session names: +App slugs and display names are derived from the `slug`, `display_name`, and session name variable. +For example: -| `slug` (default `"boo"`) | session name | app slug | display name | -| ------------------------ | -------------- | ------------------- | -------------------- | -| `"boo"` | `"shell"` | `"boo-shell"` | `"Boo: shell"` | -| `"term"` | `"dev_server"` | `"term-dev-server"` | `"term: dev_server"` | +| `slug` | `display_name` | session name | app slug | display name | +| -------- | -------------- | --------------- | ------------------- | -------------------- | +| `"boo"` | `"Boo"` | `"Claude Code"` | `"boo-claude-code"` | `"Boo: Claude Code"` | +| `"term"` | `"Terminal"` | `"shell"` | `"term-shell"` | `"Terminal: shell"` | -Session names must be valid as Terraform map keys. Underscores in session names are replaced with hyphens in app slugs. +Session names are normalized for app slugs: lowercased, runs of non-alphanumeric characters replaced with a single hyphen, leading/trailing hyphens trimmed. Display names always use the raw session key. ## Troubleshooting diff --git a/registry/coder/modules/boo/main.test.ts b/registry/coder/modules/boo/main.test.ts index a736900e5..fd5ffb68f 100644 --- a/registry/coder/modules/boo/main.test.ts +++ b/registry/coder/modules/boo/main.test.ts @@ -185,6 +185,19 @@ describe("boo", async () => { expect(app.display_name).toBe("Boo: my_session"); }); + test("uppercase-and-spaces-in-session-name-normalized-in-slug", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + sessions: '{"Claude Code":"claude","Codex":"codex"}', + }); + const claude = findAppBySlug(state, "boo-claude-code"); + expect(claude.slug).toBe("boo-claude-code"); + expect(claude.display_name).toBe("Boo: Claude Code"); + const codex = findAppBySlug(state, "boo-codex"); + expect(codex.slug).toBe("boo-codex"); + expect(codex.display_name).toBe("Boo: Codex"); + }); + test("custom-slug-and-display-name", async () => { const { state } = await setup({ slug: "term", display_name: "Terminal" }); const app = findAppBySlug(state, "term-main"); @@ -246,4 +259,12 @@ describe("boo", async () => { expect(scripts.pre_install).toBeDefined(); expect(scripts.post_install).toBeDefined(); }); + + test("install-only-no-sessions", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + install_boo: "false", + }); + expect(countApps(state)).toBe(0); + }); }); diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 78ef401d0..8c2b54f9d 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -17,6 +17,7 @@ variable "agent_id" { variable "sessions" { type = map(string) description = "Map of session names to commands. A boo session and coder_app are created for each entry." + default = {} } variable "install_boo" { @@ -80,6 +81,13 @@ locals { ARG_INSTALL_BOO = tostring(var.install_boo) ARG_BOO_VERSION = var.boo_version }) + + session_slugs = { + for k, v in var.sessions : k => replace( + replace(lower(k), "/[^a-z0-9]+/", "-"), + "/^-+|-+$/", "" + ) + } } module "coder_utils" { @@ -99,7 +107,7 @@ resource "coder_app" "boo" { for_each = var.sessions agent_id = var.agent_id - slug = "${var.slug}-${replace(each.key, "_", "-")}" + slug = "${var.slug}-${local.session_slugs[each.key]}" display_name = "${var.display_name}: ${each.key}" icon = var.icon command = <<-EOT diff --git a/registry/coder/modules/boo/main.tftest.hcl b/registry/coder/modules/boo/main.tftest.hcl index 6bbdd679a..a6b74ed6b 100644 --- a/registry/coder/modules/boo/main.tftest.hcl +++ b/registry/coder/modules/boo/main.tftest.hcl @@ -3,7 +3,11 @@ run "test_defaults" { variables { agent_id = "test-agent-defaults" - sessions = { main = "bash" } + } + + assert { + condition = var.sessions == {} + error_message = "sessions should default to {}" } assert { @@ -220,3 +224,49 @@ run "test_scripts_output_with_hooks" { error_message = "scripts output should list pre_install, install, and post_install scripts in run order" } } + +run "test_slug_normalization" { + command = plan + + variables { + agent_id = "test-agent-slug-norm" + sessions = { + "Claude Code" = "claude" + "Codex" = "codex" + my_session = "bash" + } + } + + assert { + condition = local.session_slugs["Claude Code"] == "claude-code" + error_message = "'Claude Code' should normalize to 'claude-code'" + } + + assert { + condition = local.session_slugs["Codex"] == "codex" + error_message = "'Codex' should normalize to 'codex'" + } + + assert { + condition = local.session_slugs["my_session"] == "my-session" + error_message = "'my_session' should normalize to 'my-session'" + } +} + +run "test_install_only" { + command = plan + + variables { + agent_id = "test-agent-install-only" + } + + assert { + condition = var.sessions == {} + error_message = "sessions should default to {}" + } + + assert { + condition = length(coder_app.boo) == 0 + error_message = "no coder_app resources should be created with no sessions" + } +} From af060e1bbc0e2b683d82bcef3432c65884344a92 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 16:28:36 +0000 Subject: [PATCH 17/21] feat: add image to readme --- registry/coder/.images/boo.png | Bin 0 -> 136956 bytes registry/coder/modules/boo/README.md | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 registry/coder/.images/boo.png diff --git a/registry/coder/.images/boo.png b/registry/coder/.images/boo.png new file mode 100644 index 0000000000000000000000000000000000000000..c861de2878c10b20c19efacb6846409c2d115b54 GIT binary patch literal 136956 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 Date: Thu, 16 Jul 2026 16:34:02 +0000 Subject: [PATCH 18/21] chore: fix tftest --- registry/coder/modules/boo/main.tftest.hcl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder/modules/boo/main.tftest.hcl b/registry/coder/modules/boo/main.tftest.hcl index a6b74ed6b..14eae366b 100644 --- a/registry/coder/modules/boo/main.tftest.hcl +++ b/registry/coder/modules/boo/main.tftest.hcl @@ -6,7 +6,7 @@ run "test_defaults" { } assert { - condition = var.sessions == {} + condition = length(var.sessions) == 0 error_message = "sessions should default to {}" } @@ -261,7 +261,7 @@ run "test_install_only" { } assert { - condition = var.sessions == {} + condition = length(var.sessions) == 0 error_message = "sessions should default to {}" } From 7abb68e700673b41c840bc882815d3328e4433b3 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 16:43:57 +0000 Subject: [PATCH 19/21] feat: configurable install url --- registry/coder/modules/boo/main.test.ts | 16 +++++++++++++++ registry/coder/modules/boo/main.tf | 11 ++++++++-- registry/coder/modules/boo/main.tftest.hcl | 20 +++++++++++++++++++ .../modules/boo/scripts/install.sh.tftpl | 3 ++- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/registry/coder/modules/boo/main.test.ts b/registry/coder/modules/boo/main.test.ts index fd5ffb68f..cac297d89 100644 --- a/registry/coder/modules/boo/main.test.ts +++ b/registry/coder/modules/boo/main.test.ts @@ -260,6 +260,22 @@ describe("boo", async () => { expect(scripts.post_install).toBeDefined(); }); + test("custom-install-script-url", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + sessions: '{"main":"bash"}', + install_boo: "true", + install_script_url: "https://mirror.example.com/boo/install.sh", + }); + const scripts = collectScripts(state); + const match = scripts.install.match( + /echo -n '([A-Za-z0-9+/=]+)' \| base64 -d/, + ); + expect(match).not.toBeNull(); + const decoded = Buffer.from(match![1], "base64").toString("utf8"); + expect(decoded).toContain("https://mirror.example.com/boo/install.sh"); + }); + test("install-only-no-sessions", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 8c2b54f9d..68794a910 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -62,6 +62,12 @@ variable "group" { default = null } +variable "install_script_url" { + type = string + description = "URL of the boo install.sh script. Override for air-gapped or mirrored environments." + default = "https://raw.githubusercontent.com/coder/boo/main/install.sh" +} + variable "pre_install_script" { type = string description = "Custom script to run before installing boo." @@ -78,8 +84,9 @@ locals { module_dir = "$HOME/.coder-modules/coder/boo" install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { - ARG_INSTALL_BOO = tostring(var.install_boo) - ARG_BOO_VERSION = var.boo_version + ARG_INSTALL_BOO = tostring(var.install_boo) + ARG_BOO_VERSION = var.boo_version + ARG_INSTALL_SCRIPT_URL = var.install_script_url }) session_slugs = { diff --git a/registry/coder/modules/boo/main.tftest.hcl b/registry/coder/modules/boo/main.tftest.hcl index 14eae366b..34485ba87 100644 --- a/registry/coder/modules/boo/main.tftest.hcl +++ b/registry/coder/modules/boo/main.tftest.hcl @@ -40,6 +40,11 @@ run "test_defaults" { error_message = "order should default to null" } + assert { + condition = var.install_script_url == "https://raw.githubusercontent.com/coder/boo/main/install.sh" + error_message = "install_script_url should default to the upstream GitHub URL" + } + assert { condition = var.group == null error_message = "group should default to null" @@ -225,6 +230,21 @@ run "test_scripts_output_with_hooks" { } } +run "test_custom_install_script_url" { + command = plan + + variables { + agent_id = "test-agent-url" + sessions = { main = "bash" } + install_script_url = "https://mirror.example.com/boo/install.sh" + } + + assert { + condition = var.install_script_url == "https://mirror.example.com/boo/install.sh" + error_message = "install_script_url should be overridable" + } +} + run "test_slug_normalization" { command = plan diff --git a/registry/coder/modules/boo/scripts/install.sh.tftpl b/registry/coder/modules/boo/scripts/install.sh.tftpl index d565e2f1b..5f5f5ad4b 100644 --- a/registry/coder/modules/boo/scripts/install.sh.tftpl +++ b/registry/coder/modules/boo/scripts/install.sh.tftpl @@ -4,6 +4,7 @@ set -o pipefail INSTALL_BOO="${ARG_INSTALL_BOO}" BOO_VERSION="${ARG_BOO_VERSION}" +INSTALL_SCRIPT_URL="${ARG_INSTALL_SCRIPT_URL}" if [ "$INSTALL_BOO" != "true" ]; then echo "Skipping boo installation (install_boo=false)" @@ -35,5 +36,5 @@ if [ "$BOO_VERSION" != "latest" ]; then fi echo "Installing boo..." -curl -fsSL https://raw.githubusercontent.com/coder/boo/main/install.sh | sh +curl -fsSL "$INSTALL_SCRIPT_URL" | sh echo "boo installation complete" From f4d528c33647922839d4c29380f54ffd5e591564 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 16:55:14 +0000 Subject: [PATCH 20/21] feat: change icon --- .icons/boo.svg | 12 ------------ registry/coder/modules/boo/README.md | 2 +- registry/coder/modules/boo/main.test.ts | 2 +- registry/coder/modules/boo/main.tf | 2 +- registry/coder/modules/boo/main.tftest.hcl | 4 ++-- 5 files changed, 5 insertions(+), 17 deletions(-) delete mode 100644 .icons/boo.svg diff --git a/.icons/boo.svg b/.icons/boo.svg deleted file mode 100644 index a0de3d745..000000000 --- a/.icons/boo.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/registry/coder/modules/boo/README.md b/registry/coder/modules/boo/README.md index 855f08da2..7d13f9bda 100644 --- a/registry/coder/modules/boo/README.md +++ b/registry/coder/modules/boo/README.md @@ -1,7 +1,7 @@ --- display_name: Boo description: Run commands in persistent boo terminal sessions, one app per session. -icon: ../../../../.icons/boo.svg +icon: ../../../../.icons/coder.svg verified: false tags: [terminal, multiplexer, session, boo] --- diff --git a/registry/coder/modules/boo/main.test.ts b/registry/coder/modules/boo/main.test.ts index cac297d89..cfaee7604 100644 --- a/registry/coder/modules/boo/main.test.ts +++ b/registry/coder/modules/boo/main.test.ts @@ -158,7 +158,7 @@ describe("boo", async () => { const { state } = await setup(); const app = findAppBySlug(state, "boo-main"); expect(app.display_name).toBe("Boo: main"); - expect(app.icon).toBe("/icon/boo.svg"); + expect(app.icon).toBe("/icon/coder.svg"); expect(app.order).toBeNull(); expect(app.group).toBeNull(); }); diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index 68794a910..ca06eb9cb 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -47,7 +47,7 @@ variable "slug" { variable "icon" { type = string description = "The icon to use for boo apps and scripts." - default = "/icon/boo.svg" + default = "/icon/coder.svg" } variable "order" { diff --git a/registry/coder/modules/boo/main.tftest.hcl b/registry/coder/modules/boo/main.tftest.hcl index 34485ba87..d690cb451 100644 --- a/registry/coder/modules/boo/main.tftest.hcl +++ b/registry/coder/modules/boo/main.tftest.hcl @@ -31,8 +31,8 @@ run "test_defaults" { } assert { - condition = var.icon == "/icon/boo.svg" - error_message = "icon should default to '/icon/boo.svg'" + condition = var.icon == "/icon/coder.svg" + error_message = "icon should default to '/icon/coder.svg'" } assert { From cd68c57205a7b7ae968520d3c4e08636d4896c18 Mon Sep 17 00:00:00 2001 From: Jay Kumar Date: Thu, 16 Jul 2026 17:46:29 +0000 Subject: [PATCH 21/21] feat: sessions schema change --- registry/coder/modules/boo/README.md | 85 +++++--- registry/coder/modules/boo/main.test.ts | 66 +++--- registry/coder/modules/boo/main.tf | 61 +++--- registry/coder/modules/boo/main.tftest.hcl | 240 ++++++++++++++------- 4 files changed, 285 insertions(+), 167 deletions(-) diff --git a/registry/coder/modules/boo/README.md b/registry/coder/modules/boo/README.md index 7d13f9bda..6feb155cd 100644 --- a/registry/coder/modules/boo/README.md +++ b/registry/coder/modules/boo/README.md @@ -10,7 +10,7 @@ tags: [terminal, multiplexer, session, boo] ![Boo sessions in a Coder workspace](../../.images/boo.png) -Install [boo](https://github.com/coder/boo) and run commands in persistent, named terminal sessions. Boo is a GNU screen-style terminal multiplexer built on [libghostty](https://github.com/ghostty-org/ghostty) (Zig). Pass a map of session names to commands and the module creates one `coder_app` per session. Clicking an app creates the session and attaches to it; clicking again reattaches to the running session. +Install [boo](https://github.com/coder/boo) and run commands in persistent, named terminal sessions. Boo is a GNU screen-style terminal multiplexer built on [libghostty](https://github.com/ghostty-org/ghostty) (Zig). ```tf module "boo" { @@ -22,6 +22,8 @@ module "boo" { ## Usage +Pass a list of session definitions and the module creates one `coder_app` per session. Each session requires `session_name` and `command`; `display_name` and `slug` are optional. Clicking an app creates the session and attaches to it; clicking again reattaches to the running session. + ### Multiple sessions Create separate persistent sessions for a dev server and an interactive shell, each with its own coder_app. @@ -31,41 +33,54 @@ module "boo" { source = "registry.coder.com/coder/boo/coder" version = "1.0.0" agent_id = coder_agent.main.id - sessions = { - server = "npm run dev" - shell = "bash" - } + sessions = [ + { + session_name = "server" + display_name = "Server" + slug = "server" + command = "npm run dev" + }, + { + session_name = "shell" + display_name = "Shell" + slug = "shell" + command = "bash" + }, + ] } ``` This creates: -- `coder_app` slugs `boo-server` and `boo-shell` -- Display names `Boo: server` and `Boo: shell` +- `coder_app` slugs `server` and `shell` +- Display names `Server` and `Shell` ### Multi-line commands -Session commands can be full shell scripts. The script is written to `~/.coder-modules/coder/boo//scripts/start.sh` and executed inside the boo session. +Session commands can be full shell scripts. The script is written to `~/.coder-modules/coder/boo//scripts/start.sh` and executed inside the boo session. ```tf module "boo" { source = "registry.coder.com/coder/boo/coder" version = "1.0.0" agent_id = coder_agent.main.id - sessions = { - watcher = <<-EOT - #!/bin/bash - while true; do - echo "$(date): watching..." - sleep 10 - done - EOT - } + sessions = [ + { + session_name = "watcher" + display_name = "Watcher" + slug = "watcher" + command = <<-EOT + #!/bin/bash + while true; do + echo "$(date): watching..." + sleep 10 + done + EOT + }, + ] } ``` -Apps are named `Boo: watcher` with slug `boo-watcher`. - ### Use pre/post install hooks ```tf @@ -75,7 +90,14 @@ module "boo" { agent_id = coder_agent.main.id pre_install_script = "echo 'Preparing environment...'" post_install_script = "echo 'boo ready'" - sessions = { shell = "bash" } + sessions = [ + { + session_name = "shell" + display_name = "Shell" + slug = "shell" + command = "bash" + }, + ] } ``` @@ -88,7 +110,14 @@ module "boo" { source = "registry.coder.com/coder/boo/coder" version = "1.0.0" agent_id = coder_agent.main.id - sessions = { shell = "bash" } + sessions = [ + { + session_name = "shell" + display_name = "Shell" + slug = "shell" + command = "bash" + }, + ] } resource "coder_script" "after_boo" { @@ -105,21 +134,9 @@ resource "coder_script" "after_boo" { } ``` -## Naming - -App slugs and display names are derived from the `slug`, `display_name`, and session name variable. -For example: - -| `slug` | `display_name` | session name | app slug | display name | -| -------- | -------------- | --------------- | ------------------- | -------------------- | -| `"boo"` | `"Boo"` | `"Claude Code"` | `"boo-claude-code"` | `"Boo: Claude Code"` | -| `"term"` | `"Terminal"` | `"shell"` | `"term-shell"` | `"Terminal: shell"` | - -Session names are normalized for app slugs: lowercased, runs of non-alphanumeric characters replaced with a single hyphen, leading/trailing hyphens trimmed. Display names always use the raw session key. - ## Troubleshooting -The install log is written under `~/.coder-modules/coder/boo/logs/`. Session scripts are written to `~/.coder-modules/coder/boo//scripts/start.sh`. +The install log is written under `~/.coder-modules/coder/boo/logs/`. Session scripts are written to `~/.coder-modules/coder/boo//scripts/start.sh`. ``` ~/.coder-modules/coder/boo/ diff --git a/registry/coder/modules/boo/main.test.ts b/registry/coder/modules/boo/main.test.ts index cfaee7604..732531d82 100644 --- a/registry/coder/modules/boo/main.test.ts +++ b/registry/coder/modules/boo/main.test.ts @@ -103,10 +103,13 @@ afterEach(async () => { } }); +const defaultSession = + '[{"session_name":"main","display_name":"Main","slug":"boo-main","command":"bash"}]'; + const setup = async (moduleVariables?: Record) => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - sessions: '{"main":"bash"}', + sessions: defaultSession, install_boo: "false", ...moduleVariables, }); @@ -157,7 +160,7 @@ describe("boo", async () => { test("defaults", async () => { const { state } = await setup(); const app = findAppBySlug(state, "boo-main"); - expect(app.display_name).toBe("Boo: main"); + expect(app.display_name).toBe("Main"); expect(app.icon).toBe("/icon/coder.svg"); expect(app.order).toBeNull(); expect(app.group).toBeNull(); @@ -166,42 +169,54 @@ describe("boo", async () => { test("multiple-sessions-create-multiple-apps", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - sessions: '{"alpha":"bash","beta":"vim"}', + sessions: + '[{"session_name":"alpha","display_name":"Alpha","slug":"alpha","command":"bash"},{"session_name":"beta","display_name":"Beta","slug":"beta","command":"vim"}]', }); expect(countApps(state)).toBe(2); - const alpha = findAppBySlug(state, "boo-alpha"); - const beta = findAppBySlug(state, "boo-beta"); - expect(alpha.display_name).toBe("Boo: alpha"); - expect(beta.display_name).toBe("Boo: beta"); + const alpha = findAppBySlug(state, "alpha"); + const beta = findAppBySlug(state, "beta"); + expect(alpha.display_name).toBe("Alpha"); + expect(beta.display_name).toBe("Beta"); }); - test("underscore-in-session-name-becomes-hyphen-in-slug", async () => { + test("per-session-slug-and-display-name", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - sessions: '{"my_session":"bash"}', + sessions: + '[{"session_name":"main","display_name":"Terminal: main","slug":"term-main","command":"bash"}]', }); - const app = findAppBySlug(state, "boo-my-session"); - expect(app.slug).toBe("boo-my-session"); - expect(app.display_name).toBe("Boo: my_session"); + const app = findAppBySlug(state, "term-main"); + expect(app.slug).toBe("term-main"); + expect(app.display_name).toBe("Terminal: main"); }); - test("uppercase-and-spaces-in-session-name-normalized-in-slug", async () => { + test("session-name-used-in-boo-command", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - sessions: '{"Claude Code":"claude","Codex":"codex"}', + sessions: + '[{"session_name":"my-session","display_name":"My Session","slug":"my-boo","command":"bash"}]', }); - const claude = findAppBySlug(state, "boo-claude-code"); - expect(claude.slug).toBe("boo-claude-code"); - expect(claude.display_name).toBe("Boo: Claude Code"); - const codex = findAppBySlug(state, "boo-codex"); - expect(codex.slug).toBe("boo-codex"); - expect(codex.display_name).toBe("Boo: Codex"); + const app = findAppBySlug(state, "my-boo"); + expect(app.command as string).toContain("'my-session'"); + expect(app.command as string).not.toContain("'my-boo'"); }); - test("custom-slug-and-display-name", async () => { - const { state } = await setup({ slug: "term", display_name: "Terminal" }); - const app = findAppBySlug(state, "term-main"); - expect(app.display_name).toBe("Terminal: main"); + test("derived-slug-from-session-name", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + sessions: '[{"session_name":"my.dev_server","command":"bash"}]', + }); + const app = findAppBySlug(state, "my-dev-server"); + expect(app.slug).toBe("my-dev-server"); + }); + + test("derived-display-name-from-session-name", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + sessions: '[{"session_name":"my-session","command":"bash"}]', + }); + const app = findAppBySlug(state, "my-session"); + expect(app.display_name).toBe("my-session"); }); test("order-and-group", async () => { @@ -236,7 +251,6 @@ describe("boo", async () => { "-c", `export PATH="$HOME/.local/bin:$PATH" && /tmp/boo-install.sh 2>&1 || true`, ]); - // Write and run the install script directly to capture output await writeExecutable({ containerId: id, filePath: "/tmp/boo-install.sh", @@ -263,7 +277,7 @@ describe("boo", async () => { test("custom-install-script-url", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - sessions: '{"main":"bash"}', + sessions: defaultSession, install_boo: "true", install_script_url: "https://mirror.example.com/boo/install.sh", }); diff --git a/registry/coder/modules/boo/main.tf b/registry/coder/modules/boo/main.tf index ca06eb9cb..44615dc9a 100644 --- a/registry/coder/modules/boo/main.tf +++ b/registry/coder/modules/boo/main.tf @@ -15,9 +15,14 @@ variable "agent_id" { } variable "sessions" { - type = map(string) - description = "Map of session names to commands. A boo session and coder_app are created for each entry." - default = {} + type = list(object({ + session_name = string + display_name = optional(string) + slug = optional(string) + command = string + })) + description = "List of boo sessions to create. Each entry requires session_name and command. display_name defaults to session_name; slug is derived from session_name (lowercased, '.' and '_' replaced with '-') when omitted." + default = [] } variable "install_boo" { @@ -32,18 +37,6 @@ variable "boo_version" { default = "latest" } -variable "display_name" { - type = string - description = "The display name prefix for boo apps. Each app is shown as ': '." - default = "Boo" -} - -variable "slug" { - type = string - description = "The slug prefix for boo apps. Each app slug is '-'." - default = "boo" -} - variable "icon" { type = string description = "The icon to use for boo apps and scripts." @@ -89,12 +82,16 @@ locals { ARG_INSTALL_SCRIPT_URL = var.install_script_url }) - session_slugs = { - for k, v in var.sessions : k => replace( - replace(lower(k), "/[^a-z0-9]+/", "-"), - "/^-+|-+$/", "" - ) - } + sessions_resolved = [ + for s in var.sessions : { + session_name = s.session_name + display_name = s.display_name != null ? s.display_name : s.session_name + slug = s.slug != null ? s.slug : replace(lower(s.session_name), "/[._]/", "-") + command = s.command + } + ] + + sessions_map = { for s in local.sessions_resolved : s.slug => s } } module "coder_utils" { @@ -111,27 +108,27 @@ module "coder_utils" { } resource "coder_app" "boo" { - for_each = var.sessions + for_each = local.sessions_map agent_id = var.agent_id - slug = "${var.slug}-${local.session_slugs[each.key]}" - display_name = "${var.display_name}: ${each.key}" + slug = each.value.slug + display_name = each.value.display_name icon = var.icon command = <<-EOT #!/bin/bash export PATH="$HOME/.local/bin:$PATH" - if boo peek '${each.key}' >/dev/null 2>&1; then - boo attach '${each.key}' + if boo peek '${each.value.session_name}' >/dev/null 2>&1; then + boo attach '${each.value.session_name}' else - SESSION_DIR="${local.module_dir}/${each.key}" + SESSION_DIR="${local.module_dir}/${each.value.session_name}" mkdir -p "$SESSION_DIR/scripts" SCRIPT="$SESSION_DIR/scripts/start.sh" - printf '%s' '${base64encode(each.value)}' | base64 -d > "$SCRIPT" + printf '%s' '${base64encode(each.value.command)}' | base64 -d > "$SCRIPT" chmod +x "$SCRIPT" - boo new '${each.key}' -d - boo wait '${each.key}' --idle - boo send '${each.key}' --text "$SCRIPT" --enter - boo attach '${each.key}' + boo new '${each.value.session_name}' -d + boo wait '${each.value.session_name}' --idle + boo send '${each.value.session_name}' --text "$SCRIPT" --enter + boo attach '${each.value.session_name}' fi EOT order = var.order diff --git a/registry/coder/modules/boo/main.tftest.hcl b/registry/coder/modules/boo/main.tftest.hcl index d690cb451..677fdd4e8 100644 --- a/registry/coder/modules/boo/main.tftest.hcl +++ b/registry/coder/modules/boo/main.tftest.hcl @@ -7,7 +7,7 @@ run "test_defaults" { assert { condition = length(var.sessions) == 0 - error_message = "sessions should default to {}" + error_message = "sessions should default to []" } assert { @@ -20,16 +20,6 @@ run "test_defaults" { error_message = "boo_version should default to 'latest'" } - assert { - condition = var.display_name == "Boo" - error_message = "display_name should default to 'Boo'" - } - - assert { - condition = var.slug == "boo" - error_message = "slug should default to 'boo'" - } - assert { condition = var.icon == "/icon/coder.svg" error_message = "icon should default to '/icon/coder.svg'" @@ -41,13 +31,13 @@ run "test_defaults" { } assert { - condition = var.install_script_url == "https://raw.githubusercontent.com/coder/boo/main/install.sh" - error_message = "install_script_url should default to the upstream GitHub URL" + condition = var.group == null + error_message = "group should default to null" } assert { - condition = var.group == null - error_message = "group should default to null" + condition = var.install_script_url == "https://raw.githubusercontent.com/coder/boo/main/install.sh" + error_message = "install_script_url should default to the upstream GitHub URL" } } @@ -56,12 +46,24 @@ run "test_single_session" { variables { agent_id = "test-agent-single" - sessions = { dev = "make dev" } + sessions = [ + { + session_name = "dev" + display_name = "Dev Server" + slug = "dev" + command = "make dev" + } + ] } assert { - condition = var.sessions["dev"] == "make dev" - error_message = "sessions map should contain the correct command for key 'dev'" + condition = var.sessions[0].session_name == "dev" + error_message = "session_name should be 'dev'" + } + + assert { + condition = var.sessions[0].command == "make dev" + error_message = "command should be 'make dev'" } } @@ -70,11 +72,26 @@ run "test_multiple_sessions" { variables { agent_id = "test-agent-multi" - sessions = { - server = "npm run dev" - worker = "npm run worker" - shell = "bash" - } + sessions = [ + { + session_name = "server" + display_name = "Server" + slug = "server" + command = "npm run dev" + }, + { + session_name = "worker" + display_name = "Worker" + slug = "worker" + command = "npm run worker" + }, + { + session_name = "shell" + display_name = "Shell" + slug = "shell" + command = "bash" + } + ] } assert { @@ -83,13 +100,8 @@ run "test_multiple_sessions" { } assert { - condition = var.sessions["server"] == "npm run dev" - error_message = "sessions['server'] should be 'npm run dev'" - } - - assert { - condition = var.sessions["worker"] == "npm run worker" - error_message = "sessions['worker'] should be 'npm run worker'" + condition = var.sessions[0].command == "npm run dev" + error_message = "first session command should be 'npm run dev'" } } @@ -97,8 +109,15 @@ run "test_skip_install" { command = plan variables { - agent_id = "test-agent-skip" - sessions = { main = "bash" } + agent_id = "test-agent-skip" + sessions = [ + { + session_name = "main" + display_name = "Main" + slug = "main" + command = "bash" + } + ] install_boo = false } @@ -112,8 +131,15 @@ run "test_pinned_version" { command = plan variables { - agent_id = "test-agent-pinned" - sessions = { main = "bash" } + agent_id = "test-agent-pinned" + sessions = [ + { + session_name = "main" + display_name = "Main" + slug = "main" + command = "bash" + } + ] boo_version = "v0.6.4" } @@ -123,27 +149,22 @@ run "test_pinned_version" { } } -run "test_custom_app" { +run "test_icon_order_group" { command = plan variables { - agent_id = "test-agent-app" - sessions = { main = "bash" } - slug = "my-boo" - display_name = "My Boo" - icon = "/icon/custom.svg" - order = 10 - group = "terminals" - } - - assert { - condition = var.slug == "my-boo" - error_message = "slug should be 'my-boo'" - } - - assert { - condition = var.display_name == "My Boo" - error_message = "display_name should be 'My Boo'" + agent_id = "test-agent-app" + sessions = [ + { + session_name = "main" + display_name = "Main" + slug = "boo-main" + command = "bash" + } + ] + icon = "/icon/custom.svg" + order = 10 + group = "terminals" } assert { @@ -166,8 +187,15 @@ run "test_hooks" { command = plan variables { - agent_id = "test-agent-hooks" - sessions = { main = "bash" } + agent_id = "test-agent-hooks" + sessions = [ + { + session_name = "main" + display_name = "Main" + slug = "main" + command = "bash" + } + ] pre_install_script = "echo 'pre-install'" post_install_script = "echo 'post-install'" } @@ -188,7 +216,14 @@ run "test_scripts_output_single_session" { variables { agent_id = "test-agent-output-single" - sessions = { main = "bash" } + sessions = [ + { + session_name = "main" + display_name = "Main" + slug = "main" + command = "bash" + } + ] } assert { @@ -202,10 +237,20 @@ run "test_scripts_output_multi_session" { variables { agent_id = "test-agent-output-multi" - sessions = { - alpha = "bash" - beta = "bash" - } + sessions = [ + { + session_name = "alpha" + display_name = "Alpha" + slug = "alpha" + command = "bash" + }, + { + session_name = "beta" + display_name = "Beta" + slug = "beta" + command = "bash" + } + ] } assert { @@ -219,9 +264,16 @@ run "test_scripts_output_with_hooks" { variables { agent_id = "test-agent-output-hooks" - sessions = { main = "bash" } pre_install_script = "echo pre" post_install_script = "echo post" + sessions = [ + { + session_name = "main" + display_name = "Main" + slug = "main" + command = "bash" + } + ] } assert { @@ -235,8 +287,15 @@ run "test_custom_install_script_url" { variables { agent_id = "test-agent-url" - sessions = { main = "bash" } install_script_url = "https://mirror.example.com/boo/install.sh" + sessions = [ + { + session_name = "main" + display_name = "Main" + slug = "main" + command = "bash" + } + ] } assert { @@ -245,31 +304,62 @@ run "test_custom_install_script_url" { } } -run "test_slug_normalization" { +run "test_session_name_in_command" { command = plan variables { - agent_id = "test-agent-slug-norm" - sessions = { - "Claude Code" = "claude" - "Codex" = "codex" - my_session = "bash" - } + agent_id = "test-agent-cmd" + sessions = [ + { + session_name = "my-session" + display_name = "My Session" + slug = "my-boo" + command = "bash" + } + ] } assert { - condition = local.session_slugs["Claude Code"] == "claude-code" - error_message = "'Claude Code' should normalize to 'claude-code'" + condition = can(regex("'my-session'", coder_app.boo["my-boo"].command)) + error_message = "boo command should use session_name 'my-session', not slug 'my-boo'" + } +} + +run "test_derived_slug" { + command = plan + + variables { + agent_id = "test-agent-derived-slug" + sessions = [ + { + session_name = "my.dev_server" + command = "bash" + } + ] } assert { - condition = local.session_slugs["Codex"] == "codex" - error_message = "'Codex' should normalize to 'codex'" + condition = coder_app.boo["my-dev-server"].slug == "my-dev-server" + error_message = "slug should be derived as 'my-dev-server' from session_name 'my.dev_server'" + } +} + +run "test_derived_display_name" { + command = plan + + variables { + agent_id = "test-agent-derived-dn" + sessions = [ + { + session_name = "my-session" + command = "bash" + } + ] } assert { - condition = local.session_slugs["my_session"] == "my-session" - error_message = "'my_session' should normalize to 'my-session'" + condition = coder_app.boo["my-session"].display_name == "my-session" + error_message = "display_name should default to session_name 'my-session' when omitted" } } @@ -282,7 +372,7 @@ run "test_install_only" { assert { condition = length(var.sessions) == 0 - error_message = "sessions should default to {}" + error_message = "sessions should default to []" } assert {