feat: contact form backend for linux and freebsd servers
Test / test (push) Successful in 2m1s
Release / gates (push) Successful in 1m57s
Release / build (amd64, freebsd) (push) Successful in 1m26s
Release / build (amd64, linux) (push) Successful in 1m30s
Release / build (arm64, freebsd) (push) Successful in 1m28s
Release / build (arm64, linux) (push) Successful in 1m49s
Release / build (loong64, linux) (push) Successful in 1m30s
Release / build (riscv64, linux) (push) Successful in 1m29s
Release / release (push) Successful in 41s
Test / test (push) Successful in 2m1s
Release / gates (push) Successful in 1m57s
Release / build (amd64, freebsd) (push) Successful in 1m26s
Release / build (amd64, linux) (push) Successful in 1m30s
Release / build (arm64, freebsd) (push) Successful in 1m28s
Release / build (arm64, linux) (push) Successful in 1m49s
Release / build (loong64, linux) (push) Successful in 1m30s
Release / build (riscv64, linux) (push) Successful in 1m29s
Release / release (push) Successful in 41s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user