Stoat (Revolt fork) — Lockdown Playbook (Two Edge Options + Invite-Only Registration)
Target deployment: Linux VM on FreeBSD host, Stoat self-host stack with Caddy + api/events/autumn/january/gifbox.
Org goal: closed invite-only (Star Citizen org), ~12 active / 100+ registered.
Key outcomes
- Pick one authoritative edge (Apache-on-FreeBSD or Caddy-on-VM)
- Ensure WebSockets are stable (
/ws) - Make the instance invite-only (disable open registration)
- Close internal service exposure and tighten secrets
0) Your Current Routing (Ground Truth)
Your Caddyfile defines the correct Stoat routing. Keep these paths stable; everything else builds on them.
/ws is a dedicated WebSocket route. If the edge proxy does not support WebSocket upgrades, Stoat will look “mostly fine” but behave flaky.Option A1 (Recommended): Apache on FreeBSD is the Edge
Architecture: Internet → FreeBSD PF → Apache :443 → VM (private IP) → Caddy → Stoat services.
This centralizes control (TLS, headers, rate-limit/WAF, audit logs) on the FreeBSD host.
A1.1 Compose change: stop publishing VM ports to the internet
In compose.yml, remove public port publishing for the caddy service (or bind it only to a private interface).
The goal: the VM is not directly reachable from the internet on 80/443.
Prefer a private VM IP and no published ports. If you must publish, bind to a private interface only (not
0.0.0.0).A1.2 Apache vhosts: redirect HTTP and proxy HTTPS + WebSockets
Apache modules required: ssl, proxy, proxy_http, proxy_wstunnel, headers, rewrite.
Option A2 (Chosen): Caddy on the VM is the Edge (Harden VM + Caddy)
Architecture: Internet → FreeBSD PF (pass-through) → VM Caddy :443 → Stoat services.
This is what your current compose file implements (ports: "80:80", "443:443").
A2.1 Enforce HTTPS-only (redirect HTTP → HTTPS)
Add this near the top of your site block in Caddyfile:
A2.2 Firewall the VM: expose only what you intend
- Allow inbound: tcp/443
- Optional: allow inbound tcp/80 only to support redirect
- Block everything else inbound (SSH restricted to trusted IP/VPN)
Your
compose.yml does not publish the internal service ports (14702–14706), which is good. Keep it that way.A2.3 Harden internal secrets (strongly recommended)
Even if internal services are not public, weak credentials enable total takeover after any foothold.
Update these to long random values and store them in a restricted env file.
- RabbitMQ: replace
rabbituser/rabbitpass - MinIO: replace
MINIO_ROOT_PASSWORD=minioautumn - Files: set a strong
[files].encryption_keyinRevolt.toml
Registration Lockdown: Make the Instance Invite-Only
Stoat/Revolt self-host deployments commonly support an invite-only toggle in Revolt.toml under
[api.registration] as invite_only = true. :contentReference[oaicite:0]{index=0}
1) Update Revolt.toml (add these sections)
Your current
[files].encryption_key is empty. Set it before you treat the system as production.2) Restart the stack
3) Validate invite-only is truly enforced
- Open
https://revolt.yaws.com/loginand attempt to register without an invite. - Confirm the server blocks registration (not just “UI hidden”).
- Create/issue invites only to trusted recruiters/admins; use expirations + limited uses.
Client Config Notes (.env.web)
Your current .env.web is minimal:
Keep these consistent with [hosts] in Revolt.toml. Mismatched public URLs commonly cause login/WS issues.
Quick Checklist (Do These in Order)
- Edge choice: A1 (Apache edge) or A2 (VM edge). Don’t run both as public edges.
- Invite-only: set
[api.registration].invite_only = true. :contentReference[oaicite:1]{index=1} - Encryption key: set
[files].encryption_keyto a long random secret. - Secrets: replace RabbitMQ + MinIO weak defaults.
- Network: confirm only intended ports are reachable from WAN (443; optionally 80 redirect).