NextMyDay/config.example.yaml
Claus Lohmar 6d421b61fd feat(setup): gate first run with a printed setup code and a first-run page
The real topology is an LXC container (11020) behind Proxmox + Zoraxy, so
the SSH tunnel is impractical: it lands on the container gateway, not
loopback. D-025 is re-revised and D-044 adds the general gate.

- core/setup: a Gate (gate.go) holding the 128-bit setup code (generated at
  first run, or pinned by config `setup_code`) plus a signed unlock cookie
  (`myday_setup`) that carries a keyed one-way FINGERPRINT of the code, never
  the code itself. The code is single-use (retired on the first success) and
  the comparison ignores case/separators.
- The unlock action (`POST /setup?do=setup&action=unlock`) is CSRF-protected
  (A15), rate-limited (the /setup A3 class) and audited; a wrong or absent
  code gets the same generic 403 page.
- The door (server.Deps.SetupGate): in setup mode the wizard is served to
  EITHER the direct tunnel (gate a) OR an unlocked request (gate b); `GET /`
  and an un-unlocked `GET /setup` render a friendly first-run page with the
  code form — never a bare 404 — and every other route stays 404.
- cmd/myday prints an unmistakable startup banner: setup mode, the wizard
  URL, the code, where to read it (journalctl / pct exec 11020) and the
  restart that adopts the persisted secrets.
- httpx.Signer.Fingerprint backs the unlock cookie; config gains `setup_code`.

Tests cover the boilerplate at `/` and `/setup` (200, not 404), the unlock
(code → wizard; wrong/absent → generic, 429 under the limit), single-use, the
CSRF requirement, access-log redaction, and that a non-/setup route is still
404. Gate green (build · vet · test -race); live-smoked against the binary.
2026-10-15 06:33:13 +00:00

80 lines
4.5 KiB
YAML

# MyDay — example configuration.
#
# Copy to `config.yaml` (gitignored — NEVER committed, D-010) and fill in.
# Settings live HERE, not in the database (D-035).
# ── setup & recovery ─────────────────────────────────────────────────────────
setup: true # D-015 / D-026 — while true the app serves ONLY the
# setup wizard. D-025 (2026-10-15b) / D-044: the wizard
# is reachable EITHER over a direct SSH tunnel OR by
# entering the printed setup code — never
# first-visitor-wins. Until it is unlocked, `GET /` and
# `GET /setup` show a friendly first-run page (no bare
# 404) and every other route is 404.
admin_recovery: false # D-033 — set true + restart to email user 1 an enrolment link
# D-044 — the first-run SETUP CODE. Leave blank and MyDay generates a 128-bit
# code at startup and prints it in a banner (read it with `journalctl -u myday`
# in the container, or `pct exec 11020 -- journalctl -u myday` from Proxmox).
# Set it here to pin your own value instead (then protect this file). Only
# consulted in setup mode.
setup_code: ""
# ── server ───────────────────────────────────────────────────────────────────
# D-043 (deployment behind Zoraxy): TLS terminates at the PROXY, so the app
# speaks plain HTTP and binds 0.0.0.0:8080. FIREWALL :8080 TO ZORAXY ONLY —
# anything else that can reach the port bypasses the proxy and its TLS.
server:
listen: "0.0.0.0:8080" # D-043 — any bind is allowed (no setup-mode refusal)
base_url: "https://myday.sechpoint.app" # the PUBLIC origin: invite / recovery links
# Zoraxy's address — the ONLY peer whose forwarding headers are honoured (A6/D-043).
# Zoraxy MUST set `X-Forwarded-For` (the setup gate's "no XFF" test relies on it)
# and `X-Forwarded-Proto`. A WRONG VALUE HERE FAILS SILENTLY: `X-Forwarded-Proto`
# is then ignored, the session cookie loses `Secure` (and with it the `__Host-`
# prefix and HSTS), and the audit log records the proxy — not the user — as the
# client. FIREWALL :8080 TO 10.1.0.10 ONLY: any other host that can reach the
# port bypasses Zoraxy and its TLS, and is served plain HTTP directly.
trusted_proxies:
- "10.1.0.10" # Zoraxy (TLS terminates here)
# ── app ──────────────────────────────────────────────────────────────────────
app:
locale: "en-GB"
timezone: "Europe/London"
# ── enrollment (A1) ──────────────────────────────────────────────────────────
# The bounded invite window: outside it, /{GUID} enrollment is refused.
enrollment:
window: "72h"
# ── database (D-012) ─────────────────────────────────────────────────────────
database:
path: "./data/myday.db" # SQLite · WAL · foreign keys ON · one instance only
# ── secrets — generate strong random values; never commit (D-010) ────────────
# A placeholder is replaced with a strong value: at startup in setup mode
# (so the wizard's ceremony is never signed with a known key) and again by the
# wizard's `finish`. A restart adopts what `/setup` persisted.
secrets:
cookie_secret: "CHANGE_ME_32_BYTES_RANDOM"
csrf_key: "CHANGE_ME_32_BYTES_RANDOM"
totp_encryption_key: "CHANGE_ME_32_BYTES_RANDOM" # A9 — TOTP secrets are encrypted at rest
# ── mail — invites & recovery ────────────────────────────────────────────────
mail:
host: "smtp.example.com"
port: 587
username: ""
password: "CHANGE_ME"
from: "myday@example.com"
# ── modules (D-038) — required modules cannot be disabled ────────────────────
modules:
org: true
time: true
absence: true
requests: true # required
hr: true
expense: false
reports: true
integrations: false