From 88526095e27ab058a99993b7ab7cb220ed53b5bc Mon Sep 17 00:00:00 2001 From: Mitchell R Date: Sun, 24 May 2026 01:50:21 +0200 Subject: [PATCH] refactor: build-time sec-config from template + Coolify build args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sec-config.yaml is now generated at Docker build time from sec-config.template.yaml via envsubst. Secrets come from Coolify build args (set in UI, never in git). Template uses ${VAR:-default} placeholders — safe to commit to public repo. - sec-config.yaml removed from git, added to .gitignore - sec-config.template.yaml added (public, no secrets) - Dockerfile.server: ARGs for all config, envsubst generates config at build time, result is chmod 444 (read-only) - Coolify compose: removed sec-config volume mount (baked in now) - For native installs: copy template to sec-config.yaml, fill values Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 2 + deploy/docker/Dockerfile.server | 53 +++++++++++++++---- docker-compose.coolify.yml | 9 +--- sec-config.yaml => sec-config.template.yaml | 58 ++++++++------------- 4 files changed, 71 insertions(+), 51 deletions(-) rename sec-config.yaml => sec-config.template.yaml (51%) diff --git a/.gitignore b/.gitignore index 2845990..e7324ce 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ rauc-signing/ old-python/ /Hik-Connect-Docs.pdf /Hik-Connect-Docs.md +# sec-config.yaml is generated from template — never commit real config +/sec-config.yaml diff --git a/deploy/docker/Dockerfile.server b/deploy/docker/Dockerfile.server index c6053eb..d1f48ad 100644 --- a/deploy/docker/Dockerfile.server +++ b/deploy/docker/Dockerfile.server @@ -1,11 +1,26 @@ # BetterFrame server — BSB container with built plugins. # -# sec-config.yaml is NOT baked in — mount it at runtime: -# volumes: -# - ./sec-config.yaml:/app/sec-config.yaml:ro +# sec-config.yaml is generated at build time from sec-config.template.yaml +# via envsubst. Secrets come from Coolify build args (set in UI, not in git). # -# Builder stage compiles TS + native deps (argon2). -# Runtime stage uses the official BSB container. +# Build args (set in Coolify UI as secrets): +# BF_PG_PASSWORD postgres password +# BF_FIRMWARE_SIGNING_KEY Ed25519 PEM for firmware signing +# BF_FIRMWARE_IMPORT_API_KEY CI bearer token +# BF_OTA_IMPORT_API_KEY CI bearer token +# BF_MQTT_URL mqtt://broker:1883 (optional) +# BF_MQTT_USERNAME (optional) +# BF_MQTT_PASSWORD (optional) +# +# Non-secret build args (defaults work for standard compose): +# BF_DB_DRIVER postgres|sqlite (default: postgres) +# BF_PG_HOST (default: postgres) +# BF_PG_PORT (default: 5432) +# BF_PG_DATABASE (default: betterframe) +# BF_PG_USER (default: betterframe) +# BF_NODERED_URL (default: http://nodered:1880) +# BF_SELF_URL (default: http://server:18080) +# BF_SERVER_VERSION (default: dev) FROM node:24-trixie-slim AS builder @@ -28,13 +43,30 @@ RUN npm run build # ---- Runtime ---- FROM betterweb/service-base:node +# All config build args — secrets set in Coolify UI, not in git ARG BF_SERVER_VERSION=dev +ARG BF_DB_DRIVER=postgres +ARG BF_PG_HOST=postgres +ARG BF_PG_PORT=5432 +ARG BF_PG_DATABASE=betterframe +ARG BF_PG_USER=betterframe +ARG BF_PG_PASSWORD=betterframe +ARG BF_PG_POOL_MAX=10 +ARG BF_NODERED_URL=http://nodered:1880 +ARG BF_SELF_URL=http://server:18080 +ARG BF_FIRMWARE_SIGNING_KEY= +ARG BF_FIRMWARE_IMPORT_API_KEY= +ARG BF_OTA_IMPORT_API_KEY= +ARG BF_MQTT_URL= +ARG BF_MQTT_USERNAME= +ARG BF_MQTT_PASSWORD= +ARG BF_MQTT_TOPIC_PREFIX=betterframe USER root -# ffmpeg for camera snapshot capture +# envsubst + ffmpeg RUN apt-get update && apt-get install -y --no-install-recommends \ - ffmpeg \ + gettext-base ffmpeg \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p /var/lib/betterframe && chown node:node /var/lib/betterframe @@ -48,8 +80,11 @@ COPY --from=builder /app/server/bsb-plugin.json ./bsb-plugin.json COPY --from=builder /app/server/package.json ./package.json COPY --from=builder /app/tsconfig.base.json ./tsconfig.base.json -# Static web assets served by admin-http -COPY --from=builder /app/server/lib/web-static ./lib/web-static +# Generate sec-config.yaml from template + build args +COPY sec-config.template.yaml /tmp/sec-config.template.yaml +RUN envsubst < /tmp/sec-config.template.yaml > /home/bsb/sec-config.yaml \ + && chmod 444 /home/bsb/sec-config.yaml \ + && rm /tmp/sec-config.template.yaml # Bake version RUN echo "$BF_SERVER_VERSION" > /home/bsb/.bf-version diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index 524a35a..f57cf9b 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -4,11 +4,8 @@ # # Point Coolify resource at this file instead of docker-compose.yml. # -# Volume name overrides: -# BF_DATA_VOLUME_NAME default "betterframe-data" -# NODERED_DATA_VOLUME_NAME default "nodered-data" -# -# Server config comes from sec-config.yaml, not env vars. +# Server config is baked into the image at build time from +# sec-config.template.yaml + Coolify build args (secrets). version: "3.8" @@ -23,8 +20,6 @@ services: restart: unless-stopped environment: - TZ=UTC - volumes: - - ./sec-config.yaml:/home/bsb/sec-config.yaml:ro expose: - "18080" - "18081" diff --git a/sec-config.yaml b/sec-config.template.yaml similarity index 51% rename from sec-config.yaml rename to sec-config.template.yaml index 94edec8..6248e8c 100644 --- a/sec-config.yaml +++ b/sec-config.template.yaml @@ -1,10 +1,10 @@ -# BSB runtime configuration for BetterFrame server. +# BSB runtime configuration — template for Docker builds. # -# This file is bind-mounted into the container at /home/bsb/sec-config.yaml. -# All server config lives here — no env vars in the application code. +# Placeholders (${VAR}) are replaced by envsubst during docker build. +# Set values via Coolify build args or docker build --build-arg. # -# For native (non-Docker) installs, adjust hostnames to 127.0.0.1 and -# set driver: sqlite if not using PostgreSQL. +# For native (non-Docker) installs, copy to sec-config.yaml and +# replace placeholders with actual values. default: observable: @@ -17,23 +17,19 @@ default: plugin: events-default enabled: true services: - # ----- Data layer ----- service-store: plugin: service-store enabled: true config: - driver: postgres - # SQLite (native installs) + driver: ${BF_DB_DRIVER:-postgres} sqlitePath: /var/lib/betterframe/betterframe.db - # PostgreSQL (Docker / production) - pgHost: postgres - pgPort: 5432 - pgDatabase: betterframe - pgUser: betterframe - pgPassword: betterframe - pgPoolMax: 10 + pgHost: ${BF_PG_HOST:-postgres} + pgPort: ${BF_PG_PORT:-5432} + pgDatabase: ${BF_PG_DATABASE:-betterframe} + pgUser: ${BF_PG_USER:-betterframe} + pgPassword: ${BF_PG_PASSWORD:-betterframe} + pgPoolMax: ${BF_PG_POOL_MAX:-10} - # ----- Admin UI + API ----- service-admin-http: plugin: service-admin-http enabled: true @@ -41,7 +37,6 @@ default: host: 0.0.0.0 port: 18080 dataDir: /var/lib/betterframe - # Auth sessionIdleSeconds: 43200 sessionMaxSeconds: 2592000 loginLockoutThreshold: 8 @@ -51,18 +46,13 @@ default: argon2Parallelism: 2 cookieName: betterframe_session totpIssuer: BetterFrame - # Inter-service URLs (Docker container names) - noderedUrl: http://nodered:1880 - selfUrl: http://server:18080 - # Systemd credentials directory (native installs only) + noderedUrl: ${BF_NODERED_URL:-http://nodered:1880} + selfUrl: ${BF_SELF_URL:-http://server:18080} systemdCredsDir: "" - # Firmware signing key (PEM). Leave empty to auto-generate on disk. - firmwareSigningKey: "" - # Bearer tokens for CI import endpoints. Generate with: openssl rand -base64 32 - firmwareImportApiKey: "" - otaImportApiKey: "" + firmwareSigningKey: "${BF_FIRMWARE_SIGNING_KEY:-}" + firmwareImportApiKey: "${BF_FIRMWARE_IMPORT_API_KEY:-}" + otaImportApiKey: "${BF_OTA_IMPORT_API_KEY:-}" - # ----- Kiosk-facing REST API ----- service-api-http: plugin: service-api-http enabled: true @@ -76,14 +66,12 @@ default: argon2Parallelism: 2 cookieName: betterframe_session totpIssuer: BetterFrame - noderedUrl: http://nodered:1880 - # MQTT telemetry bridge (optional) - mqttUrl: "" - mqttUsername: "" - mqttPassword: "" - mqttTopicPrefix: betterframe + noderedUrl: ${BF_NODERED_URL:-http://nodered:1880} + mqttUrl: "${BF_MQTT_URL:-}" + mqttUsername: "${BF_MQTT_USERNAME:-}" + mqttPassword: "${BF_MQTT_PASSWORD:-}" + mqttTopicPrefix: ${BF_MQTT_TOPIC_PREFIX:-betterframe} - # ----- Live kiosk WebSocket channel ----- service-coordinator-ws: plugin: service-coordinator-ws enabled: true @@ -96,4 +84,4 @@ default: argon2Parallelism: 2 cookieName: betterframe_session totpIssuer: BetterFrame - noderedUrl: http://nodered:1880 + noderedUrl: ${BF_NODERED_URL:-http://nodered:1880}