# Deployment How nuntius runs in production: a single static binary behind Caddy, under systemd, with its state in one data directory. ## Topology ```mermaid flowchart LR Browser[Visitor's browser] --> Caddy[Caddy :443] Caddy -->|reverse_proxy /api/nuntius/*| Nuntius[nuntius :8080] Systemd[Systemd unit] -.->|manages| Nuntius Nuntius -->|SMTP PLAIN| SMTP[(SMTP provider)] Nuntius -->|append JSONL| Disk[(/var/lib/nuntius/data)] ``` nuntius binds `[::]:8080` by default: the dual-stack wildcard that accepts both IPv4 and IPv6 connections. The host part is the `server.bind` key in `config.toml`; set it to `127.0.0.1` for a loopback-only listener, which is the recommended shape behind a proxy. ### Reverse proxy Add this directive to your Caddyfile (most likely `/etc/caddy/Caddyfile`): ```caddyfile handle_path /api/nuntius/* { reverse_proxy 127.0.0.1:8080 } ``` Then validate and reload: ```sh sudo caddy validate sudo systemctl reload caddy ``` Caddy overwrites `X-Forwarded-For` for untrusted clients, which is what the `server.trust_proxy_headers` opt-in expects. Frontends on the same site post to the same origin and need no extra URL; the per-form `allowed_origins` list carries every origin that may post. ## Requirements - A Linux or FreeBSD host with systemd and a C compiler-free runtime: the binary is static, nothing else ships with it. - The `nuntius` system user and group, the data directory `/var/lib/nuntius`, the configuration at `/etc/nuntius/config.toml`, and the environment file `/etc/nuntius/.env` (all created by the installer or the manual steps below). - Port 8080 locally, 443 publicly through Caddy. - An SMTP account (any standards-compliant provider, such as Proton Mail or Thundermail). | File | Owner | Mode | Purpose | |---|---|---|---| | `/usr/local/bin/nuntius` | `root` | `0755` | The static binary | | `/var/lib/nuntius/` | `nuntius` | `0750` | Working directory and data root | | `/var/lib/nuntius/data/` | `nuntius` | `0750` | JSONL logs and the rate-limit snapshot (created at runtime) | | `/etc/nuntius/config.toml` | `root` | `0644` | The configuration (auto-generated, then edited) | | `/etc/nuntius/.env` | `root:nuntius` | `0600` | Secrets, loaded via `EnvironmentFile=` | | `/etc/systemd/system/nuntius.service` | `root` | `0644` | The systemd unit | ## Build ```sh just build ``` Copy the artefacts to the server: ```sh rsync -avz bin/nuntius nuntius.service .env.example scripts/install.pl user@your-server:/tmp/nuntius/ ``` ## Run The installer does the whole sequence and is idempotent; re-running is safe and it never starts the service, so you review the configuration first: ```sh ssh user@your-server cd /tmp/nuntius sudo perl install.pl ``` It creates the `nuntius` system user, `/var/lib/nuntius`, installs the binary and the unit, generates the starter configuration, seeds `/etc/nuntius/.env` from `.env.example`, and enables the service. Then review and start: ```sh sudo $EDITOR /etc/nuntius/.env # set NUNTIUS_SMTP_PASSWORD sudo $EDITOR /etc/nuntius/config.toml # real SMTP settings and allowed origins sudo systemctl start nuntius sudo journalctl -u nuntius -f ``` Without the installer, the same steps by hand: create the user and directory as above, `install -m 0755` the binary, `install -m 0644` the unit, start the binary once to generate `/etc/nuntius/config.toml`, `systemctl daemon-reload && systemctl enable --now nuntius`. ## Service unit The unit lives at `nuntius.service` in the repository root and installs to `/etc/systemd/system/nuntius.service`; the copy in this document would drift, the pointer does not. It runs as the `nuntius` user with `WorkingDirectory=/var/lib/nuntius` (so the default `data_dir = "./data"` resolves to `/var/lib/nuntius/data`), loads `/etc/nuntius/.env`, validates the configuration through `ExecStartPre=/usr/local/bin/nuntius --check-config`, and restarts on failure. The service is enabled, not started, after installation. ## Production configuration The keys that differ from the defaults in production: `allowed_origins` carries the real frontend origins, the `[forms.smtp]` block carries the real host and credentials, `server.metrics_token` guards the metrics endpoint when it is exposed, and `server.bind` stays `::` or moves to a loopback address behind the proxy. Secrets live in `/etc/nuntius/.env` and reach the configuration through `${VAR}` references; no secret value belongs in `config.toml` or in this repository. ## Upgrade ```sh cd nuntius just build rsync -avz bin/nuntius user@your-server:/tmp/nuntius/ ssh user@your-server 'sudo install -m 0755 /tmp/nuntius/nuntius /usr/local/bin/nuntius && sudo systemctl restart nuntius' ``` The service does not reload `config.toml`; after editing it, restart. A broken configuration exits 1 and systemd does not keep it up, so fix the reported key and start again. `just gates` before the new binary ships. ## Rollback Reinstall the previous release's binary from the releases page and restart; the configuration, the JSONL logs and the rate-limit snapshot carry over untouched. Rehearsed exactly this far, and no further: there is no automated rollback path. The reverse of the whole installation is `systemctl disable --now nuntius`, removing the unit, the binary, `/etc/nuntius` and `/var/lib/nuntius`, and deleting the user. ## Monitoring A healthy instance answers the health endpoint and writes one JSON request line per submission: ```sh systemctl status nuntius /usr/local/bin/nuntius --version curl -s http://127.0.0.1:8080/health journalctl -u nuntius -n 50 --no-pager curl -i https://your-domain.example/api/nuntius/health ``` An end-to-end check through the proxy: ```sh curl -X POST https://your-domain.example/api/nuntius/contact \ -H "Content-Type: application/json" \ -H "Origin: https://your-domain.example" \ -d '{"name":"Test","email":"test@example.com","message":"hello there, this is a test message"}' ``` `{"ok": true}` plus a `request` line with `status=200` in the journal means the deploy is live. Subscriber counts come from the log: ```sh wc -l /var/lib/nuntius/data/newsletter-newsletter.jsonl jq -r .email /var/lib/nuntius/data/newsletter-newsletter.jsonl | sort -u ``` Back up `/etc/nuntius/config.toml`, `/etc/nuntius/.env` and `/var/lib/nuntius/data/*.jsonl` with the usual jobs; the log is append-only and trivially archivable.