tailwart/README.md
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

74 lines
3.7 KiB
Markdown
Raw Permalink 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
> Tailscale × Stalwart. A mailbox with no WAN presence, fronted by a layer-4
> proxy that can live on another machine entirely.
A deliberately over-engineered playground: [Stalwart](https://github.com/stalwartlabs/stalwart)
mail server wired into **Postgres + Redis + Garage S3** at once, deployed as a
Tailscale sidecar, with a separate `caddy-l4` edge that pipes the raw mail ports
over the tailnet. For `infinidim.net`.
See [CLAUDE.md](./CLAUDE.md) for the architecture and the gotchas, and
[LESSONS.md](./LESSONS.md) for the hard-won symptom→cause→fix notes from
bringing it up (PG races, ACME DNS-01, PROXY-protocol trust, IPv6 egress, …).
## Layout
```
tailwart/
├── docker-compose.yml # the mailbox: ts-stalwart sidecar + stalwart (+ IPv6 egress)
├── config/config.json # Stalwart v0.16 bootstrap config — datastore (Postgres) ONLY
├── config/config.toml # dead v0.15-era config, kept as historical reference
├── caddy/ # the edge: custom Caddy (caddy-l4) layer-4 mail proxy
│ ├── Dockerfile # pulls prebuilt caddy-l4 binary (caddyserver.com, no local build)
│ ├── caddy.json # :25/465/587/143/993 mail + :443 SNI fan-out → stalwart over the tailnet
│ ├── docker-compose.yml # deploy on any public-IP, tailnet, tag:reverse-proxy host
│ └── README.md
├── provision-stalwart.sh # one-shot: configure Stalwart entirely from .env (idempotent)
├── acl-snippet.hujson # tag:stalwart owner + grants to merge into your policy
├── .env.example # operator surface — copy to .env
└── .gitignore
```
> **v0.16 config model:** `config.json` describes *only* where Postgres lives;
> everything else (domains, accounts, listeners, ACME, blob/Redis wiring, proxy
> trust, DKIM, spam) lives **in Postgres**, managed via the web UI or JMAP. The
> old TOML + `%{env}%` macro model is gone — see CLAUDE.md / LESSONS.md.
## Quickstart
```bash
cp .env.example .env && $EDITOR .env # fill secrets (see CLAUDE.md prereqs)
# 1. create the stalwart role/db in shared Postgres + the Garage bucket
# (one-off; see CLAUDE.md "Prerequisites")
# 2. admin console: assign tag:stalwart to the OAuth client + paste acl-snippet
# 3. bring up the mailbox (tailnet-only)
docker compose up -d
# 4. configure Stalwart entirely from .env — stores, listeners, domain (+DKIM),
# admin + relay/catch-all accounts, TLS/DNS, optional Authelia SSO. Idempotent.
./provision-stalwart.sh # add --print-dns to also dump records to publish
# 5. bring up the edge (binds public mail ports; can be a different host)
cd caddy && docker compose up -d --build
```
`provision-stalwart.sh` replaces the setup wizard: it writes config straight into
Postgres via the `x:` JMAP API. **Tier 1** (default) configures everything and
*prints* the DNS records for you to publish; **tier 2** (set `STALWART_DNS_API_KEY`)
lets Stalwart auto-publish DNS + run ACME DNS-01. See the comments in `.env.example`.
Then point `infinidim.net`'s MX at the edge host (or let tier-2 publish it) and
finish any opinionated bits (spam tuning, retention) in the web admin.
## Status
Live. Pinned to `stalwartlabs/stalwart:v0.16.7`, booting cleanly against the
shared Postgres/Redis/Garage backends, holding a Let's Encrypt wildcard via
ACME DNS-01 (Spaceship), and relaying outbound over the tailnet (the VPS blocks
all outbound SMTP ports). The container has its own IPv6 egress.
Not yet exposed for inbound IPv6 mail: `mail.infinidim.net` is intentionally
A-only until the edge proxy gains v6 `:25` listeners — publish its `AAAA` and
the v6 listeners together, or senders will try v6 and some won't fall back. See
LESSONS.md.