Read this after
The reference Quadlet unit at docker/rousseau-agent.container covers the "how to run rousseau" story. This guide covers what you add around it before calling it production: logs, backups, health, and process hygiene.
Log shipping
Rousseau writes structured logs to stderr via log/slog (internal/cli/root.go). When you run it under systemd, that stderr lands in the journal. Options for shipping off the host:
| Tool | Fit | Notes |
|---|---|---|
Vector (vector.dev) |
Best default. | journald source + a filter dropping DEBUG. Ship to Loki, Datadog, S3, whatever. |
| Promtail + Loki | If you already run Grafana. | Loki's journal source works directly against journalctl -o json. |
| Datadog Agent | If Datadog is the org standard. | The DD agent has a journald tail. Structured JSON parses natively. |
| Fluent Bit | Small footprint alternative. | Set log.format: json in config.yaml; Fluent Bit's systemd input parses. |
Configure log.format: json (internal/config/config.go LogConfig.Format) unconditionally in production. Text output is designed for less, not machine parsing.
See Guides: Observability for a full Loki pipeline recipe.
Session-store backup
The state directory ~/.local/share/rousseau/ is the only durable state rousseau owns. Back it up nightly.
Two approaches:
1. SQLite .backup (recommended).
sqlite3 ~/.local/share/rousseau/sessions.db \
".backup '/backup/sessions.db.$(date +%Y%m%d).bak'"
sqlite3 ~/.local/share/rousseau/whatsapp.db \
".backup '/backup/whatsapp.db.$(date +%Y%m%d).bak'"
restic backup /backup
.backup uses SQLite's online API — safe even while the daemon is writing. See Reference: Session store.
2. Filesystem snapshot.
Because WAL journaling is on (Open() in internal/state/sqlite/store.go), restic and borg can snapshot the raw files while the daemon runs. WAL guarantees a consistent point-in-time image.
Do not:
- Copy the
.dbfile withcpwhile the daemon is running unless you also copy-waland-shm. - Store backups on the same disk.
- Skip the WhatsApp device credentials file — losing it means re-scanning the QR.
Health checks
rousseau status (internal/cli/status.go) exits 0 on healthy, non-zero on trouble. Use it as a systemd health probe:
[Service]
ExecStartPost=/usr/bin/timeout 30 podman exec rousseau-agent rousseau status
For a richer probe, script a check that:
- Runs
rousseau status. - Confirms the session store's last write was recent (
stat sessions.db -c %Ycompared to now). - Checks the container's uptime via
podman inspect.
Rousseau does not expose an HTTP /healthz. If your platform requires one (Kubernetes readiness probes), see Guides: Kubernetes deployment — you wrap rousseau in a small curl-friendly sidecar.
Rolling restart
Because state is a single SQLite file, the daemon is genuinely single-instance. A rolling restart is: stop, replace image, start. No warm-up required.
podman pull localhost/rousseau-agent:local # or rebuild locally
systemctl --user restart rousseau-agent
podman logs -n 50 rousseau-agent | grep -E 'starting|connected'
Expected log sequence (from internal/transport/whatsapp/client.go):
INFO whatsapp.starting store=… allowlist=1
INFO whatsapp.connected
If the daemon does not emit whatsapp.connected within ~15 seconds, roll back.
Multiple transports on one host
You may want the same session store shared by WhatsApp and Slack. Two ways:
- Multiple Quadlet units — one for each transport, each pointing at the same
state.path. WAL +busy_timeout(seeOpen()ininternal/state/sqlite/store.go) makes concurrent writers safe. - One binary, one transport per invocation. Rousseau's transport commands are single-transport (
whatsapp,slack,signal, …). To run two transports you run two processes.
Zero-downtime configuration changes
Rousseau does not hot-reload config.yaml. Config changes require a restart. SIGHUP is not wired for reload.
Practical workflow:
- Edit
~/.config/rousseau/config.yaml. systemctl --user restart rousseau-agent.- Verify from logs.
For most transports the reconnection is fast (~1-3 seconds). The main pause is on WhatsApp, where whatsmeow re-establishes the websocket.
Log retention
journald retention is set by SystemMaxUse= in /etc/systemd/journald.conf. For an audit-friendly deployment, ship logs off-host and set journald to a shorter retention on the local disk (e.g. 7 days) so the audit trail lives in Loki/S3, not on a filesystem an intruder might rotate.
Container image lifecycle
Rebuild the image on every rousseau release you want to adopt:
cd ~/rousseau-agent
git pull
podman build -t rousseau-agent:local -f docker/Dockerfile .
systemctl --user restart rousseau-agent
The Quadlet AutoUpdate=disabled line (in docker/rousseau-agent.container) prevents podman auto-update from touching the container. You control the update cadence.
Related
- Deployment — the reference Quadlet unit.
- Tutorial: Deploy to a VPS — worked example.
- Guides: Observability — log pipeline.
- Guides: Enterprise Onboarding — full checklist.