Files
Profiles-for-Coder/README.md
T
octoturge a5a760b24a Add daily Coder token auto-rotation workflow
This Coder deployment caps API token lifetime at 168h (7 days), so
rather than raising that cap deployment-wide, add
.gitea/workflows/rotate-coder-token.yml: runs daily, mints a new 168h
coder token, PUTs it into this repo's CODER_SESSION_TOKEN secret via
Gitea's actions/secrets API (confirmed against the live instance's
swagger.v1.json - PUT /repos/{owner}/{repo}/actions/secrets/{name} with
{"data": "..."}), then deletes the token(s) it replaced. Old token isn't
touched until the new one is confirmed live, so a failed run fails safe.

Needs a one-time GITEA_ROTATION_TOKEN secret (a Gitea PAT with
write:repository scope, no expiration) so the workflow can write to its
own repo's secrets going forward - documented in README. After that,
CODER_SESSION_TOKEN (used by coder-templates.yml) never needs manual
attention again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 12:02:22 +02:00

178 lines
8.3 KiB
Markdown

# Profiles-for-Coder
Coder templates for per-discipline dev environments (Default, 3D Printing,
COBOL, Python, TTRPG, Web). Each environment is its own Coder **template** -
not a dropdown inside one shared container, which is what this repo used to
do and which didn't actually work.
## Layout
```
templates/
default/main.tf # Standard Default Dev
3d-printing/main.tf # 3D Printing & Engineering
cobol/main.tf # COBOL Modern Mainframe
python/main.tf # Python Engineering
ttrpg/main.tf # TTRPG & Lore Building
web/main.tf # Web Applications
scripts/
cli-setup-wizard.sh # shared first-run wizard, see below
install-skills.sh # shared Agent Skills installer, see below
profile-templates/ # VS Code .code-profile exports, one per env
extensions/ # Agent Skills bundles, installed per env (see below)
```
Each `templates/<env>/main.tf` is a complete, independent Coder template
(agent, docker container, code-server, JetBrains). They're deliberately not
built from a shared Terraform module - only their `locals` block differs
(which profile file to read). The naming convention is `profiles-<dir>`
(matches what `.gitea/workflows/coder-templates.yml` does automatically -
see below). To push by hand:
```sh
coder templates push profiles-default -d templates/default
coder templates push profiles-3d-printing -d templates/3d-printing
coder templates push profiles-cobol -d templates/cobol
coder templates push profiles-python -d templates/python
coder templates push profiles-ttrpg -d templates/ttrpg
coder templates push profiles-web -d templates/web
```
### How the VS Code profile gets applied
`profile-templates/*.code-profile` is a real VS Code Profile export: a JSON
file whose `settings` and `extensions` fields are themselves JSON-encoded
strings (double/triple-nested). Each template's `main.tf` reads and decodes
its matching file **at `terraform apply`/push time** (via `file()` +
`jsondecode()`), then:
- passes the extension ID list straight into the `code-server` module's
`extensions` input, so code-server installs them on first boot - no
interactive prompt needed, Terraform handles it declaratively;
- writes the raw `settings.json` text (comments and all - VS Code tolerates
JSONC) to `~/.local/share/code-server/User/settings.json` via a
`coder_script`.
This replaces the old approach, which downloaded a zip of this repo from
Gitea *inside* the running container and tried to apply settings from
`~/.local/share/profiles-cache/<profile>.json` - a path that never matched
the actual `.code-profile` file extension, so settings never applied. That
bug (plus the single shared container) is why "one container, many envs"
never really worked.
### Bun
Every template installs [Bun](https://bun.sh) via a `coder_script`
(`curl -fsSL https://bun.sh/install | bash`) and hooks `~/.bun/bin` onto
`PATH` in `~/.bashrc` (the installer doesn't reliably do this itself in a
non-interactive/scripted shell). The CLI setup wizard below uses
`bun install -g <pkg>` instead of `npm install -g <pkg>` for everything it
installs.
### CLI setup wizard
`scripts/cli-setup-wizard.sh` is dropped onto every workspace and hooked
into `~/.bashrc`. It runs in every new interactive terminal - until the user
finishes it - and offers to install + log into:
- **GitHub Copilot CLI** (`bun install -g @github/copilot`, then `copilot login`)
- **Google Antigravity CLI** (`curl -fsSL https://antigravity.google/cli/install.sh | bash`, binary `agy`)
- **Claude Code CLI** (`bun install -g @anthropic-ai/claude-code`, then `claude`)
It does **not** ask about VS Code extensions, since those are handled by
Terraform (see above). Once the user confirms completion it writes a
sentinel file (`~/.cache/coder-cli-wizard/done`) and stops prompting. It can
always be re-run manually: `bash /opt/coder/cli-setup-wizard.sh --force`.
### `extensions/` directory -> Agent Skills
`extensions/awesome-skills-plugin` and `extensions/custom-specialty-plugin`
are **Agent Skills bundles** (`plugin.json` + `SKILL.md` files) - the old
root `main.tf` copied this folder straight into code-server's VS Code
extensions directory, which never worked since these aren't VS Code
extension packages.
Each template now runs `scripts/install-skills.sh` (via a `coder_script`,
pulling a fresh zip of this repo from Gitea rather than embedding ~2.5MB
into Terraform state) to install skills into all three AI CLIs' personal
skills directories:
| CLI | Skills directory |
| --- | --- |
| Claude Code | `~/.claude/skills/<name>/` |
| GitHub Copilot CLI | `~/.copilot/skills/<name>/` |
| Antigravity CLI | `~/.gemini/config/skills/<name>/` (per antigravity.google/docs/skills - some third-party docs disagree on this path, worth a spot-check on a live workspace) |
Every environment gets the full `extensions/awesome-skills-plugin/skills/*`
bundle. On top of that, whichever env has a matching entry in
`extensions/custom-specialty-plugin/skills/` gets it installed too, set via
the `SPECIALTY_SKILLS` env var passed to the script from each template's
`install_skills` `coder_script`:
- COBOL -> `cobol-teacher`
- 3D Printing -> `openscad-parametric`
- TTRPG -> `foundryvtt-modding` and `ttrpg-lore-weaver`
- Default / Python / Web -> none (no matching specialty skill exists yet)
### Auto-provisioning (Gitea Actions)
`.gitea/workflows/coder-templates.yml` keeps Coder in sync with this repo on
every push to `main` that touches `templates/**`, `scripts/**`, or
`profile-templates/**`:
- **Add** a new `templates/<env>/` directory -> next push creates a new
Coder template `profiles-<env>` automatically. No workflow edits needed.
- **Edit** an existing `templates/<env>/main.tf` (or a shared script/profile
it references) -> next push updates that template with a new version.
- **Remove** a `templates/<env>/` directory -> next push deletes
`profiles-<env>` from Coder. `coder templates delete` refuses if the
template still has active workspaces, so this fails loudly instead of
silently orphaning anyone's running workspace - that failure only shows up
as a `::warning::` in the job log, it doesn't fail the whole run.
It runs on the `ubuntu-latest` self-hosted runner already registered on this
Gitea instance and installs the `coder` CLI itself via `coder.com/install.sh`.
**One-time setup required** (not something this workflow can do for itself -
needs a human with Coder access). This deployment caps API token lifetime at
168h (7 days), so rather than raising that cap deployment-wide,
`.gitea/workflows/rotate-coder-token.yml` (see below) keeps a fresh token
flowing into the secret automatically:
1. Create a Coder API token - ideally under a dedicated service account
rather than a personal login, since this token can create/delete
templates:
```sh
coder login https://code.octoturge.com
coder tokens create --name gitea-ci --lifetime 168h
```
2. In Gitea, go to this repo's **Settings -> Actions -> Secrets** (or the
org-level equivalent to share across repos) and add:
- `CODER_URL` = `https://code.octoturge.com`
- `CODER_SESSION_TOKEN` = the token printed by step 1
Until those secrets exist, `coder-templates.yml` will run and fail cleanly
at the `coder templates push` step rather than doing anything destructive.
### Token rotation (Gitea Actions)
`.gitea/workflows/rotate-coder-token.yml` runs daily and keeps
`CODER_SESSION_TOKEN` alive forever without anyone needing to remember to
refresh it: it mints a new 168h Coder token, writes it into the
`CODER_SESSION_TOKEN` secret via the Gitea API, then deletes the token(s) it
just replaced. If a run ever fails, the previous token is still untouched
and still valid (nothing gets deleted until the new one is confirmed live),
so it fails safe rather than locking you out.
**One-time bootstrap** (also needs a human - this is what lets the rotation
workflow write to its own repo's secrets):
1. Create a Gitea personal access token with **write:repository** scope and
**no expiration** (Settings -> Applications -> Generate New Token). This
one doesn't rotate itself, so give it a long life up front.
2. Add it as a repo/org Actions secret named `GITEA_ROTATION_TOKEN`.
After that, `CODER_SESSION_TOKEN` never needs manual attention again - you
can also trigger a rotation on demand from Gitea's Actions tab
(`workflow_dispatch`) instead of waiting for the daily schedule.