Files
parking_solution/komodo/README.md
T
julian 381046190b feat(deploy): add stage tier — park-buzi as the staging booth
Model the staging-vs-production split that fleet-deployment-komodo flagged as open.
Three tiers: dev (working, no booth) -> stage (staging booth park-buzi, real-world
test) -> main (production, manual + pinned).

- build-images.yml: trigger on [dev, stage, main]. The tag computation is already
  branch-derived, so :stage / :stage-<sha> build with no other change.
- komodo/resources.toml: park-buzi now branch=stage + TAG=stage-<sha> (pinned;
  no webhook even on staging). BACKUP_KEY already wired as a per-booth secret.
- komodo/README.md: a Promotion (dev->stage->main) section; per-booth secret list
  now includes backup_key; hard-rule #1 generalised to pinned <branch>-<sha>.
- wiki: fleet-deployment-komodo open-item resolved + a Promotion-tiers table;
  deploy-trigger choice generalised; container-deployment tag list gains :stage.

Promotion is a merge: when dev is ready, merge dev->stage, CI builds the image,
bump TAG=stage-<sha> in resources.toml, deploy from Core. stage is branched from
dev HEAD so the first real-world test carries the full current app. Per-booth
secrets must pre-exist in Core; migrations run at boot so a promotion auto-migrates
the staging ledger (where a bad migration is caught before production).

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-29 13:11:35 +02:00

78 lines
4.7 KiB
Markdown

# `komodo/` — fleet deployment as code
Infra-as-code for the **Komodo Core** control plane that deploys the parking appliance to the
booth fleet over the **NetBird** mesh. See `wiki/decisions/fleet-deployment-komodo.md` for the
rationale, threat-model analysis, and the three settled choices (many/growing fleet · deploys
are **manual + pinned** · secrets are **Komodo-managed, per-booth**).
This directory does **not** change how images are built or how the app runs — it's only the
control plane. The booth still runs the same `docker-compose.yml` + `docker-compose.prod.yml`
([[container-deployment]]); Komodo just drives them remotely instead of someone SSH-ing in to
run `booth.sh`.
## Files
- **`resources.toml`** — the Komodo resource definitions (Servers, Stacks, optional Builders/
Procedures), synced into Core via a **ResourceSync**. This is the reviewable, version-
controlled source of truth for *which booth runs what*.
- **`.env.komodo.example`** — the variables a Stack expects, documenting what comes from Core's
**secret store** (per-booth `JWT_SECRET` / `EVENT_SIGNING_KEY` / `BACKUP_KEY`) vs. plain Stack env.
## Promotion: dev → stage → main
Three tiers (see `wiki/decisions/fleet-deployment-komodo.md`):
- **`dev`** — the working branch. CI builds `:dev` / `:dev-<sha>`. No booth deploys off it.
- **`stage`** — what the **staging booth (`park-buzi`)** runs, to test the app in real-world
conditions. When `dev` is confident-ready, **merge `dev → stage`**; CI builds `:stage` /
`:stage-<sha>`; then **bump `TAG=stage-<sha>` in `resources.toml`** to that build and deploy
from Core (manual, pinned — no webhook even on staging).
- **`main`** — vetted **production** booths: `:main-<sha>`, manual + pinned. `main` only gets what
survived staging.
> The `TAG=stage-<sha>` in `resources.toml` is a **pinned pointer**: the moving `:stage` tag exists
> but we deploy the immutable sha so a booth runs a known image. Re-pin on each promotion.
## How Core consumes this (one-time)
In Komodo Core, create a **ResourceSync** pointing at this repo + path (`komodo/resources.toml`),
on the branch you manage from (e.g. `main`). Core reads the file and reconciles Servers/Stacks to
match. Thereafter, a PR to this directory + a sync is how you change the fleet — no clicking.
> Komodo's TOML schema evolves across releases. Treat `resources.toml` as a **starting sketch**:
> `resources.toml` mirrors the **working `park-buzi` Stack** (built by hand in the Core UI, then
> exported to TOML — so field names match the running Komodo version, v2.2). Import it into the
> sync **Unmanaged** first and review the diff; it should be ~empty against the live Stack.
## How servers get created — NOT here
There is **no `[[server]]` block** in `resources.toml`. Servers are created by the **Periphery
agent onboarding outbound**: in Core, create a one-time **Onboarding Key** (Settings →
Onboarding), then install Periphery on the booth passing `--onboarding-key` + `--core-address`
(Core's reverse-proxy URL, reached over the NetBird mesh) + `--connect-as=<booth-name>`. The
agent self-registers, generates its own auto-rotating key pair (private key never leaves the
booth), and connects **outbound** — the booth opens **no inbound port**. The sync owns only the
**Stack**, which references the server by the name it onboarded as (`server = "park-buzi"`). See
`wiki/decisions/fleet-deployment-komodo.md`.
## Adding booth N
Copy the `[[stack]]` block, change `name`, `server` (its onboarded name), and the per-booth
secret references (`[[park_<site>_jwt_secret]]`, `[[park_<site>_event_signing_key]]`,
`[[park_<site>_backup_key]]`). Create those secrets in Core's store first. Set `branch` +
`TAG` for the tier the booth runs (staging → `stage` / `stage-<sha>`; production → `main` /
`main-<sha>`).
## Hard rules encoded here (do not relax without updating the decision page)
1. **No deploy webhook on a booth Stack.** Deploys are a human action; pin an immutable
`TAG=<branch>-<sha>` (staging → `stage-<sha>`, production → `main-<sha>`). A moving tag on a
booth is the non-determinism we rejected — and we hold that line even on the staging booth.
2. **Onboarding, outbound, mesh-only.** Servers self-register via an onboarding key; Periphery
connects outbound to Core's mesh URL and exposes no inbound port. Never a LAN/WAN address.
3. **Secrets are per-booth and unique.** `EVENT_SIGNING_KEY` signs the anti-fraud ledger — one
leak must taint one booth, never the fleet. Reference Core secrets by name; never inline a
real value in this file (it's in git).
4. **Volumes preserved.** The Stack must never run `compose down -v` — that would wipe the
`parking-data` volume (the signed ledger). Komodo's "destroy" is gated for the same reason.