From feb917fc6ad5c028dc2f5c2fb347fac9ab03cd14 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 13 Jun 2026 23:23:52 +0200 Subject: [PATCH] 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_): 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= 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. --- deploy/cluster/deploy-cluster.sh | 15 +++++++++++++++ deploy/cluster/membershipd-cluster.service | 4 +++- deploy/cluster/nodes.env | 13 +++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/deploy/cluster/deploy-cluster.sh b/deploy/cluster/deploy-cluster.sh index f14fba09..6ce2a774 100755 --- a/deploy/cluster/deploy-cluster.sh +++ b/deploy/cluster/deploy-cluster.sh @@ -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= 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_ 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 diff --git a/deploy/cluster/membershipd-cluster.service b/deploy/cluster/membershipd-cluster.service index ddb88c4b..6a937912 100644 --- a/deploy/cluster/membershipd-cluster.service +++ b/deploy/cluster/membershipd-cluster.service @@ -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 diff --git a/deploy/cluster/nodes.env b/deploy/cluster/nodes.env index 3a0886d0..d99ab6ab 100644 --- a/deploy/cluster/nodes.env +++ b/deploy/cluster/nodes.env @@ -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_). 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"