0c53bb6554
Provision Coder Templates / provision (push) Successful in 2m7s
- cli-setup-wizard.sh (identical across all 6 templates): add GitHub CLI (gh, installed via the official apt repo) and Gitea CLI (tea, official binary release to ~/.local/bin) as two more opt-in install-then-login prompts, alongside the existing Copilot/Antigravity/Claude Code CLI entries. Same pattern: skip if already installed, ask before installing, attempt login, never hard-fail the wizard. - main.tf (all 6 templates): remove the `module "jetbrains"` block - not used, dropped per request. - README.md: update the "Layout" blurb and CLI setup wizard tool list to match.
254 lines
13 KiB
Markdown
254 lines
13 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
|
|
profile.code-profile # VS Code .code-profile export for this env
|
|
cli-setup-wizard.sh # first-run wizard, see below
|
|
install-skills.sh # Agent Skills installer, see below
|
|
3d-printing/ (same 4 files) # 3D Printing & Engineering
|
|
cobol/ (same 4 files) # COBOL Modern Mainframe
|
|
python/ (same 4 files) # Python Engineering
|
|
ttrpg/ (same 4 files) # TTRPG & Lore Building
|
|
web/ (same 4 files, plus Dockerfile) # Web Applications
|
|
extensions/ # Agent Skills bundles, installed per env (see below)
|
|
```
|
|
|
|
### Web Applications: baked-in toolchain image
|
|
|
|
Every other template pulls `codercom/enterprise-base:ubuntu` straight from
|
|
Docker Hub and installs what little it needs (just Bun) via a `coder_script`
|
|
at workspace start. `templates/web/` doesn't do that: it needs a large,
|
|
slow-to-install native toolchain (Rust/rustup with cross targets, the Tauri 2
|
|
/ WebKit GUI dev libraries, Node + Bun + pnpm/yarn, Python with OpenCV/ONNX/
|
|
CPU-torch, Postgres/Redis/SQLite CLI clients, protoc, clang/llvm) that would
|
|
make every workspace start take many minutes if installed on the fly.
|
|
|
|
Instead `templates/web/main.tf` builds `templates/web/Dockerfile` at
|
|
apply/push time via the `docker` provider's `docker_image` resource (`build
|
|
{ context = path.module }`, same directory as `main.tf` so no `file()`
|
|
reaches outside the template per the constraint above) and runs the
|
|
container from that image instead of the enterprise-base one. The image tag
|
|
embeds `filesha1(Dockerfile)`, so editing the Dockerfile forces a rebuild on
|
|
the next apply/push while an unchanged Dockerfile reuses Docker's build
|
|
cache. Because the toolchain lives under `/home/coder` (rustup, cargo, bun),
|
|
and that path is a fresh *named* Docker volume on a workspace's first boot,
|
|
Docker's own "populate an empty volume from the image's directory contents"
|
|
behavior copies all of it into the persistent volume automatically - no
|
|
extra `coder_script` needed, matching how the `/etc/skel` copy in every
|
|
template's `startup_script` already relies on that same mechanism.
|
|
|
|
Each `templates/<env>/` is a complete, independent Coder template (agent,
|
|
docker container, code-server) with its own copy of everything `main.tf`
|
|
needs. They're deliberately not built from a shared Terraform
|
|
module or shared files elsewhere in the repo: `coder templates push -d
|
|
templates/<env>` only uploads that one directory to the Coder server, so any
|
|
`file()` reference reaching outside it (e.g. the old shared
|
|
`scripts/`/`profile-templates/` layout) fails at push/apply time with
|
|
`Invalid function argument: ... this function works only with files that
|
|
are distributed as part of the configuration source code` - discovered the
|
|
hard way once `coder-templates.yml` actually ran end-to-end. `main.tf`,
|
|
`profile.code-profile`, `cli-setup-wizard.sh`, and `install-skills.sh` are
|
|
duplicated per template rather than shared for this reason; only their
|
|
`locals` block and profile file differ meaningfully. 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
|
|
|
|
`templates/<env>/profile.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 except `web` 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). `web` instead bakes Bun
|
|
into its Dockerfile with `BUN_INSTALL`/`PATH` set as image `ENV` - see "Web
|
|
Applications: baked-in toolchain image" above. Either way, the CLI setup
|
|
wizard below uses `bun install -g <pkg>` instead of `npm install -g <pkg>`
|
|
for everything it installs.
|
|
|
|
### CLI setup wizard
|
|
|
|
`templates/<env>/cli-setup-wizard.sh` (identical across envs) 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`)
|
|
- **GitHub CLI** (`gh`, via the official apt repo, then `gh auth login`)
|
|
- **Gitea CLI** (`tea`, official binary release downloaded to `~/.local/bin`, then `tea login add`)
|
|
|
|
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 its own copy of `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/**`:
|
|
|
|
- **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 its `profile.code-profile`,
|
|
`cli-setup-wizard.sh`, or `install-skills.sh`) -> 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.
|
|
|
|
**If the Secrets tab doesn't show up** (some Gitea/Forgejo versions drop the
|
|
*Secrets* nav link from Settings -> Actions while *Runners* and *Variables*
|
|
still show, even though the page and API underneath both still work - see
|
|
[forgejo#938](https://codeberg.org/forgejo/forgejo/issues/938)), try either:
|
|
|
|
1. Go straight to the URL the nav link would normally point at:
|
|
`https://<gitea-host>/<owner>/<repo>/settings/actions/secrets`. If a
|
|
working "Add Secret" form loads there, it's just a missing nav link - add
|
|
the secrets on that page as normal.
|
|
2. If that also won't load, set the secrets via the Actions Secrets API
|
|
instead, using a Gitea personal access token (`write:repository` scope,
|
|
Settings -> Applications -> Generate New Token):
|
|
```sh
|
|
GITEA_PAT="<your Gitea PAT>"
|
|
OWNER=octoturge
|
|
REPO=Profiles-for-Coder
|
|
|
|
curl -s -X PUT "https://<gitea-host>/api/v1/repos/$OWNER/$REPO/actions/secrets/CODER_URL" \
|
|
-H "Authorization: token $GITEA_PAT" -H "Content-Type: application/json" \
|
|
-d '{"data":"https://code.octoturge.com"}'
|
|
|
|
curl -s -X PUT "https://<gitea-host>/api/v1/repos/$OWNER/$REPO/actions/secrets/CODER_SESSION_TOKEN" \
|
|
-H "Authorization: token $GITEA_PAT" -H "Content-Type: application/json" \
|
|
-d "{\"data\":\"$(coder tokens create --name gitea-ci --lifetime 168h)\"}"
|
|
```
|
|
A `201`/`204` response means the secret was saved. This is the exact same
|
|
endpoint `rotate-coder-token.yml` uses at runtime, so if it works here it
|
|
confirms the workflow itself will be able to update the secret later too.
|
|
Never paste a PAT or Coder token into a chat/ticket - run these commands
|
|
from a trusted shell only.
|
|
|
|
### 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 `ROTATION_PAT` (same
|
|
Settings -> Actions -> Secrets page as above - if that tab is missing,
|
|
see the nav-link workaround / API fallback in the "Auto-provisioning"
|
|
section above, same `curl -X PUT .../actions/secrets/<name>` pattern,
|
|
just with `ROTATION_PAT` as the secret name and the PAT itself as the
|
|
value). Not named `GITEA_ROTATION_TOKEN` - Gitea reserves the `GITEA_`
|
|
prefix for its own automatic tokens/variables and rejects secrets with
|
|
that prefix (`Error: invalid variable or secret name`).
|
|
|
|
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.
|