refactor: build-time sec-config from template + Coolify build args

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) <noreply@anthropic.com>
This commit is contained in:
Mitchell R 2026-05-24 01:50:21 +02:00
parent aab2e928c5
commit 88526095e2
No known key found for this signature in database
4 changed files with 71 additions and 51 deletions

2
.gitignore vendored
View file

@ -47,3 +47,5 @@ rauc-signing/
old-python/ old-python/
/Hik-Connect-Docs.pdf /Hik-Connect-Docs.pdf
/Hik-Connect-Docs.md /Hik-Connect-Docs.md
# sec-config.yaml is generated from template — never commit real config
/sec-config.yaml

View file

@ -1,11 +1,26 @@
# BetterFrame server — BSB container with built plugins. # BetterFrame server — BSB container with built plugins.
# #
# sec-config.yaml is NOT baked in — mount it at runtime: # sec-config.yaml is generated at build time from sec-config.template.yaml
# volumes: # via envsubst. Secrets come from Coolify build args (set in UI, not in git).
# - ./sec-config.yaml:/app/sec-config.yaml:ro
# #
# Builder stage compiles TS + native deps (argon2). # Build args (set in Coolify UI as secrets):
# Runtime stage uses the official BSB container. # 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 FROM node:24-trixie-slim AS builder
@ -28,13 +43,30 @@ RUN npm run build
# ---- Runtime ---- # ---- Runtime ----
FROM betterweb/service-base:node FROM betterweb/service-base:node
# All config build args — secrets set in Coolify UI, not in git
ARG BF_SERVER_VERSION=dev 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 USER root
# ffmpeg for camera snapshot capture # envsubst + ffmpeg
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg \ gettext-base ffmpeg \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /var/lib/betterframe && chown node:node /var/lib/betterframe 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/server/package.json ./package.json
COPY --from=builder /app/tsconfig.base.json ./tsconfig.base.json COPY --from=builder /app/tsconfig.base.json ./tsconfig.base.json
# Static web assets served by admin-http # Generate sec-config.yaml from template + build args
COPY --from=builder /app/server/lib/web-static ./lib/web-static 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 # Bake version
RUN echo "$BF_SERVER_VERSION" > /home/bsb/.bf-version RUN echo "$BF_SERVER_VERSION" > /home/bsb/.bf-version

View file

@ -4,11 +4,8 @@
# #
# Point Coolify resource at this file instead of docker-compose.yml. # Point Coolify resource at this file instead of docker-compose.yml.
# #
# Volume name overrides: # Server config is baked into the image at build time from
# BF_DATA_VOLUME_NAME default "betterframe-data" # sec-config.template.yaml + Coolify build args (secrets).
# NODERED_DATA_VOLUME_NAME default "nodered-data"
#
# Server config comes from sec-config.yaml, not env vars.
version: "3.8" version: "3.8"
@ -23,8 +20,6 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
- TZ=UTC - TZ=UTC
volumes:
- ./sec-config.yaml:/home/bsb/sec-config.yaml:ro
expose: expose:
- "18080" - "18080"
- "18081" - "18081"

View file

