feat(cluster): deploy browser WebSocket + CORS to the 3-node cluster

Roll the --ws-port + --cors-origins flags (issue uniweb/0001) out to the unibus
cluster so the browser-native uniweb client can reach the data plane (nats.ws)
and the control plane (CORS) on every node. The WS reuses the data-plane TLS
(wss://) and the same origin allowlist.

Per-node WS port override (WS_PORT_<NAME>): magnus runs unibus_admin on
127.0.0.1:8480, so the bus WS binds 8485 there to avoid a crash-loop; homer and
datardos keep 8480. deploy-cluster.sh also gains DEPLOY_ONLY=<name> for rolling
one node at a time. Rolled out and verified 2026-06-13: all three nodes healthy,
WS reachable, CORS 204, cluster quorum (R3) intact throughout.
This commit is contained in:
2026-06-13 23:23:52 +02:00
parent c0216de766
commit feb917fc6a
3 changed files with 31 additions and 1 deletions
+15
View File
@@ -69,6 +69,12 @@ routes_for() {
echo "==> [2/3] stage each node (REMOTE_DIR=$REMOTE_DIR)"
for row in "${CLUSTER_NODES[@]}"; do
read -r name ssh _pub _wg <<<"$row"
# Rolling deploy: DEPLOY_ONLY=<name> stages just that node, so a new binary can be
# rolled out one node at a time (the other nodes keep the cluster quorum). Empty =
# stage every node (the original behavior).
if [[ -n "${DEPLOY_ONLY:-}" && "$name" != "$DEPLOY_ONLY" ]]; then
continue
fi
target="${SSH_USER}@${ssh}"
nodedir="out/${name}"
if [[ ! -d "$nodedir" ]]; then
@@ -79,6 +85,13 @@ for row in "${CLUSTER_NODES[@]}"; do
echo "-- node ${name} (ssh ${ssh}) routes=${routes}"
# Resolve this node's WebSocket port. magnus runs unibus_admin on 127.0.0.1:8480,
# so the bus WS cannot bind 0.0.0.0:8480 there (it crash-loops). A per-node
# override (WS_PORT_<NAME> in nodes.env) lets magnus use a free port while the
# rest share the default — keeping the deploy reproducible (issue uniweb/0001).
node_ws_var="WS_PORT_${name^^}"
node_ws="${!node_ws_var:-$WS_PORT}"
# Generate this node's cluster.env locally, then ship it.
envfile="build/cluster-${name}.env"
mkdir -p build
@@ -90,6 +103,8 @@ KV_REPLICAS=${KV_REPLICAS}
HTTP_PORT=${HTTP_PORT}
NATS_CLIENT_PORT=${NATS_CLIENT_PORT}
NATS_ROUTE_PORT=${NATS_ROUTE_PORT}
WS_PORT=${node_ws}
CORS_ORIGINS=${CORS_ORIGINS}
ROUTES=${routes}
CLUSTER_PASS_FILE=${REMOTE_DIR}/secrets/cluster.pass
TLS_CERT=${REMOTE_DIR}/tls/server-${name}.crt
+3 -1
View File
@@ -35,7 +35,9 @@ ExecStart=/opt/unibus/membershipd \
--route-tls-ca ${ROUTE_TLS_CA} \
--internal-id-file ${INTERNAL_ID_FILE} \
--store kv \
--kv-replicas ${KV_REPLICAS}
--kv-replicas ${KV_REPLICAS} \
--ws-port ${WS_PORT} \
--cors-origins ${CORS_ORIGINS}
# Restart=always (NOT on-failure): a clean SIGTERM exits success, and on-failure
# would then NOT restart, leaving the node silently dead (see function_tags.md).
Restart=always
+13
View File
@@ -23,6 +23,19 @@ NATS_CLIENT_PORT=4250
NATS_ROUTE_PORT=6250
HTTP_PORT=8470
# Browser data-plane: WebSocket listener so the browser-native uniweb client
# (nats.ws) reaches NATS, and the CORS allowlist for its calls to the control
# plane. WS reuses the data-plane TLS, so it serves wss:// (the cluster runs with
# TLS). CORS_ORIGINS is a comma-separated list of allowed browser origins (no
# spaces). Issue uniweb/0001. The node's firewall must allow WS_PORT.
WS_PORT=8480
# Per-node WS port override (WS_PORT_<NAME>). magnus runs unibus_admin on
# 127.0.0.1:8480, so the bus WebSocket cannot bind 0.0.0.0:8480 there — it would
# crash-loop. magnus therefore serves the browser WS on 8485; homer and datardos
# keep 8480 (no admin panel). Verified during the 2026-06-13 rollout.
WS_PORT_MAGNUS=8485
CORS_ORIGINS="http://localhost:5173"
# Remote install layout and SSH login user.
REMOTE_DIR="/opt/unibus"
SSH_USER="root"