84f00db48b
Retention (keep-last / keep-daily-days) is operational policy the on-site admin should tune, not a server env var requiring a redeploy -- same reasoning that moved the target directory to the UI. - Migration 0017: site_config.backup_keep_last + backup_keep_daily_days (nullable; null = code default 7 / 30 per field). - BackupService reads retention fresh each run; status() exposes keepLast + keepDailyDays. DEFAULT_BACKUP_RETENTION is now a pure code default (env reads gone). - PUT /api/backup/config accepts keepLast / keepDailyDays (non-negative int, or null to reset to default; 400 on negative). - UI: two retention fields on the Backup config card; one Save covers target + retention. i18n sq + en. BACKUP_KEY wired into Komodo: - komodo/resources.toml: BACKUP_KEY=[[park_buzi_backup_key]] (per-booth secret, alongside JWT / signing keys). - komodo/.env.komodo.example: documents it as the ONLY backup env var -- escrow it offsite alongside EVENT_SIGNING_KEY (recovery needs both); target + retention are admin-chosen in the UI / DB, not env. Server .env.example trimmed to just BACKUP_KEY. Also carries the small in-progress setup-intro i18n copy trim. Tests: 218 server tests green, incl. retention persist / reset-to-default / reject- negative and the updated status shape. Migration applies cleanly (needed a statement-breakpoint between the two ALTERs). Wiki backup-recovery updated. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
121 lines
11 KiB
Bash
121 lines
11 KiB
Bash
# 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_<name>_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_<name>_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_<name>_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_<name>_jwt_secret]] (login)
|
|
# EVENT_SIGNING_KEY -> [[booth_<name>_event_signing_key]] (ledger signing — fraud root)
|
|
# BACKUP_KEY -> [[booth_<name>_backup_key]] (backup encryption — escrow offsite)
|
|
# periphery passkey -> [[periphery_passkey_booth_<name>]] (agent onboarding)
|
|
# registry account -> [[gitea_registry_account]] (image pull)
|