Skip to main content

Realtime events with Reverb

Problem

The web app and realtime: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 the websocket role, the same way the metrics role runs Prometheus and Grafana.

Solution

Add the role

The 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

  1. It installs the role packages: caddy, composer, git, openssl, php-curl, and php-xml. php-curl and php-xml let composer install pass its platform check on a fresh Ubuntu 26.04 Node.
  2. It generates a Reverb app ID, app key, app secret, and a Laravel APP_KEY the first time, and reuses them later. It stores them as settings of that Node. The secret and APP_KEY are encrypted.
  3. It checks out https://github.com/nckrtl/orbit-reverb.git at main into /opt/orbit/websocket as the managed user and runs composer install --no-dev.
  4. It writes the app’s .env with mode 0600.
  5. It installs and starts orbit-websocket.service, which runs php artisan reverb:start --host=127.0.0.1 --port=8790 with Restart=always.
  6. It issues an Orbit CA certificate for reverb.orbit and adds a Caddy site on the Node that proxies to Reverb. Caddy admits the WebSocket upgrade.
  7. It publishes reverb.orbit in private DNS with the Node’s WireGuard address.
Before any change, the converge refuses a Node that lacks an address its Caddy sites bind, so a refused converge leaves a running Reverb alone. When a step after the runtime fails, the converge stops the runtime again. A failed rollback reports 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

Relocate copies the Reverb identity to the target when the target has none, and never overwrites one. It converges the target and points 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 one websocket 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.