@ -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. # Placeholders (${VAR}) are replaced by envsubst during docker build.
# All server config lives here — no env vars in the application code. # Set values via Coolify build args or docker build --build-arg.
# #
# For native (non-Docker) installs, adjust hostnames to 127.0.0.1 and # For native (non-Docker) installs, copy to sec-config.yaml and
# set driver: sqlite if not using PostgreSQL. # replace placeholders with actual values.
default: default:
observable: observable:
@ -17,23 +17,19 @@ default:
plugin: events-default plugin: events-default
enabled: true enabled: true
services: services:
# ----- Data layer -----
service-store: service-store:
plugin: service-store plugin: service-store
enabled: true enabled: true
config: config:
driver: postgres driver: ${BF_DB_DRIVER:-postgres}
# SQLite (native installs)
sqlitePath: /var/lib/betterframe/betterframe.db sqlitePath: /var/lib/betterframe/betterframe.db
# PostgreSQL (Docker / production) pgHost: ${BF_PG_HOST:-postgres}
pgHost: postgres pgPort: ${BF_PG_PORT:-5432}
pgPort: 5432 pgDatabase: ${BF_PG_DATABASE:-betterframe}
pgDatabase: betterframe pgUser: ${BF_PG_USER:-betterframe}
pgUser: betterframe pgPassword: ${BF_PG_PASSWORD:-betterframe}
pgPassword: betterframe pgPoolMax: ${BF_PG_POOL_MAX:-10}
pgPoolMax: 10
# ----- Admin UI + API -----
service-admin-http: service-admin-http:
plugin: service-admin-http plugin: service-admin-http
enabled: true enabled: true
@ -41,7 +37,6 @@ default:
host: 0.0.0.0 host: 0.0.0.0
port: 18080 port: 18080
dataDir: /var/lib/betterframe dataDir: /var/lib/betterframe
# Auth
sessionIdleSeconds: 43200 sessionIdleSeconds: 43200
sessionMaxSeconds: 2592000 sessionMaxSeconds: 2592000
loginLockoutThreshold: 8 loginLockoutThreshold: 8
@ -51,18 +46,13 @@ default:
argon2Parallelism: 2 argon2Parallelism: 2
cookieName: betterframe_session cookieName: betterframe_session
totpIssuer: BetterFrame totpIssuer: BetterFrame
# Inter-service URLs (Docker container names) noderedUrl: ${BF_NODERED_URL:-http://nodered:1880}
noderedUrl: http://nodered:1880 selfUrl: ${BF_SELF_URL:-http://server:18080}
selfUrl: http://server:18080
# Systemd credentials directory (native installs only)
systemdCredsDir: "" systemdCredsDir: ""
# Firmware signing key (PEM). Leave empty to auto-generate on disk. firmwareSigningKey: "${BF_FIRMWARE_SIGNING_KEY:-}"
firmwareSigningKey: "" firmwareImportApiKey: "${BF_FIRMWARE_IMPORT_API_KEY:-}"
# Bearer tokens for CI import endpoints. Generate with: openssl rand -base64 32 otaImportApiKey: "${BF_OTA_IMPORT_API_KEY:-}"
firmwareImportApiKey: ""
otaImportApiKey: ""
# ----- Kiosk-facing REST API -----
service-api-http: service-api-http:
plugin: service-api-http plugin: service-api-http
enabled: true enabled: true
@ -76,14 +66,12 @@ default:
argon2Parallelism: 2 argon2Parallelism: 2
cookieName: betterframe_session cookieName: betterframe_session
totpIssuer: BetterFrame totpIssuer: BetterFrame
noderedUrl: http://nodered:1880 noderedUrl: ${BF_NODERED_URL:-http://nodered:1880}
# MQTT telemetry bridge (optional) mqttUrl: "${BF_MQTT_URL:-}"
mqttUrl: "" mqttUsername: "${BF_MQTT_USERNAME:-}"
mqttUsername: "" mqttPassword: "${BF_MQTT_PASSWORD:-}"
mqttPassword: "" mqttTopicPrefix: ${BF_MQTT_TOPIC_PREFIX:-betterframe}
mqttTopicPrefix: betterframe
# ----- Live kiosk WebSocket channel -----
service-coordinator-ws: service-coordinator-ws:
plugin: service-coordinator-ws plugin: service-coordinator-ws
enabled: true enabled: true
@ -96,4 +84,4 @@ default:
argon2Parallelism: 2 argon2Parallelism: 2
cookieName: betterframe_session cookieName: betterframe_session
totpIssuer: BetterFrame totpIssuer: BetterFrame
noderedUrl: http://nodered:1880 noderedUrl: ${BF_NODERED_URL:-http://nodered:1880}