Persist Redis (AOF+vol), read config from mounted secrets.js, add backup/restore docs (#34)

Lossless upgrades + config story for the all-in-one image.

Redis persistence (Part A):
- Replace in-memory `--save "" --appendonly no` with AOF + RDB persisted to /data
  (--appendonly yes, periodic saves, --dbfilename dump.rdb). OAuth clients,
  tokens, and other model-redis state now survive container recreation.
- Add the `sso-data` named volume -> /data in docker-compose.yml.

Config from ./config/sso-secrets.js (Part B):
- docker-entrypoint.sh: when /config/sso-secrets.js is mounted, symlink it to
  /app/conf/secrets.js and read the server-side LDAP vars (base DN, admin pass,
  org, domain, cert CN, JWT) from the file via one `node` call (base64-decoded,
  no eval/quoting hazards). No app_* env is exported in this mode, so the file
  is authoritative (@simpleworkjs/conf precedence: base < env < secrets.js <
  app_* env). Falls back to the existing LDAP_* env-var mode when the file is
  absent (standalone/bare-metal still works).
- docker-compose.yml: trim `environment:` to NODE_ENV/NODE_PORT only and add
  `./config:/config:ro`. Removing the app_* env is required — any leftover
  app_* would silently override secrets.js.
- secrets.js.example: add orchestrator-only `stack`, `bootstrap`, and
  `serviceAccountPass` keys (ignored by the app; read by the entrypoint, the
  theta-env bootstrap, and setup.sh).

Backup/restore docs:
- Full "Backups and restore" runbook in DEPLOYMENT.md (what lives where,
  manual backup, full / Redis-only / LDAP-only restore, AOF-vs-RDB note,
  upgrades). Restore uses slapadd -f (static slapd.conf), and RDB restore
  requires deleting the AOF first (AOF wins on startup).
- Pointers in docs/deployment.md and docs/ldap.md; update the Docker Setup
  section for the new ./config/ approach (env vars now advanced/optional).

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-12 12:55:22 -04:00
committed by GitHub
parent d5e951fa9a
commit 6920a9f9f0
6 changed files with 283 additions and 131 deletions
+24 -46
View File
@@ -1,18 +1,20 @@
# Docker Compose for the SSO Manager all-in-one image (app + OpenLDAP in one container).
#
# The image (Dockerfile.openldap) bundles a slapd that the app talks to over
# localhost. Configuration is supplied to the app via `app_*` environment
# variables — the highest-precedence config layer in @simpleworkjs/conf. Any
# `app_*` var not set here falls back to a sensible default baked into
# docker-entrypoint.sh (e.g. app_ldap__url -> ldap://localhost:389).
# localhost. The app reads its configuration from conf/base.js + conf/secrets.js
# (deep-merged by @simpleworkjs/conf). The operator-edited secrets live in a
# bind-mounted ./config/sso-secrets.js, which docker-entrypoint.sh symlinks into
# /app/conf/secrets.js on startup. No app_* env vars are passed here: any app_*
# env would override secrets.js (env beats the file in @simpleworkjs/conf), so
# the file must be the only source.
#
# Copy this file to .env (or export the vars) to set the secrets below, or
# set them inline on the command line:
# LDAP_ADMIN_PASS=... JWT_SECRET=... docker compose up -d
# Compose only interpolates the port defaults below — there is no .env file.
# Override a port on the command line if needed:
# PORT=3002 LDAPS_PORT=1636 docker compose up -d
#
# Requires @simpleworkjs/conf >= 1.1.0 in the image (env overrides). Refresh
# Requires @simpleworkjs/conf >= 1.1.0 in the image. Refresh
# nodejs/package-lock.json with `npm install @simpleworkjs/conf@^1.1.0` before
# building, so the image actually contains the env-override feature.
# building.
services:
sso-manager:
@@ -34,45 +36,16 @@ services:
# plain binds from the LAN (not recommended):
# - "${LDAP_PORT:-389}:389"
environment:
# ── LDAP server-side (configures the bundled slapd) ──
- LDAP_BASE_DN=${LDAP_BASE_DN:-dc=example,dc=com}
- LDAP_DOMAIN=${LDAP_DOMAIN:-example.com}
- LDAP_ADMIN_PASS=${LDAP_ADMIN_PASS:-admin} # slapd root + app bind password
- ORG_NAME=${ORG_NAME:-SSO Manager}
# CN on the LDAP TLS cert. Clients verify against this hostname. Leave
# empty to default to LDAP_DOMAIN; set to the public hostname clients
# connect over (computed in docker-entrypoint.sh, not here, to avoid
# nested-variable interpolation limitations in compose v1).
- LDAP_CERT_CN=${LDAP_CERT_CN:-}
# ── App config overrides (app_* -> @simpleworkjs/conf) ──
# Defaults come from docker-entrypoint.sh; set these to override.
# - app_oauth__issuer=https://sso.example.com
# - app_ldap__url=ldap://localhost:389 # default; points at bundled slapd
# - app_ldap__bindDN=cn=admin,${LDAP_BASE_DN}
# - app_ldap__bindPassword=${LDAP_ADMIN_PASS}
# - app_ldap__userBase=ou=people,${LDAP_BASE_DN}
# - app_ldap__groupBase=ou=groups,${LDAP_BASE_DN}
- app_oauth__jwtSecret=${JWT_SECRET} # falls back to an auto-generated secret
# OIDC issuer advertised in /.well-known/openid-configuration. This is the
# browser-facing URL the front proxy serves the SSO at — the theta42/proxy
# (an OIDC client) and other apps use it for discovery. Server-to-server
# token/userinfo calls from the proxy can go to http://sso-manager:3001
# over the docker network; only the issuer/redirect URLs must be public.
# Leave empty to default to https://sso.<LDAP_DOMAIN> (set in the entrypoint).
- app_oauth__issuer=${OAUTH_ISSUER:-}
- app_name=${ORG_NAME:-SSO Manager}
# ── Optional SMTP (outbound email) ──
- app_smtp__host=${SMTP_HOST:-localhost}
- app_smtp__port=${SMTP_PORT:-587}
- app_smtp__user=${SMTP_USER:-}
- app_smtp__pass=${SMTP_PASS:-}
- app_smtp__from=${SMTP_FROM:-SSO Manager <noreply@example.com>}
# Config (LDAP, OAuth, SMTP, ...) comes from ./config/sso-secrets.js (see
# volumes below), not from env. NODE_ENV/NODE_PORT are the only env the app
# reads that are not part of its conf tree.
- NODE_ENV=production
- NODE_PORT=3001
volumes:
# Operator-edited secrets (sso-secrets.js). The entrypoint symlinks
# /config/sso-secrets.js -> /app/conf/secrets.js so @simpleworkjs/conf reads
# it. See secrets.js.example / config.example/ for the shape.
- ./config:/config:ro
# Persist the LDAP database across container recreation.
- ldap-data:/var/lib/ldap
# Persist the auto-generated self-signed TLS cert so clients don't have to
@@ -81,6 +54,10 @@ services:
# - ./certs:/etc/openldap/certs
# (must contain ldap.crt + ldap.key; the entrypoint leaves them untouched).
- ldap-certs:/etc/openldap/certs
# Persist Redis (AOF + RDB) so OAuth clients, tokens, and other
# Redis-backed state survive container recreation. Restoring Redis also
# restores lua-resty-auto-ssl cert state if this image fronts a proxy.
- sso-data:/data
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/health"]
interval: 30s
@@ -90,4 +67,5 @@ services:
volumes:
ldap-data:
ldap-certs:
ldap-certs:
sso-data: