# ============================================================================ # tailwart — operator configuration (Tailscale × Stalwart) # ============================================================================ # Copy to .env and fill in. The mailbox stack (docker-compose.yml) and the # edge proxy (caddy/docker-compose.yml) both read their values from here. # # Design: Stalwart runs as a Tailscale sidecar with NO WAN presence. A # layer-4 (TCP) Caddy proxy on a public-IP host pipes the raw mail ports to it # over the tailnet. The proxy can live on a DIFFERENT machine than the mailbox # — that's the whole point. See CLAUDE.md. # ============================================================================ # ---------------------------------------------------------------------------- # Tailscale — same OAuth client as the rest of the tailnet # ---------------------------------------------------------------------------- # Sidecar advertises tag:stalwart. Assign that tag to the OAuth client on both # the Devices/Core and Keys/AuthKeys scopes, or node creation 403s at boot. TS_OAUTH_CLIENT_SECRET= TS_TAILNET=tailfe8c.ts.net # MagicDNS hostname for the Stalwart sidecar → reachable at # ${STALWART_MAGIC_NAME}.${TS_TAILNET} over the tailnet. STALWART_MAGIC_NAME=stalwart # ---------------------------------------------------------------------------- # Mail identity # ---------------------------------------------------------------------------- # The domain you send/receive for, and the public MX/hostname clients connect # to (its A record points at the EDGE PROXY's public IP, not the mailbox). STALWART_DOMAIN=infinidim.net STALWART_HOSTNAME=mail.infinidim.net # Initial admin password for the Stalwart web console (first boot only). # Generate: openssl rand -base64 24 STALWART_FALLBACK_ADMIN_SECRET= # ---------------------------------------------------------------------------- # Over-engineering, part 1/3 — Postgres (shared instance on the tailnet) # ---------------------------------------------------------------------------- # Stalwart's metadata + full-text store. Create the role/db once (see README). DB_MAGIC_NAME=the-record-prod STALWART_DB_NAME=stalwart STALWART_DB_USER=stalwart # Generate: openssl rand -base64 24 (avoid / and + if you ever inline it in a URL) STALWART_DB_PASSWORD= # ---------------------------------------------------------------------------- # Over-engineering, part 2/3 — Redis (shared instance on the tailnet) # ---------------------------------------------------------------------------- # Stalwart's in-memory store: distributed rate limits, caches, token revocation. REDIS_MAGIC_NAME=slo-time-prod # Use a dedicated logical DB index so we don't collide with the fediverse apps. STALWART_REDIS_DB=3 # ---------------------------------------------------------------------------- # Over-engineering, part 3/3 — Garage S3 (shared instance on the tailnet) # ---------------------------------------------------------------------------- # Stalwart's blob store: message bodies, attachments, sieve scripts. # Reuses the shared Garage access key; needs its own bucket (see README). GARAGE_MAGIC_NAME=garage GARAGE_REGION=garage GARAGE_ACCESS_KEY_ID= GARAGE_SECRET_ACCESS_KEY= STALWART_S3_BUCKET=stalwart-mail # ---------------------------------------------------------------------------- # Outbound delivery # ---------------------------------------------------------------------------- # Most VPS providers block outbound :25. If yours does, relay through a # smarthost (host:port). Leave blank to attempt direct MX delivery. # # IMPORTANT: Use an IPv4 literal or a tailnet IP — never a dual-stack hostname. # The container has no IPv6 and will NOT fall back from AAAA to A; any host # with an AAAA record will fail immediately (os error 101). Relaying over the # tailnet (100.x:587) sidesteps this entirely and also bypasses VPS SMTP blocks. STALWART_SMARTHOST= # ---------------------------------------------------------------------------- # TLS — Stalwart self-manages certs via ACME DNS-01 (works behind the L4 proxy) # ---------------------------------------------------------------------------- # DNS provider + token for the DNS-01 challenge. Leave blank to instead mount # a certbot-issued cert (see config/config.toml [certificate]). STALWART_ACME_PROVIDER= STALWART_ACME_TOKEN= # ============================================================================ # AUTHELIA — merged from /opt/authelia. Shares TS_OAUTH_CLIENT_SECRET, TS_TAILNET, # DB_MAGIC_NAME, REDIS_MAGIC_NAME from the stalwart section above. # ============================================================================ # ============================================================================ # authelia — operator configuration (SSO / 2FA / OIDC, Tailscale sidecar) # ============================================================================ # Copy to .env and fill in. Self-contained stack at /opt/authelia, outside any # upstream repo. Reuses the shared tailnet Postgres + Redis + SMTP relay. # # Authelia is a forward-auth / OIDC provider: it runs tailnet-only and the main # box Caddy fronts its portal (auth.) and routes protected vhosts # through its /api/verify endpoint. See caddy-forward-auth.snippet. # ============================================================================ # Tailscale — same OAuth client as the rest of the tailnet (tag:authelia). AUTHELIA_MAGIC_NAME=authelia # Identity / cookie scope. Portal lives at AUTHELIA_PORTAL_URL; the session # cookie is valid across *.AUTHELIA_DOMAIN. AUTHELIA_DOMAIN=infinidim.net AUTHELIA_PORTAL_URL=https://auth.infinidim.net # Postgres (shared) — storage backend (users' 2FA devices, identity, consent). AUTHELIA_DB_NAME=authelia AUTHELIA_DB_USER=authelia # Generate: openssl rand -hex 24 AUTHELIA_DB_PASSWORD= # Redis (shared) — session backend. Dedicated logical DB index. AUTHELIA_REDIS_DB=4 # SMTP relay (shared) — password-reset + 2FA notifications. SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USER= SMTP_PASSWORD= AUTHELIA_SMTP_SENDER=no-reply@infinidim.net # Authelia secrets. Generate each: openssl rand -hex 32 AUTHELIA_SESSION_SECRET= AUTHELIA_STORAGE_ENCRYPTION_KEY= AUTHELIA_JWT_SECRET= AUTHELIA_OIDC_HMAC_SECRET= # First admin (web portal login). The hash goes in config/users_database.yml: # docker run --rm authelia/authelia:latest \ # authelia crypto hash generate argon2 --password 'YOURPASS' AUTHELIA_ADMIN_USER=admin AUTHELIA_ADMIN_PASSWORD=