# `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-`. 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-`; then **bump `TAG=stage-` in `resources.toml`** to that build and deploy from Core (manual, pinned — no webhook even on staging). - **`main`** — vetted **production** booths: `:main-`, manual + pinned. `main` only gets what survived staging. > The `TAG=stage-` 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=`. 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__jwt_secret]]`, `[[park__event_signing_key]]`, `[[park__backup_key]]`). Create those secrets in Core's store first. Set `branch` + `TAG` for the tier the booth runs (staging → `stage` / `stage-`; production → `main` / `main-`). ## 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=-` (staging → `stage-`, production → `main-`). 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.