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
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <git-host> # space-separated, added to known_hosts
onedev_url: https://<onedev-host> # tod server URL (ONEDEV_SERVER_URL)
extra_git_hosts: <git-host> # space-separated, added to known_hosts
```

Every template must declare each variable in the file.
Expand All @@ -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
Expand Down
10 changes: 6 additions & 4 deletions node-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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.

Expand Down
5 changes: 4 additions & 1 deletion node-js/build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 -------------------------------------------
Expand Down
10 changes: 10 additions & 0 deletions node-js/build/tod-env.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"
18 changes: 3 additions & 15 deletions node-js/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down
6 changes: 4 additions & 2 deletions ops/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ Use `--context swarm` per command, or `docker context use swarm` with care.
hcloud context create <project> # paste a project token
wrangler login # or CLOUDFLARE_API_TOKEN in a local .env
coder login https://<coder-url>
tod config set access-token <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
Expand All @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion ops/build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 -------------------------------------------
Expand Down
10 changes: 10 additions & 0 deletions ops/build/tod-env.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"
18 changes: 3 additions & 15 deletions ops/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down
10 changes: 6 additions & 4 deletions php-wordpress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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.

Expand Down
5 changes: 4 additions & 1 deletion php-wordpress/build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 -------------------------------------------
Expand Down
10 changes: 10 additions & 0 deletions php-wordpress/build/tod-env.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"
18 changes: 3 additions & 15 deletions php-wordpress/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down
3 changes: 3 additions & 0 deletions tests/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
1 change: 0 additions & 1 deletion vars.example.yaml
Original file line number Diff line number Diff line change
@@ -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)
Loading