# Komodo Stack environment — the COMPLETE reference of every env the booth stack reads: # required, image-selection, set-by-compose (don't override), and the optional tunables # with their code defaults. Under Komodo, plain env lives in the Stack definition # (komodo/resources.toml); the two SECRETS come from Core's secret store, PER BOOTH and # UNIQUE. This file is DOCUMENTATION — never fill in real secrets here. See # wiki/decisions/fleet-deployment-komodo.md. # # A minimal working Stack only needs: REGISTRY, TAG, the two secrets, COOKIE_SECURE=0, # VISION_ENABLED=1, WS_ALLOWED_ORIGINS. Everything under "OPTIONAL TUNABLES" has a safe # default in code — set one only to override it. # ════════════════════════════════════════════════════════════════════════════ # IMAGE SELECTION (picks which container image to pull — not server runtime env) # ════════════════════════════════════════════════════════════════════════════ 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 (a staging booth may track `dev`). TAG=dev-830993b # ════════════════════════════════════════════════════════════════════════════ # REQUIRED (no safe default — the server refuses to boot / login breaks without) # ════════════════════════════════════════════════════════════════════════════ # Login signing secret (>=32 chars, no change-me/insecure/dev-only). openssl rand -hex 32. JWT_SECRET=[[booth__jwt_secret]] # Ledger-signing HMAC key — the anti-fraud root. DISTINCT per booth; never reuse. If unset # it falls back to JWT_SECRET (warned). openssl rand -hex 32. EVENT_SIGNING_KEY=[[booth__event_signing_key]] # CRITICAL on the plain-HTTP booth LAN: cookies are Secure (HTTPS-only) by DEFAULT, so # without =0 the auth cookie never sends and operators CANNOT log in. Set 1 only behind TLS. COOKIE_SECURE=0 # ════════════════════════════════════════════════════════════════════════════ # BACKUP (encrypted on-site DB backup — see wiki/concepts/backup-recovery.md) # ════════════════════════════════════════════════════════════════════════════ # Dedicated backup-ENCRYPTION key. SEPARATE from EVENT_SIGNING_KEY (independent rotation; # backups travel to the target, the signing key must not). Per booth + unique. openssl rand # -hex 32. ESCROW it offsite alongside EVENT_SIGNING_KEY — disaster recovery needs BOTH, and # neither is ever stored inside the backup it unlocks. Backups stay OFF until this key is set # AND the admin picks a target directory in the UI. The key is the ONLY backup env var — the # target directory and retention (keep-last / keep-daily) are admin-chosen in the UI (Setup → # Backup) and stored in the DB, so changing them needs no redeploy. BACKUP_KEY=[[booth__backup_key]] # ════════════════════════════════════════════════════════════════════════════ # COMMONLY SET (have defaults, but you usually want these explicit on a booth) # ════════════════════════════════════════════════════════════════════════════ # Turn the ANPR/vision call on. Default "" (off-ish). Set 1 to enable. (Prod compose also # forces VISION_RECOGNIZER=fast_alpr on the vision container.) VISION_ENABLED=1 # Extra origins the booth WebSocket (/api/ws) accepts beyond same-origin. Comma-separated, # e.g. http://parksystems.msai.al. Blank = only the same-origin booth URL. Default "". WS_ALLOWED_ORIGINS= # ════════════════════════════════════════════════════════════════════════════ # SET BY COMPOSE — do NOT put these in the Stack (the compose files own them) # ════════════════════════════════════════════════════════════════════════════ # VISION_URL=http://127.0.0.1:8089 # prod override (host-net server → loopback vision) # DATABASE_URL=/data/parking.sqlite # the mounted volume (the signed ledger) # VISION_RECOGNIZER=fast_alpr # prod override on the vision container # ════════════════════════════════════════════════════════════════════════════ # OPTIONAL TUNABLES — all have code defaults; set only to override. (defaults shown) # ════════════════════════════════════════════════════════════════════════════ # --- networking / process --- # PORT=3000 # server listen port # HOST=0.0.0.0 # bind interface (127.0.0.1 = loopback only) # BACKEND_HOST_IP= # override the auto-picked IP devices push back to # # (multi-NIC hosts; usually auto-detected fine) # WEB_DIST_DIR= # where the built SPA lives (the image sets it) # --- logging --- # LOG_LEVEL=info # debug|info|warn|error # LOG_RETENTION_DAYS=30 # app_logs auto-purge age # LOG_RETENTION_MAX_ROWS=50000 # app_logs row cap # RECYCLE_BIN_RETENTION_DAYS=30 # soft-deleted items auto-purge age (0 = keep forever) # --- snapshots (camera evidence; stored re-encoded, then pruned under disk pressure) --- # SNAPSHOT_MAX_EDGE=1280 # downscale long edge (px) before storing # SNAPSHOT_JPEG_QUALITY=80 # stored JPEG quality (recognition uses full-res original) # Disk-pressure prune (daily safety valve; deletes oldest + VACUUM only when the disk is tight): # SNAPSHOT_DISK_HIGH_PCT=70 # prune only when the DB's filesystem is ≥ this % used # SNAPSHOT_DISK_FREE_TARGET_PCT=10 # try to free ~this % of the disk per run # SNAPSHOT_MIN_KEEP=500 # never prune below this many snapshots (floor) # SNAPSHOT_PRUNE_BATCH=200 # delete oldest in batches of this many # --- device monitor / lane --- # DEVICE_POLL_MS=8000 # device health poll interval # PRINTER_POLL_MS=5000 # printer status poll interval # LANE_BUSY_TTL_MS=30000 # how long a lane stays "busy" after a vehicle push # CAPTURE_TTL_MS=30000 # snapshot evidence cache TTL # --- ANPR / vision (server side) --- # VISION_URL is compose-set (above). These are the knobs you may tweak per booth: # VISION_TIMEOUT_MS=1500 # per /analyze call timeout # VISION_MIN_CONFIDENCE=0.5 # advisory floor (telemetry/lane); below = low_confidence # VISION_ENTRY_MIN_CONFIDENCE=0.85 # STRICT barrier-driving floor (auto entry/exit). A # # read below this is ignored (falls back to card/QR). # ANPR_DEBOUNCE_MS=12000 # same plate/camera within this = one presentation # ANPR_POLL_MS=1000 # poll-until-confident: re-pull a fresh frame every N ms # ANPR_POLL_WINDOW_MS=8000 # ...for this long AFTER THE LAST vehicle push (sliding: # # a car arriving mid-loop extends it). RAISE if a slow # # barrier means the car waits >8s before reading clean. # ANPR_POLL_MAX_MS=30000 # hard ceiling on one loop from start (so a continuously # # busy lane can't slide the window forever) # ════════════════════════════════════════════════════════════════════════════ # VISION CONTAINER env (the Python ANPR service — its OWN process, prefix VISION_) # Mostly compose-set; documented here for completeness. (defaults shown) # ════════════════════════════════════════════════════════════════════════════ # VISION_RECOGNIZER=fast_alpr # stub | fast_alpr (prod compose forces fast_alpr) # VISION_HOST=0.0.0.0 # bind (prod publishes 127.0.0.1 only — see compose) # VISION_PORT=8089 # VISION_DETECTOR_MODEL=yolo-v9-t-384-license-plate-end2end # VISION_OCR_MODEL=cct-xs-v2-global-model # VISION_MIN_CONFIDENCE=0.5 # the Python service's own floor (keep ~in sync w/ server) # ════════════════════════════════════════════════════════════════════════════ # SECRETS — referenced by name in resources.toml, stored in Core (never inline here) # ════════════════════════════════════════════════════════════════════════════ # JWT_SECRET -> [[booth__jwt_secret]] (login) # EVENT_SIGNING_KEY -> [[booth__event_signing_key]] (ledger signing — fraud root) # BACKUP_KEY -> [[booth__backup_key]] (backup encryption — escrow offsite) # periphery passkey -> [[periphery_passkey_booth_]] (agent onboarding) # registry account -> [[gitea_registry_account]] (image pull)