Realtime events with Reverb
Problem
The web app andrealtime:tail need the Gateway to push events. The Gateway publishes through Laravel Reverb, and clients subscribe at wss://reverb.orbit. Realtime events defines the channels and the events.
Cause
Reverb holds long-lived WebSocket connections, so it runs outside the Gateway process. It is a plain Laravel app with no Orbit code. The Gateway installs and runs it as thewebsocket role, the same way the metrics role runs Prometheus and Grafana.
Solution
Add the role
websocket role is a singleton. It conflicts with no other role, so it can share a Node with gateway and vpn. No Project, Instance, Route, or .env edit is involved.
What the converge does
- It installs the role packages:
caddy,composer,git,openssl,php-curl, andphp-xml.php-curlandphp-xmlletcomposer installpass its platform check on a fresh Ubuntu 26.04 Node. - It generates a Reverb app ID, app key, app secret, and a Laravel
APP_KEYthe first time, and reuses them later. It stores them as settings of that Node. The secret andAPP_KEYare encrypted. - It checks out
https://github.com/nckrtl/orbit-reverb.gitatmaininto/opt/orbit/websocketas the managed user and runscomposer install --no-dev. - It writes the app’s
.envwith mode0600. - It installs and starts
orbit-websocket.service, which runsphp artisan reverb:start --host=127.0.0.1 --port=8790withRestart=always. - It issues an Orbit CA certificate for
reverb.orbitand adds a Caddy site on the Node that proxies to Reverb. Caddy admits the WebSocket upgrade. - It publishes
reverb.orbitin private DNS with the Node’s WireGuard address.
websocket.rollback_failed.
ORBIT_WEBSOCKET_REPOSITORY, ORBIT_WEBSOCKET_REF, ORBIT_WEBSOCKET_INSTALL_PATH, and ORBIT_WEBSOCKET_PORT in the Gateway .env change the defaults. Another Reverb app must accept client events from channel members (accept_client_events_from set to members), as orbit-reverb does. Without it, the Node agent channels carry no agent events.
The Gateway reads the connection from the active role assignment. It needs no broadcast setting in its .env.
Move the role
reverb.orbit at it. It then waits 31 seconds, the private DNS answer TTL plus one second, so clients that resolved the old address resolve again. Then it removes the runtime and publication from the source. The Reverb checkout stays on the source. Clients keep the same key. Removing the role and adding it again would generate a new identity, and every client would need the new key. node:role:relocate describes --from for a target that already holds the role.
Remove the role
orbit node:role:remove <node> websocket --force withdraws the Caddy site, stops and disables the unit, removes the certificate and DNS record, and deletes the stored credentials. --purge-data also deletes the checkout. With --offline, the Gateway removes only the DNS record and the credentials.
Limits
This covers one Reverb server behind onewebsocket assignment. It does not cover several Reverb Nodes, Redis-backed scaling, or rotating the Reverb secret without a reconnect.
Verification
orbit realtime:show reports configured and wss://reverb.orbit. getent resolves reverb.orbit to the role Node’s WireGuard address. On the Node, systemctl is-active orbit-websocket prints active. From a Node that trusts the Orbit CA, curl -i -X POST https://reverb.orbit/apps/<app-id>/events answers 401 with Authentication signature invalid, which proves that Reverb answers behind the Caddy site.