NextMyDay/config.example.yaml
Claus Lohmar c7229e23ff feat(setup): gate setup with the config toggle and an optional code
D-045 — the setup gate is now `config.setup`, not the peer/SSH tunnel:

- setup: true      -> /setup serves the wizard (the code form first when
                      `setup_code` is set, via the existing D-044 gate);
                      GET / points at the wizard; every other route -> 404.
- setup: false + no admin -> LOCKED: GET / and /setup render an "enable
                      setup" boilerplate; every other route -> 404; the
                      wizard is not claimable.
- completion       -> writes `setup: false` and creates the admin.

`setup_code` is now OPTIONAL: blank => Gate.Required() false => the toggle
alone admits; set => required (reusing the unlock cookie/fingerprint/
redaction/rate-limit). The peer/tunnel gate is retired: `httpx.
SetupRequestAllowed` and `httpx.IsLoopbackIP` are deleted; the XFF/XFP
handling is kept for the client IP and the Secure/__Host- cookies + HSTS
(A6/A11/D-043). The !bootstrapped() fail-safe becomes the locked predicate,
latched at startup.

New door seams: Deps.LockedMode and SetupGate.{Required,CodeForm,Landing,
Locked}; the Gate renders three first-run pages. The startup banner states
the mode and prints the code only when required.

Tests cover the three states, the boilerplate, the code optionality and the
unlock mechanics; live-smoked end to end (locked / wizard / code form /
full bootstrap -> setup: false).
2026-10-15 06:47:33 +00:00

81 lines
4.6 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-045 — the setup gate. While true the app serves the
# /setup wizard at the public domain: completing it writes
# `setup: false` and creates the admin. While FALSE with no
# admin yet, the app is LOCKED (a boilerplate at / and
# /setup; every other route 404) — deliberately not
# publicly claimable, so a fresh instance never exposes
# the bootstrap by accident.
admin_recovery: false # D-033 — set true + restart to email user 1 an enrolment link
# D-044/D-045 — the OPTIONAL first-run SETUP CODE (a second factor for /setup).
# Leave it EMPTY ("") and the `setup: true` toggle alone admits — the wizard is
# served directly. Set it and the code form comes first; the code is single-use,
# rate-limited and redacted from the logs (A8). If you expose MyDay publicly,
# SET THIS: while `setup: true` whoever reaches /setup first could otherwise
# claim the admin.
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` (so the audit log records the real client)
# 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