Skip to content
This repository was archived by the owner on Oct 14, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ resets the variable to its default.
Create it once on each machine you release from (see `vars.example.yaml`):

```yaml
extra_git_hosts: <git-host> # space-separated, added to known_hosts
extra_git_hosts: <git-host> # space-separated, added to known_hosts
onedev_url: https://<onedev-host> # default tod server URL
```

Every template must declare each variable in the file.
Expand All @@ -87,12 +88,15 @@ 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 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.
- Each user only provides a token: the Coder user secret `ONEDEV_TOKEN`.
`/usr/local/bin/tod` is a wrapper (`build/tod-env.sh`) around the real
binary (`/usr/local/lib/tod/tod`):
- token: `ONEDEV_ACCESS_TOKEN`, else `ONEDEV_TOKEN`, else the tod config file
- server URL: `ONEDEV_SERVER_URL` (a user secret, if any), else the tod
config file, else the template default `ONEDEV_DEFAULT_SERVER_URL`
(template variable `onedev_url`, from `vars.yaml`)

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
Expand Down
12 changes: 6 additions & 6 deletions node-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +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**). 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.
(your avatar → **Access Tokens**). Store it as a Coder user secret with the
environment variable `ONEDEV_TOKEN`:
`printf %s "<token>" | coder secret create onedev-token --env ONEDEV_TOKEN`.
Restart the workspace, then test with `tod project list`. The OneDev URL is
preset by the template. 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.

Expand Down
22 changes: 17 additions & 5 deletions node-js/build/tod-env.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
#!/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).
# tod wrapper for Coder workspaces. Starts the real binary with:
#
# token: ONEDEV_ACCESS_TOKEN, else the Coder user secret ONEDEV_TOKEN,
# else the tod config file (tod config set)
# server URL: ONEDEV_SERVER_URL (user secret), else the tod config file,
# else the template default ONEDEV_DEFAULT_SERVER_URL
# (template variable onedev_url)
tod=/usr/local/lib/tod/tod

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 "$@"

if [ -z "${ONEDEV_SERVER_URL+set}" ] && [ -n "${ONEDEV_DEFAULT_SERVER_URL:-}" ] \
&& [ -z "$("$tod" config get server-url 2>/dev/null)" ]; then
ONEDEV_SERVER_URL="$ONEDEV_DEFAULT_SERVER_URL"
export ONEDEV_SERVER_URL
fi

exec "$tod" "$@"
15 changes: 14 additions & 1 deletion node-js/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ variable "extra_git_hosts" {
}
}

variable "onedev_url" {
description = "Default OneDev server URL for the tod CLI (e.g. https://onedev.example.com). A user secret ONEDEV_SERVER_URL or the tod config file wins."
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
Expand Down Expand Up @@ -103,6 +114,8 @@ resource "coder_agent" "main" {
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}"
# Default for tod; the tod wrapper uses it only as the last fallback.
ONEDEV_DEFAULT_SERVER_URL = var.onedev_url
}

# Git over SSH uses `coder gitssh` (GIT_SSH_COMMAND is set by the agent).
Expand Down Expand Up @@ -313,7 +326,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.8.0"
name = "ghcr.io/moveyourdigital/coder-workspace-node:v0.9.0"
keep_locally = true
}

