Assisted-by: GLM 5.3 Flash
6.3 KiB
Deployment
How nuntius runs in production: a single static binary behind Caddy, under systemd, with its state in one data directory.
Topology
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):
handle_path /api/nuntius/* {
reverse_proxy 127.0.0.1:8080
}
Then validate and reload:
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
nuntiussystem 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
just build
Copy the artefacts to the server:
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:
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:
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
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:
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:
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:
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.