dorfteich/deploy/compose/.env.example
Claude Fable 5 5cef359b8f
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m45s
CD / Build and push images (push) Successful in 3m49s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 5m35s
CI / Import/export fidelity gate (push) Successful in 47s
Nextcloud backup target: admin-configured, manual + scheduled uploads, in-app restore (#103)
Off-host backups for every self-hoster, configured entirely in the admin
UI — supersedes the host-specific mirror plan behind #84.

shared:
- webdav.ts (new package entry like token-crypto): minimal WebDAV client
  with basic auth — PROPFIND (tolerant multistatus parser), MKCOL, PUT
  (streamed), GET, DELETE; Nextcloud DAV path derived from the plain
  server URL, explicit DAV bases pass through
- backup-status.ts: additive remote-upload status in status.json, the
  restore-status.json contract (running/succeeded/failed + staleness
  bound), the backup_command/backup_maintenance NOTIFY channels, and the
  one-bundle-per-set naming (dorfteich-backup-<id>.tar.gz)
- backup-set.ts moved here from apps/backup (api lists local sets)

backup sidecar:
- reads the backup.* instance settings directly from the database (admin
  changes apply next run; local retention row overrides the env) and the
  app password from the secret store
- after each successful set: bundle dump + files archive + manifest into
  ONE self-contained tar.gz, upload via WebDAV per schedule
  (off/daily/weekly; manual runs always upload), prune remote bundles —
  never the newest — and record the outcome in status.json; upload
  failures alert via a new backupUploadFailed mail (de+en)
- command listener on backup_command (run / restore) with a serial queue
  against the nightly timer
- restore orchestrator: restore-status.json → maintenance NOTIFY →
  grace → (remote: download + manifest-verify bundle) → terminate other
  DB connections → shared perform-restore path (same code as restore.sh)
  → final status + maintenance exit

api:
- MaintenanceGuard (global, registered before the setup gate): 503
  maintenance_mode while restore-status says running; health endpoints
  and the new public GET /backup/restore-status stay exempt; a stale
  running state (crashed sidecar) unblocks after 30 min
- MaintenanceStateService watches the file and restarts the api after a
  successful restore (fresh caches, migrate-on-start for older dumps);
  main.ts refuses to touch the database while a restore runs — a
  container restarting mid-restore must not race pg_restore with
  migrate deploy
- worker sweeps (conversion, mail outbox, scheduler) catch transient
  database failures instead of dying on an unhandled rejection — the
  restore's connection termination crashed the api in verification
- backup admin endpoints under /admin/system/backup: settings (live
  connection test before save, password write-only into the secret
  store), nextcloud/test, sets (local via the ro backups mount + remote
  via WebDAV), run + restore (type-to-confirm backstop, source
  validation) — commands travel as NOTIFY payloads; audit actions
  backup.settings_changed/run_triggered/restore_requested
- readyz: new warning-level backup_remote check while a target is
  configured (26 h daily / 170 h weekly bound)

collab:
- maintenance listener: on enter, persist + close every live session and
  refuse new connections until exit (failsafe timeout 30 min) — no
  in-memory document may write pre-restore content back afterwards

web:
- Admin → System backup section: status card with remote facts and a
  "Back up now" button, the Nextcloud settings form with test button,
  and the restore picker (local + remote sets, type-to-confirm)
- global maintenance screen: any 503 maintenance_mode flips the SPA to a
  status page polling the exempt endpoint, reloading when the instance
  returns

Verified end-to-end against a live stack (fresh DB, native api + sidecar,
fake WebDAV server): configure → test → manual backup → bundle upload →
readyz/sets/status surfaces → remote restore with maintenance gate,
marker rollback and api restart; suites: shared 21, backup 9, collab 11,
api 58 files green, lint + i18n:check + typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-12 10:39:18 +02:00

95 lines
4.3 KiB
Plaintext

# Dorfteich stage configuration. Copy to `.env` (mode 600, never in git)
# next to docker-compose.yml and adjust the values.
# --- required ---------------------------------------------------------------
# PostgreSQL password for the `dorfteich` database user.
POSTGRES_PASSWORD=change-me
# Secret that signs/verifies the short-lived collaboration tokens (issue #34).
# The api and collab services share this one value; use a long random string
# (e.g. `openssl rand -base64 32`). Min length 16.
COLLAB_TOKEN_SECRET=change-me-to-a-long-random-string
# --- images -----------------------------------------------------------------
# Image name prefix. Stages pull from the Gitea registry, e.g.
# gitea.101010.cloud/stwaidele/dorfteich — local builds use the default.
IMAGE_PREFIX=dorfteich
# Image tag to run: a git SHA, `test`, `int`, or a release tag like v1.2.0.
TAG=latest
# --- ports (localhost only; the host reverse proxy routes to these) ---------
# Suggested per stage on the shared host (ONE): test 8100/8101/8102,
# int 8110/8111/8112, prod 8120/8121/8122 (web/api/collab).
WEB_PORT=8100
API_PORT=8101
# collab (Hocuspocus) WebSocket server; the proxy routes /collab here.
COLLAB_PORT=8102
# --- behavior ----------------------------------------------------------------
# pino log level: fatal|error|warn|info|debug|trace
LOG_LEVEL=info
# Compose project name; set per stage (dorfteich-test, dorfteich-int, …).
COMPOSE_PROJECT_NAME=dorfteich
# --- public URL + mail --------------------------------------------------------
# Public base URL of the stage (scheme + host). E-mail links and the CSRF
# origin check are derived from it — it must match what browsers use.
APP_BASE_URL=https://test.dorfteich.cloud
# SMTP relay for outgoing mail (verification, password reset). Optional:
# leave everything unset and configure the relay in the browser during the
# first-run setup wizard instead (stored on the `secrets` volume, issue #80).
# Values set here always win over wizard-stored ones.
SMTP_HOST=mail.example.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=wiki@example.com
SMTP_PASS=change-me
SMTP_FROM=Dorfteich <wiki@example.com>
# --- optional TLS ingress (`caddy` profile, issue #88) -------------------------
# Only when you have no reverse proxy of your own: start with
# `docker compose --profile caddy up -d`. Caddy terminates TLS for DOMAIN
# via Let's Encrypt (80+443 must be reachable from the internet; keep
# APP_BASE_URL=https://<DOMAIN> in sync). The `localhost` default issues
# an internal-CA certificate instead — good for smoke tests only.
#DOMAIN=wiki.example.com
# Published ports; change only when 80/443 are taken on the host.
#CADDY_HTTP_PORT=80
#CADDY_HTTPS_PORT=443
# --- backups (ADR 0015, issue #83) --------------------------------------------
# The backup sidecar dumps the database and archives the uploads/plugins
# volumes nightly onto the `backups` volume; restore via
# deploy/backup/restore.sh <backup-id>. All values optional.
# Daily run time HH:MM in TZ (default 03:00; set TZ for stage-local time,
# e.g. TZ=Europe/Berlin — unset means UTC).
#TZ=Europe/Berlin
#BACKUP_TIME=03:00
# Local retention in days: 30 (default) for Prod, 7 for Test/Int (ADR 0015).
# A Site Admin can override this in the admin UI (issue #103) — the saved
# setting then wins over this value.
#BACKUP_RETENTION_DAYS=30
# Off-host copies to a Nextcloud (issue #103) are configured entirely in the
# admin UI (Admin -> System -> Backups) — no env values needed here.
# Failure alert: recipient (unset = no mail, failures only in the logs and
# status.json), mail language (de|en), and the label used in the subject
# (defaults to the compose project name).
#BACKUP_MAIL_TO=ops@example.com
#BACKUP_MAIL_LOCALE=en
#BACKUP_INSTANCE_LABEL=dorfteich-test
# --- first-run setup (optional pre-seeding, issue #80) ------------------------
# A fresh (empty) database makes the instance require the browser setup
# wizard. Automated deploys can skip it entirely by pre-seeding the Site
# Admin here; the wizard then completes and locks itself at first boot.
# All three SETUP_ADMIN_* values are required for pre-seeding to trigger.
#SETUP_ADMIN_USERNAME=admin
#SETUP_ADMIN_EMAIL=admin@example.com
#SETUP_ADMIN_PASSWORD=change-me-please
#SETUP_ADMIN_DISPLAY_NAME=Admin
#SETUP_INSTANCE_NAME=Dorfteich
#SETUP_DEFAULT_LOCALE=en
#SETUP_REGISTRATION_MODE=open