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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user