tailwart/.env.example
Wayne Hayes 238cce506d feat: provision-stalwart.sh — configure Stalwart fully from .env
Stalwart v0.16 keeps all config in Postgres, reachable via the x: JMAP
management objects. This script writes everything the setup wizard would —
stores (Garage S3 + Redis), listeners (with per-listener PROXY trust on the
mail ports), the primary domain (+auto DKIM), admin + relay/catch-all
accounts, TLS/DNS, and optional Authelia SSO — straight into Postgres over
HTTP Basic. Idempotent (query-before-create), so re-runs are safe.

Tiers (the DNS/TLS automation boundary):
  * Tier 1 (default, trustless): manual DNS, prints the records to publish.
  * Tier 2 (STALWART_DNS_API_KEY set): Stalwart auto-publishes DNS + ACME
    DNS-01 via the provider (Spaceship wired).

Authelia SSO is opt-in (STALWART_SSO_ENABLE); admin + relay keep password
auth as break-glass so enabling SSO can never lock you out.

.env.example: documents the tiered DNS + SSO surface (core reuses existing
fields; only tier-2 needs DNS provider keys). README: quickstart step + layout.

Validated: bash -n; all JMAP payloads build valid JSON; read/idempotency
paths against a live instance. NOT yet validated on a fresh boot (fallback
admin -> create -> re-auth) or the OIDC login round-trip — verify on a
throwaway deploy before relying on those paths.

Shaped to drop into federatedSocial bootstrap.sh as cmd_provision_stalwart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:35:15 -04:00

159 lines
7.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================================
# 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=
# ----------------------------------------------------------------------------
# Provisioning — `./provision-stalwart.sh` configures Stalwart from this .env
# ----------------------------------------------------------------------------
# Run it AFTER `docker compose up -d`. It writes stores, listeners, the primary
# domain (+DKIM), the admin + relay/catch-all accounts, TLS/DNS and (optional)
# SSO — all via the x: JMAP API. Idempotent; re-run any time.
#
# TIER 1 (default, trustless): leave the DNS keys below blank. The domain is
# created in MANUAL dns/dkim mode and the script PRINTS the records to publish.
# Certs: mount your wildcard, or front :80 at the edge for HTTP-01.
#
# TIER 2 (auto-DNS): set the DNS provider keys and Stalwart auto-publishes every
# record (MX/SPF/DKIM/DMARC/MTA-STS/SRV/CAA/TLS-RPT) and does ACME DNS-01.
# Provider tokens are fiddly and provider-specific (HE's API is flaky; Spaceship
# needs API access enabled on the key) — so this stays opt-in and user-managed.
STALWART_DNS_PROVIDER=spaceship # currently only 'spaceship' is wired
STALWART_DNS_API_KEY= # set => tier 2; blank => tier 1
STALWART_DNS_API_SECRET=
STALWART_DNS_DESC=managed # label for the x:DnsServer entry
# ACME contact email (enables Let's Encrypt DNS-01 in tier 2). Blank = skip ACME.
STALWART_ACME_CONTACT=
# --- SSO: let Authelia manage Stalwart login (optional) ---------------------
# true => provision-stalwart.sh creates an OIDC directory pointing at
# AUTHELIA_PORTAL_URL and prints the Authelia client block to paste. admin and
# the relay account KEEP password auth as break-glass, so SSO can't lock you out.
# (Login flow is UNVALIDATED on a throwaway here — test before trusting upstream.)
STALWART_SSO_ENABLE=false
STALWART_OIDC_CLIENT_SECRET= # shared secret for the Stalwart<->Authelia client
# ============================================================================
# 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.<domain>) 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=