feat(deploy): Komodo fleet deployment — resources.toml + decision
CI / check (push) Successful in 36s
CI / check (push) Successful in 36s
Adopt Komodo Periphery (over the NetBird mesh) as the booth fleet control plane, superseding SSH-and-booth.sh. The booth runs the SAME compose files; Komodo Core drives them remotely. booth.sh is demoted to a break-glass local fallback. - komodo/resources.toml mirrors the working park-buzi Stack (built by hand in the Core UI, then exported to TOML — field names match the running v2.2). Stack-only: servers are created by the agent onboarding OUTBOUND (one-time onboarding key → Periphery self-registers, auto-rotating keys, booth opens no inbound port), so there is no [[server]] block. Per-booth secrets via [[...]] refs to Core's store. - komodo/README.md + .env.komodo.example document the flow and the hard rules (no webhook; onboarding/outbound/mesh-only; per-booth unique secrets; never down -v the ledger volume). - wiki/decisions/fleet-deployment-komodo.md records the decision + threat-model analysis (Periphery is a root agent → mesh-bound; EVENT_SIGNING_KEY-in-Core is a fraud-root blast radius until ATECC608 signs; Core is now Tier-0; GPL-3.0 is fine as external ops tooling). container-deployment reframed (booth.sh = fallback); index + log updated. Verified end-to-end against a real booth (park-buzi): onboarded OK, Stack deployed, all containers green, admin seeded. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# Komodo Stack environment — reference of what a booth Stack needs and WHERE it comes
|
||||
# from. Under Komodo, plain env lives in the Stack definition (komodo/resources.toml);
|
||||
# the two SECRETS come from Komodo Core's secret store, PER BOOTH and UNIQUE. This file
|
||||
# is documentation only — do NOT fill in real secrets here (it would be the same leak
|
||||
# we're avoiding). See wiki/decisions/fleet-deployment-komodo.md.
|
||||
|
||||
# --- plain Stack env (lives in resources.toml; safe in git) -------------------
|
||||
REGISTRY=git.infra.msai.al/mca/parking_solution
|
||||
# IMMUTABLE per-commit tag. Manual + pinned. Bump per deploy. Never the moving `dev`
|
||||
# on a production booth.
|
||||
TAG=dev-830993b
|
||||
COOKIE_SECURE=0
|
||||
VISION_ENABLED=1
|
||||
WS_ALLOWED_ORIGINS=
|
||||
|
||||
# --- secrets (Core secret store, referenced by name in resources.toml) --------
|
||||
# Generated PER BOOTH (openssl rand -hex 32), registered in Core under booth-scoped
|
||||
# names, never reused across sites. The user generates these; they are never typed on
|
||||
# a CLI or committed.
|
||||
# JWT_SECRET -> [[booth_<name>_jwt_secret]] (login)
|
||||
# EVENT_SIGNING_KEY -> [[booth_<name>_event_signing_key]] (ledger signing — fraud root)
|
||||
# Periphery/registry auth also live in Core:
|
||||
# periphery passkey -> [[periphery_passkey_booth_<name>]]
|
||||
# registry account -> [[gitea_registry_account]]
|
||||
@@ -0,0 +1,60 @@
|
||||
# `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`) vs. plain Stack env.
|
||||
|
||||
## 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]]`). Create
|
||||
those secrets in Core's store first.
|
||||
|
||||
## 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 `TAG=dev-<sha>` before
|
||||
a production booth goes live. A moving `:dev` on a production booth is the non-determinism we
|
||||
rejected. (`TAG=dev` here is fine while staging.)
|
||||
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.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Komodo resources — parking appliance fleet (control plane as code)
|
||||
#
|
||||
# Synced into Komodo Core via a ResourceSync pointing at this file. Drives the SAME
|
||||
# compose files the booth runs locally (docker-compose.yml + docker-compose.prod.yml);
|
||||
# Komodo Periphery on each booth executes them. See:
|
||||
# wiki/decisions/fleet-deployment-komodo.md (rationale + threat model)
|
||||
# wiki/decisions/container-deployment.md (image build/tag/registry — unchanged)
|
||||
#
|
||||
# This file mirrors the WORKING park-buzi Stack (built by hand in the Core UI, then
|
||||
# exported to TOML). Field names match the running Komodo version (v2.2).
|
||||
#
|
||||
# NO [[server]] block: servers are created by the AGENT onboarding outbound (a one-time
|
||||
# onboarding key → Periphery self-registers with auto-rotating key pairs). The sync owns
|
||||
# only the Stack; it references the server by the name it onboarded as (`connect_as`).
|
||||
#
|
||||
# Secrets ([[park_buzi_jwt_secret]] etc.) are REFERENCES to Komodo Core's secret store —
|
||||
# per-booth + unique, never inlined here (this file is in git). JWT_SECRET gates login;
|
||||
# EVENT_SIGNING_KEY signs the append-only anti-fraud ledger.
|
||||
#
|
||||
# Deploys are MANUAL + PINNED in spirit: bump TAG to an immutable dev-<sha> before a
|
||||
# production booth goes live (TAG=dev here is the moving tag, fine while staging). NO
|
||||
# deploy webhook is attached to a booth Stack.
|
||||
|
||||
##############################################################################
|
||||
# Stack — the deployable unit for booth "park-buzi". One Stack per booth; add a
|
||||
# new [[stack]] block per site (unique name, its own per-booth secret refs).
|
||||
##############################################################################
|
||||
|
||||
[[stack]]
|
||||
name = "park-buzi"
|
||||
[stack.config]
|
||||
server = "park-buzi"
|
||||
git_provider = "git.infra.msai.al"
|
||||
git_account = "komodo"
|
||||
repo = "mca/parking_solution"
|
||||
branch = "dev"
|
||||
file_paths = [
|
||||
"docker-compose.yml",
|
||||
"docker-compose.prod.yml"
|
||||
]
|
||||
registry_provider = "git.infra.msai.al"
|
||||
registry_account = "komodo"
|
||||
environment = """
|
||||
REGISTRY=git.infra.msai.al/mca/parking_solution
|
||||
TAG=dev
|
||||
COOKIE_SECURE=0
|
||||
VISION_ENABLED=1
|
||||
WS_ALLOWED_ORIGINS=
|
||||
JWT_SECRET=[[park_buzi_jwt_secret]]
|
||||
EVENT_SIGNING_KEY=[[park_buzi_event_signing_key]]
|
||||
"""
|
||||
Reference in New Issue
Block a user