251 lines
8.3 KiB
Markdown
251 lines
8.3 KiB
Markdown
# Production Deployment
|
|
|
|
This guide covers installing nuntius on a Linux server with `Caddy`, `systemd`, and `rsync` already in place — the same setup used by [petrbalvin.org](https://www.petrbalvin.org). The same `rsync -avz` workflow that ships the static website works for nuntius with no changes.
|
|
|
|
## Architecture in Production
|
|
|
|
```mermaid
|
|
graph LR
|
|
Browser[Visitor's browser]:::accent6
|
|
Internet((Internet)):::accent2
|
|
Caddy[Caddy :443]:::accent0
|
|
Nuntius[nuntius :8080]:::accent1
|
|
Systemd[systemd unit]:::accent7
|
|
SMTP[(SMTP provider)]:::accent2
|
|
Disk[(/var/lib/nuntius/data/*.jsonl)]:::accent2
|
|
|
|
Browser --> Internet
|
|
Internet --> Caddy
|
|
Caddy -->|reverse_proxy /api/nuntius/| Nuntius
|
|
Systemd -.->|manages| Nuntius
|
|
Nuntius -->|SMTP PLAIN| SMTP
|
|
Nuntius -->|append JSONL| Disk
|
|
```
|
|
|
|
nuntius binds `[::]:8080` by default — the dual-stack IPv6 wildcard that accepts both IPv4 and IPv6 connections on Linux and FreeBSD. Caddy should be configured to proxy to `[::1]:8080` or `127.0.0.1:8080` for loopback-only access.
|
|
|
|
## 1. Build and Upload (Local Machine)
|
|
|
|
```bash
|
|
cd nuntius
|
|
just build
|
|
# → ./bin/nuntius
|
|
|
|
rsync -avz \
|
|
bin/nuntius \
|
|
.env.example \
|
|
user@your-server:/tmp/nuntius/
|
|
```
|
|
|
|
> `install.py` lives in the repository root. The systemd unit is included inline in Option A below (you do not need to rsync it from the repo). `.env.example` is a template for the secrets file. Adjust the `rsync` source list to match what you keep checked in.
|
|
|
|
## 2. Install on the Server (SSH Session)
|
|
|
|
### Option A — Manual (the README quick start)
|
|
|
|
```bash
|
|
ssh user@your-server
|
|
|
|
# Create the nuntius system user (one time)
|
|
sudo useradd --system --shell /usr/sbin/nologin --home-dir /var/lib/nuntius nuntius
|
|
sudo mkdir -p /var/lib/nuntius
|
|
sudo chown nuntius:nuntius /var/lib/nuntius
|
|
|
|
# Install the binary
|
|
sudo install -m 0755 /tmp/nuntius/nuntius /usr/local/bin/nuntius
|
|
|
|
# First run auto-creates /etc/nuntius/config.toml with the 3-form template
|
|
sudo /usr/local/bin/nuntius &
|
|
sleep 2
|
|
sudo kill %1
|
|
|
|
# Install the systemd service (created inline from this guide)
|
|
sudo tee /etc/systemd/system/nuntius.service > /dev/null <<'EOF'
|
|
[Unit]
|
|
Description=nuntius contact form backend
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=nuntius
|
|
Group=nuntius
|
|
WorkingDirectory=/var/lib/nuntius
|
|
EnvironmentFile=/etc/nuntius/.env
|
|
ExecStart=/usr/local/bin/nuntius
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now nuntius
|
|
|
|
# Set up secrets
|
|
sudo cp /tmp/nuntius/.env.example /etc/nuntius/.env
|
|
sudo chmod 600 /etc/nuntius/.env
|
|
sudo chown root:nuntius /etc/nuntius/.env
|
|
sudo $EDITOR /etc/nuntius/.env # set NUNTIUS_SMTP_PASSWORD
|
|
|
|
# Edit the auto-generated config to your real SMTP + allowed origins
|
|
sudo $EDITOR /etc/nuntius/config.toml
|
|
|
|
# Restart and watch logs
|
|
sudo systemctl restart nuntius
|
|
sudo journalctl -u nuntius -f
|
|
```
|
|
|
|
The service file expects the binary at `/usr/local/bin/nuntius`, the env file at `/etc/nuntius/.env`, and the data dir at `/var/lib/nuntius` (the config's `data_dir: "./data"` resolves to `/var/lib/nuntius/data` because the unit sets `WorkingDirectory=/var/lib/nuntius`). If you'd rather use `/opt/nuntius/...` or any other layout, edit the systemd unit block above before installing.
|
|
|
|
### Option B — `install.py` (idempotent)
|
|
|
|
Before running the script, copy the systemd unit from the block in Option A above into `/tmp/nuntius/nuntius.service` (the script reads it from the current working directory):
|
|
|
|
```bash
|
|
ssh user@your-server
|
|
cd /tmp/nuntius
|
|
# Paste the [Unit]…[Install] block from Option A into this file:
|
|
sudo $EDITOR /tmp/nuntius/nuntius.service
|
|
sudo python3 install.py
|
|
```
|
|
|
|
The script is idempotent: re-running on an already-installed host is safe. It:
|
|
|
|
1. Creates the `nuntius` system user if missing.
|
|
2. Creates `/var/lib/nuntius` with the right ownership.
|
|
3. Installs the binary to `/usr/local/bin/nuntius`.
|
|
4. Installs the systemd unit to `/etc/systemd/system/nuntius.service`.
|
|
5. Runs nuntius briefly to auto-generate `/etc/nuntius/config.toml`.
|
|
6. Copies `.env.example` to `/etc/nuntius/.env` (mode 0600) if missing.
|
|
7. Reloads systemd and `enable`s the service — but does **not** start it, so you can edit `/etc/nuntius/.env` and `/etc/nuntius/config.toml` first.
|
|
|
|
The script ends with the exact next steps you need to run by hand.
|
|
|
|
## 3. Caddy 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:
|
|
|
|
```bash
|
|
sudo caddy validate
|
|
sudo systemctl reload caddy
|
|
```
|
|
|
|
Caddy forwards `X-Forwarded-For` automatically — so the real client IP is always passed to nuntius without any extra configuration.
|
|
|
|
## 4. Wire It Up to Your Web
|
|
|
|
If you proxy `/api/nuntius/*` through Caddy (recommended, step 3 above), `VITE_NUNTIUS_URL` can stay empty and the form will post to the same origin as the page. Otherwise, set it to the bare upstream URL:
|
|
|
|
```bash
|
|
# .env (in the web project, only needed if NOT proxying through Caddy)
|
|
VITE_NUNTIUS_URL=https://your-server:8080
|
|
```
|
|
|
|
For a feedback or newsletter endpoint on the same site, just call the matching path:
|
|
|
|
```js
|
|
await fetch(`${import.meta.env.VITE_NUNTIUS_URL || ''}/api/nuntius/feedback`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name, email, message }),
|
|
});
|
|
|
|
await fetch(`${import.meta.env.VITE_NUNTIUS_URL || ''}/api/nuntius/newsletter`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email }),
|
|
});
|
|
```
|
|
|
|
CORS is enforced per form by the `allowed_origins` list in `config.toml`. Add every frontend origin that should be able to post to each form.
|
|
|
|
## 5. Updating Later
|
|
|
|
```bash
|
|
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'
|
|
```
|
|
|
|
For schema changes, also ship the new `config.toml` and a migration note in your changelog.
|
|
|
|
## 6. Updating the Config
|
|
|
|
The service does **not** auto-reload `config.toml`. After editing it, restart:
|
|
|
|
```bash
|
|
sudo $EDITOR /etc/nuntius/config.toml
|
|
sudo systemctl restart nuntius
|
|
sudo journalctl -u nuntius -f
|
|
```
|
|
|
|
The process exits 1 on a broken config (unknown field, unknown form type, duplicate path, missing SMTP host, …) and systemd will not keep it up. Fix the reported line and retry.
|
|
|
|
## Files This Guide Creates on the Server
|
|
|
|
| File | Owner | Mode | Purpose |
|
|
|---|---|---|---|
|
|
| `/usr/local/bin/nuntius` | `root` | `0755` | The static binary |
|
|
| `/var/lib/nuntius/` | `nuntius` | `0750` | Working directory + data dir |
|
|
| `/var/lib/nuntius/data/` | `nuntius` | `0750` | Newsletter JSONL logs (created at runtime) |
|
|
| `/etc/nuntius/config.toml` | `root` | `0644` | nuntius config (auto-generated, then edited) |
|
|
| `/etc/nuntius/.env` | `root:nuntius` | `0600` | Secrets (SMTP password) |
|
|
| `/etc/systemd/system/nuntius.service` | `root` | `0644` | systemd unit |
|
|
|
|
`/var/lib/nuntius/data/newsletter-*.jsonl` is created on the first `newsletter` form submission, by the `nuntius` user.
|
|
|
|
## Verifying the Deploy
|
|
|
|
After `systemctl restart nuntius`, check:
|
|
|
|
```bash
|
|
# Service is up
|
|
systemctl status nuntius
|
|
|
|
# Binary version
|
|
/usr/local/bin/nuntius --version
|
|
|
|
# Listener is bound
|
|
ss -tlnp | grep 8080
|
|
|
|
# Health endpoint responds
|
|
curl -s http://127.0.0.1:8080/health | jq .
|
|
|
|
# Recent log lines
|
|
journalctl -u nuntius -n 50 --no-pager
|
|
|
|
# Caddy proxy works
|
|
curl -i https://your-domain.example/api/nuntius/health
|
|
|
|
# End-to-end POST through Caddy
|
|
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"}'
|
|
```
|
|
|
|
If the end-to-end POST returns `{"ok": true}` and `journalctl -u nuntius -f` shows a `request` line with `status=200`, the deploy is live.
|
|
|
|
## Backing Up
|
|
|
|
- `/etc/nuntius/config.toml` and `/etc/nuntius/.env` — back up alongside your other config files.
|
|
- `/var/lib/nuntius/data/*.jsonl` — back up the JSONL log with your usual file backup job. It is append-only and trivially `tar`'able.
|
|
|
|
## Uninstalling
|
|
|
|
```bash
|
|
sudo systemctl disable --now nuntius
|
|
sudo rm /etc/systemd/system/nuntius.service /usr/local/bin/nuntius
|
|
sudo rm -rf /etc/nuntius /var/lib/nuntius
|
|
sudo userdel nuntius
|
|
```
|