From 5aa1f79a86a9242089aaab6dc90b00f6f405e2ed Mon Sep 17 00:00:00 2001 From: Vitor Carvalho Date: Wed, 14 Oct 2026 13:41:18 +0100 Subject: [PATCH] tod: use the Coder user secrets ONEDEV_SERVER_URL and ONEDEV_TOKEN - /usr/local/bin/tod is now a wrapper (build/tod-env.sh): when ONEDEV_ACCESS_TOKEN is unset, it uses ONEDEV_TOKEN; the binary moves to /usr/local/lib/tod/tod. - Remove the onedev_url template variable: ONEDEV_SERVER_URL comes from the user secret (one source, no precedence question). - Smoke test checks the mapping; READMEs: set the two user secrets, or tod config set. Image tags: node/php v0.8.0, ops v0.7.0. Co-Authored-By: Claude Opus 5.5 --- README.md | 15 +++++++++------ node-js/README.md | 10 ++++++---- node-js/build/Dockerfile | 5 ++++- node-js/build/tod-env.sh | 10 ++++++++++ node-js/main.tf | 18 +++--------------- ops/README.md | 6 ++++-- ops/build/Dockerfile | 5 ++++- ops/build/tod-env.sh | 10 ++++++++++ ops/main.tf | 18 +++--------------- php-wordpress/README.md | 10 ++++++---- php-wordpress/build/Dockerfile | 5 ++++- php-wordpress/build/tod-env.sh | 10 ++++++++++ php-wordpress/main.tf | 18 +++--------------- tests/smoke.sh | 3 +++ vars.example.yaml | 1 - 15 files changed, 79 insertions(+), 65 deletions(-) create mode 100644 node-js/build/tod-env.sh create mode 100644 ops/build/tod-env.sh create mode 100644 php-wordpress/build/tod-env.sh diff --git a/README.md b/README.md index 394e52a..f2b926e 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,7 @@ resets the variable to its default. Create it once on each machine you release from (see `vars.example.yaml`): ```yaml -extra_git_hosts: # space-separated, added to known_hosts -onedev_url: https:// # tod server URL (ONEDEV_SERVER_URL) +extra_git_hosts: # space-separated, added to known_hosts ``` Every template must declare each variable in the file. @@ -88,10 +87,14 @@ can work with OneDev issues, pull requests and builds. latest binary without verification, so it is not used. - The skills are installed in `/etc/claude-code/.claude/skills/`: managed, loaded for every user, always the same version as the binary. -- Server URL: template variable `onedev_url` (`vars.yaml`), exported as - `ONEDEV_SERVER_URL` only when set. Access token: each user's own, set with - `tod config set access-token` (stored in `~/.config/tod/config`, home volume). - No token in the templates or images. +- Server URL and token come from each user's Coder user secrets + `ONEDEV_SERVER_URL` and `ONEDEV_TOKEN`. tod reads `ONEDEV_ACCESS_TOKEN`, so + `/usr/local/bin/tod` is a wrapper (`build/tod-env.sh`) that uses + `ONEDEV_TOKEN` when `ONEDEV_ACCESS_TOKEN` is not set, then starts the real + binary (`/usr/local/lib/tod/tod`). Without secrets: `tod config set` + (stored in `~/.config/tod/config`). No token in the templates or images. +- The token is an environment variable, so any process in the workspace + (Claude Code included) can read it. Use a token with the minimum scope. - Claude Code policy: read commands are allowed; commands that change OneDev ask first; `tod config set` and reading the tod config are denied. - Version hold: OneDev here is 16.4.1, and tod 4.3.3+ needs 16.5.11. Renovate diff --git a/node-js/README.md b/node-js/README.md index 84544b6..b4246ce 100644 --- a/node-js/README.md +++ b/node-js/README.md @@ -46,10 +46,12 @@ in the repo wins over the parameter. 2. **Log in to Claude Code once.** Open a terminal and run `claude`. The login is kept in `~/.claude`. The VS Code extension uses the same login. 3. **Connect `tod` to OneDev (optional).** In OneDev, create an access token - (your avatar → **Access Tokens**), then run - `tod config set access-token `. The server URL is already set. Test - it with `tod project list`. Claude Code can then work with OneDev issues, - pull requests and builds through its `tod` skills. It asks before any change. + (your avatar → **Access Tokens**). In Coder, add two user secrets (**Account → + Secrets**) as environment variables: `ONEDEV_SERVER_URL` (your OneDev URL) + and `ONEDEV_TOKEN` (the token). Restart the workspace, then test with + `tod project list`. Without secrets, use `tod config set` instead. Claude + Code can then work with OneDev issues, pull requests and builds through its + `tod` skills. It asks before any change. 4. **Run the app.** Start the dev server on port 3000 (`pnpm dev`), then open the **Preview :3000** app in the Coder dashboard. diff --git a/node-js/build/Dockerfile b/node-js/build/Dockerfile index 08e48e3..09643d1 100644 --- a/node-js/build/Dockerfile +++ b/node-js/build/Dockerfile @@ -92,7 +92,10 @@ COPY --chmod=0644 managed-settings.json /etc/claude-code/managed-settings.json # --- OneDev CLI (tod) and its Claude Code skills ---------------------------- # Skills in /etc/claude-code/.claude/skills load for every user (managed). -COPY --from=tod /out/tod /usr/local/bin/tod +# /usr/local/bin/tod is a wrapper: it maps the Coder user secret ONEDEV_TOKEN +# to ONEDEV_ACCESS_TOKEN (see tod-env.sh). +COPY --from=tod /out/tod /usr/local/lib/tod/tod +COPY --chmod=0755 tod-env.sh /usr/local/bin/tod COPY --from=tod /out/skills /etc/claude-code/.claude/skills # --- coder user ------------------------------------------- diff --git a/node-js/build/tod-env.sh b/node-js/build/tod-env.sh new file mode 100644 index 0000000..fcc90d0 --- /dev/null +++ b/node-js/build/tod-env.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# tod wrapper: tod reads ONEDEV_ACCESS_TOKEN. Coder user secrets may provide +# the token as ONEDEV_TOKEN instead; use it when ONEDEV_ACCESS_TOKEN is unset. +# ONEDEV_SERVER_URL is read by tod directly. Without both, tod uses its config +# file (tod config set). +if [ -z "${ONEDEV_ACCESS_TOKEN+set}" ] && [ -n "${ONEDEV_TOKEN:-}" ]; then + ONEDEV_ACCESS_TOKEN="$ONEDEV_TOKEN" + export ONEDEV_ACCESS_TOKEN +fi +exec /usr/local/lib/tod/tod "$@" diff --git a/node-js/main.tf b/node-js/main.tf index 90fd64d..9c97ed1 100644 --- a/node-js/main.tf +++ b/node-js/main.tf @@ -29,17 +29,6 @@ variable "extra_git_hosts" { } } -variable "onedev_url" { - description = "OneDev server URL for the tod CLI (e.g. https://onedev.example.com). Empty: each user sets it with tod config set." - type = string - default = "" - - validation { - condition = var.onedev_url == "" || can(regex("^https://[A-Za-z0-9.-]+(:[0-9]+)?/?$", var.onedev_url)) - error_message = "Use an https:// URL without a path, or leave it empty." - } -} - locals { template_slug = "node" preview_port = 3000 @@ -110,12 +99,11 @@ resource "coder_agent" "main" { arch = "amd64" os = "linux" - # ONEDEV_SERVER_URL only when set: tod treats an empty value as an override. - env = merge({ + env = { REPO_URL = data.coder_parameter.repo_url.value GIT_REF = data.coder_parameter.git_ref.value MISE_TOOLS = "${data.coder_parameter.node_version.value} ${data.coder_parameter.extra_mise_tools.value}" - }, var.onedev_url == "" ? {} : { ONEDEV_SERVER_URL = var.onedev_url }) + } # Git over SSH uses `coder gitssh` (GIT_SSH_COMMAND is set by the agent). # Add the Coder public key (`coder publickey`) once to GitHub and OneDev. @@ -325,7 +313,7 @@ resource "docker_image" "workspace" { # Built from build/ by GitHub Actions (.github/workflows/images.yml) and # pushed to GHCR. Not built on x1-hel1. Tags are immutable: a change under # build/ needs a new tag here. - name = "ghcr.io/moveyourdigital/coder-workspace-node:v0.7.0" + name = "ghcr.io/moveyourdigital/coder-workspace-node:v0.8.0" keep_locally = true } diff --git a/ops/README.md b/ops/README.md index 0b0a3db..88036f8 100644 --- a/ops/README.md +++ b/ops/README.md @@ -90,9 +90,11 @@ Use `--context swarm` per command, or `docker context use swarm` with care. hcloud context create # paste a project token wrangler login # or CLOUDFLARE_API_TOKEN in a local .env coder login https:// -tod config set access-token # OneDev: avatar → Access Tokens ``` +OneDev (`tod`): add the Coder user secrets `ONEDEV_SERVER_URL` and +`ONEDEV_TOKEN` (OneDev: avatar → Access Tokens), or run `tod config set`. + Use tokens with the minimum scope for the task. ## Credentials: where they live @@ -104,7 +106,7 @@ Use tokens with the minimum scope for the task. | Tailscale node | `~/.local/share/tailscale/` | | SSH key to nodes | `~/.ssh/ops_ed25519` | | Coder session | `~/.config/coderv2/` | -| OneDev token (`tod`) | `~/.config/tod/config` | +| OneDev token (`tod`) | Coder user secret `ONEDEV_TOKEN`, or `~/.config/tod/config` | Do **not** store these as Coder user secrets: user secrets go to every workspace you own, including the coding workspaces. diff --git a/ops/build/Dockerfile b/ops/build/Dockerfile index 0eeaa09..cbec803 100644 --- a/ops/build/Dockerfile +++ b/ops/build/Dockerfile @@ -108,7 +108,10 @@ COPY --chmod=0644 managed-settings.json /etc/claude-code/managed-settings.json # --- OneDev CLI (tod) and its Claude Code skills ---------------------------- # Skills in /etc/claude-code/.claude/skills load for every user (managed). -COPY --from=tod /out/tod /usr/local/bin/tod +# /usr/local/bin/tod is a wrapper: it maps the Coder user secret ONEDEV_TOKEN +# to ONEDEV_ACCESS_TOKEN (see tod-env.sh). +COPY --from=tod /out/tod /usr/local/lib/tod/tod +COPY --chmod=0755 tod-env.sh /usr/local/bin/tod COPY --from=tod /out/skills /etc/claude-code/.claude/skills # --- coder user ------------------------------------------- diff --git a/ops/build/tod-env.sh b/ops/build/tod-env.sh new file mode 100644 index 0000000..fcc90d0 --- /dev/null +++ b/ops/build/tod-env.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# tod wrapper: tod reads ONEDEV_ACCESS_TOKEN. Coder user secrets may provide +# the token as ONEDEV_TOKEN instead; use it when ONEDEV_ACCESS_TOKEN is unset. +# ONEDEV_SERVER_URL is read by tod directly. Without both, tod uses its config +# file (tod config set). +if [ -z "${ONEDEV_ACCESS_TOKEN+set}" ] && [ -n "${ONEDEV_TOKEN:-}" ]; then + ONEDEV_ACCESS_TOKEN="$ONEDEV_TOKEN" + export ONEDEV_ACCESS_TOKEN +fi +exec /usr/local/lib/tod/tod "$@" diff --git a/ops/main.tf b/ops/main.tf index 3e6275d..b0968c4 100644 --- a/ops/main.tf +++ b/ops/main.tf @@ -29,17 +29,6 @@ variable "extra_git_hosts" { } } -variable "onedev_url" { - description = "OneDev server URL for the tod CLI (e.g. https://onedev.example.com). Empty: each user sets it with tod config set." - type = string - default = "" - - validation { - condition = var.onedev_url == "" || can(regex("^https://[A-Za-z0-9.-]+(:[0-9]+)?/?$", var.onedev_url)) - error_message = "Use an https:// URL without a path, or leave it empty." - } -} - locals { template_slug = "ops" @@ -128,12 +117,11 @@ resource "coder_agent" "main" { arch = "amd64" os = "linux" - # ONEDEV_SERVER_URL only when set: tod treats an empty value as an override. - env = merge({ + env = { REPO_URL = data.coder_parameter.repo_url.value GIT_REF = data.coder_parameter.git_ref.value MISE_TOOLS = "${data.coder_parameter.node_version.value} ${local.ops_tools} ${data.coder_parameter.extra_mise_tools.value}" - }, var.onedev_url == "" ? {} : { ONEDEV_SERVER_URL = var.onedev_url }) + } # Git over SSH uses `coder gitssh` (GIT_SSH_COMMAND is set by the agent). # Add the Coder public key (`coder publickey`) once to GitHub and OneDev. @@ -361,7 +349,7 @@ resource "docker_image" "workspace" { # Built from build/ by GitHub Actions (.github/workflows/images.yml) and # pushed to GHCR. Not built on x1-hel1. Tags are immutable: a change under # build/ needs a new tag here. - name = "ghcr.io/moveyourdigital/coder-workspace-ops:v0.6.0" + name = "ghcr.io/moveyourdigital/coder-workspace-ops:v0.7.0" keep_locally = true } diff --git a/php-wordpress/README.md b/php-wordpress/README.md index 338eda3..5a17ab3 100644 --- a/php-wordpress/README.md +++ b/php-wordpress/README.md @@ -45,10 +45,12 @@ On each start, the workspace runs `mise install`, `npm`/`pnpm`/`yarn`/`bun insta 2. **Log in to Claude Code once.** Open a terminal and run `claude`. The login is kept in `~/.claude`. The VS Code extension uses the same login. 3. **Connect `tod` to OneDev (optional).** In OneDev, create an access token - (your avatar → **Access Tokens**), then run - `tod config set access-token `. The server URL is already set. Test - it with `tod project list`. Claude Code can then work with OneDev issues, - pull requests and builds through its `tod` skills. It asks before any change. + (your avatar → **Access Tokens**). In Coder, add two user secrets (**Account → + Secrets**) as environment variables: `ONEDEV_SERVER_URL` (your OneDev URL) + and `ONEDEV_TOKEN` (the token). Restart the workspace, then test with + `tod project list`. Without secrets, use `tod config set` instead. Claude + Code can then work with OneDev issues, pull requests and builds through its + `tod` skills. It asks before any change. 4. **Start WordPress locally.** Use a `compose.yaml` in your project, then open the **Preview :8080** app in the Coder dashboard. diff --git a/php-wordpress/build/Dockerfile b/php-wordpress/build/Dockerfile index f4f29df..869d425 100644 --- a/php-wordpress/build/Dockerfile +++ b/php-wordpress/build/Dockerfile @@ -112,7 +112,10 @@ COPY --chmod=0644 managed-settings.json /etc/claude-code/managed-settings.json # --- OneDev CLI (tod) and its Claude Code skills ---------------------------- # Skills in /etc/claude-code/.claude/skills load for every user (managed). -COPY --from=tod /out/tod /usr/local/bin/tod +# /usr/local/bin/tod is a wrapper: it maps the Coder user secret ONEDEV_TOKEN +# to ONEDEV_ACCESS_TOKEN (see tod-env.sh). +COPY --from=tod /out/tod /usr/local/lib/tod/tod +COPY --chmod=0755 tod-env.sh /usr/local/bin/tod COPY --from=tod /out/skills /etc/claude-code/.claude/skills # --- coder user ------------------------------------------- diff --git a/php-wordpress/build/tod-env.sh b/php-wordpress/build/tod-env.sh new file mode 100644 index 0000000..fcc90d0 --- /dev/null +++ b/php-wordpress/build/tod-env.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# tod wrapper: tod reads ONEDEV_ACCESS_TOKEN. Coder user secrets may provide +# the token as ONEDEV_TOKEN instead; use it when ONEDEV_ACCESS_TOKEN is unset. +# ONEDEV_SERVER_URL is read by tod directly. Without both, tod uses its config +# file (tod config set). +if [ -z "${ONEDEV_ACCESS_TOKEN+set}" ] && [ -n "${ONEDEV_TOKEN:-}" ]; then + ONEDEV_ACCESS_TOKEN="$ONEDEV_TOKEN" + export ONEDEV_ACCESS_TOKEN +fi +exec /usr/local/lib/tod/tod "$@" diff --git a/php-wordpress/main.tf b/php-wordpress/main.tf index 6ce9407..95a0d14 100644 --- a/php-wordpress/main.tf +++ b/php-wordpress/main.tf @@ -29,17 +29,6 @@ variable "extra_git_hosts" { } } -variable "onedev_url" { - description = "OneDev server URL for the tod CLI (e.g. https://onedev.example.com). Empty: each user sets it with tod config set." - type = string - default = "" - - validation { - condition = var.onedev_url == "" || can(regex("^https://[A-Za-z0-9.-]+(:[0-9]+)?/?$", var.onedev_url)) - error_message = "Use an https:// URL without a path, or leave it empty." - } -} - locals { template_slug = "php-wordpress" preview_port = 8080 @@ -110,12 +99,11 @@ resource "coder_agent" "main" { arch = "amd64" os = "linux" - # ONEDEV_SERVER_URL only when set: tod treats an empty value as an override. - env = merge({ + env = { REPO_URL = data.coder_parameter.repo_url.value GIT_REF = data.coder_parameter.git_ref.value MISE_TOOLS = "${data.coder_parameter.node_version.value} ${data.coder_parameter.extra_mise_tools.value}" - }, var.onedev_url == "" ? {} : { ONEDEV_SERVER_URL = var.onedev_url }) + } # Git over SSH uses `coder gitssh` (GIT_SSH_COMMAND is set by the agent). # Add the Coder public key (`coder publickey`) once to GitHub and OneDev. @@ -328,7 +316,7 @@ resource "docker_image" "workspace" { # Built from build/ by GitHub Actions (.github/workflows/images.yml) and # pushed to GHCR. Not built on x1-hel1. Tags are immutable: a change under # build/ needs a new tag here. - name = "ghcr.io/moveyourdigital/coder-workspace-php-wordpress:v0.7.0" + name = "ghcr.io/moveyourdigital/coder-workspace-php-wordpress:v0.8.0" keep_locally = true } diff --git a/tests/smoke.sh b/tests/smoke.sh index 5336f17..a702c56 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -22,6 +22,9 @@ common=' jq -e . /etc/claude-code/managed-settings.json >/dev/null # OneDev CLI and its managed skills (readable by coder) tod version + # The wrapper maps the Coder user secret ONEDEV_TOKEN to ONEDEV_ACCESS_TOKEN + test -z "$(env -u ONEDEV_ACCESS_TOKEN -u ONEDEV_TOKEN tod config get access-token)" + test -n "$(env -u ONEDEV_ACCESS_TOKEN ONEDEV_TOKEN=x tod config get access-token)" n=0 for f in /etc/claude-code/.claude/skills/*/SKILL.md; do test -r "$f" && n=$((n + 1)); done test "$n" -ge 8 || { echo "tod skills: $n readable, want 8+" >&2; exit 1; } diff --git a/vars.example.yaml b/vars.example.yaml index 46775fc..b8e66ea 100644 --- a/vars.example.yaml +++ b/vars.example.yaml @@ -1,4 +1,3 @@ # Copy to vars.yaml (not in Git) and set the values. # `just push` passes vars.yaml to `coder templates push --variables-file`. extra_git_hosts: "" # space-separated host names, e.g. "git.example.com" -onedev_url: "" # e.g. "https://onedev.example.com" (tod CLI server)