Expand Down
6 changes: 4 additions & 2 deletions ops/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ wrangler login # or CLOUDFLARE_API_TOKEN in a local
coder login https://<coder-url>
```

OneDev (`tod`): add the Coder user secrets `ONEDEV_SERVER_URL` and
`ONEDEV_TOKEN` (OneDev: avatar → Access Tokens), or run `tod config set`.
OneDev (`tod`): store your OneDev access token (avatar → Access Tokens) as the
Coder user secret `ONEDEV_TOKEN`
(`printf %s "<token>" | coder secret create onedev-token --env ONEDEV_TOKEN`),
then restart the workspace. The OneDev URL is preset by the template.

Use tokens with the minimum scope for the task.

Expand Down
22 changes: 17 additions & 5 deletions ops/build/tod-env.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
#!/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).
# tod wrapper for Coder workspaces. Starts the real binary with:
#
# token: ONEDEV_ACCESS_TOKEN, else the Coder user secret ONEDEV_TOKEN,
# else the tod config file (tod config set)
# server URL: ONEDEV_SERVER_URL (user secret), else the tod config file,
# else the template default ONEDEV_DEFAULT_SERVER_URL
# (template variable onedev_url)
tod=/usr/local/lib/tod/tod

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 "$@"

if [ -z "${ONEDEV_SERVER_URL+set}" ] && [ -n "${ONEDEV_DEFAULT_SERVER_URL:-}" ] \
&& [ -z "$("$tod" config get server-url 2>/dev/null)" ]; then
ONEDEV_SERVER_URL="$ONEDEV_DEFAULT_SERVER_URL"
export ONEDEV_SERVER_URL
fi

exec "$tod" "$@"
15 changes: 14 additions & 1 deletion ops/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ variable "extra_git_hosts" {
}
}

variable "onedev_url" {
description = "Default OneDev server URL for the tod CLI (e.g. https://onedev.example.com). A user secret ONEDEV_SERVER_URL or the tod config file wins."
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"

Expand Down Expand Up @@ -121,6 +132,8 @@ resource "coder_agent" "main" {
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}"
# Default for tod; the tod wrapper uses it only as the last fallback.
ONEDEV_DEFAULT_SERVER_URL = var.onedev_url
}

# Git over SSH uses `coder gitssh` (GIT_SSH_COMMAND is set by the agent).
Expand Down Expand Up @@ -349,7 +362,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.7.0"
name = "ghcr.io/moveyourdigital/coder-workspace-ops:v0.8.0"
keep_locally = true
}

Expand Down
12 changes: 6 additions & 6 deletions php-wordpress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +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**). 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.
(your avatar → **Access Tokens**). Store it as a Coder user secret with the
environment variable `ONEDEV_TOKEN`:
`printf %s "<token>" | coder secret create onedev-token --env ONEDEV_TOKEN`.
Restart the workspace, then test with `tod project list`. The OneDev URL is
preset by the template. 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.

Expand Down
22 changes: 17 additions & 5 deletions php-wordpress/build/tod-env.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
#!/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).
# tod wrapper for Coder workspaces. Starts the real binary with:
#
# token: ONEDEV_ACCESS_TOKEN, else the Coder user secret ONEDEV_TOKEN,
# else the tod config file (tod config set)
# server URL: ONEDEV_SERVER_URL (user secret), else the tod config file,
# else the template default ONEDEV_DEFAULT_SERVER_URL
# (template variable onedev_url)
tod=/usr/local/lib/tod/tod

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 "$@"

if [ -z "${ONEDEV_SERVER_URL+set}" ] && [ -n "${ONEDEV_DEFAULT_SERVER_URL:-}" ] \
&& [ -z "$("$tod" config get server-url 2>/dev/null)" ]; then
ONEDEV_SERVER_URL="$ONEDEV_DEFAULT_SERVER_URL"
export ONEDEV_SERVER_URL
fi

exec "$tod" "$@"
15 changes: 14 additions & 1 deletion php-wordpress/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ variable "extra_git_hosts" {
}
}

variable "onedev_url" {
description = "Default OneDev server URL for the tod CLI (e.g. https://onedev.example.com). A user secret ONEDEV_SERVER_URL or the tod config file wins."
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
Expand Down Expand Up @@ -103,6 +114,8 @@ resource "coder_agent" "main" {
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}"
# Default for tod; the tod wrapper uses it only as the last fallback.
ONEDEV_DEFAULT_SERVER_URL = var.onedev_url
}

# Git over SSH uses `coder gitssh` (GIT_SSH_COMMAND is set by the agent).
Expand Down Expand Up @@ -316,7 +329,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.8.0"
name = "ghcr.io/moveyourdigital/coder-workspace-php-wordpress:v0.9.0"
keep_locally = true
}

Expand Down
3 changes: 3 additions & 0 deletions tests/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ common='
# 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)"
# ...and uses the template default URL when nothing else sets one
test "$(env -u ONEDEV_SERVER_URL ONEDEV_DEFAULT_SERVER_URL=https://d.example tod config get server-url)" = https://d.example
test "$(ONEDEV_SERVER_URL=https://u.example ONEDEV_DEFAULT_SERVER_URL=https://d.example tod config get server-url)" = https://u.example
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; }
Expand Down
1 change: 1 addition & 0 deletions vars.example.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# 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" (default tod server)
Loading