diff --git a/server/broker-cutover.sh b/server/broker-cutover.sh new file mode 100755 index 0000000..9a6f14c --- /dev/null +++ b/server/broker-cutover.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Move a running broker from passwd/acl files to the dynamic-security plugin, +# keeping every existing login and password. Run AFTER deploy.sh has put a +# server on the host that has `broker-init`. +# +# server/broker-cutover.sh do it +# ROLLBACK=1 server/broker-cutover.sh put the previous config back +# +# What it does: backs up mosquitto/config, converts passwd → the plugin's store +# inside the broker's data volume (same hashes, so no shop PC re-claims), +# rewrites mosquitto.conf, restarts the broker, and proves the server and the +# health probe reconnect. Every step before the restart is reversible by not +# doing the restart; the rollback restores the backed-up config and restarts. +set -euo pipefail +HOST=${HOST:-root@66.116.226.161} +KEY=${KEY:-$HOME/.ssh/behavision_deploy} +DIR=/root/behavision +SSH=(ssh -i "$KEY" -o BatchMode=yes -o ConnectTimeout=10 "$HOST") +step() { printf '\n\033[1m%s\033[0m\n' "$*"; } + +if [ -n "${ROLLBACK:-}" ]; then + step "Rolling back to the passwd/acl configuration" + "${SSH[@]}" "cd $DIR && latest=\$(ls -d mosquitto/config.bak-* | tail -1) && cp \$latest/mosquitto.conf mosquitto/config/mosquitto.conf && docker compose restart mosquitto && sleep 3 && docker logs --tail 5 behavision-mqtt" + exit 0 +fi + +step "1. Back up the broker configuration" +"${SSH[@]}" "cd $DIR && cp -a mosquitto/config mosquitto/config.bak-\$(date +%Y%m%d-%H%M%S) && ls -d mosquitto/config.bak-* | tail -1" + +step "2. Convert passwd into the plugin's store (hashes unchanged)" +# The data volume belongs to the broker's user (1883); the init runs as root to +# write there and then hands the file over. Refuses if a store already exists. +"${SSH[@]}" "cd $DIR && docker run --rm --user root \ + -v $DIR/mosquitto/config:/m:ro -v behavision_mosquitto-data:/d \ + --entrypoint /usr/local/bin/behavision-server behavision-backend:latest \ + broker-init -passwd /m/passwd -out /d/dynamic-security.json \ + && docker run --rm --user root -v behavision_mosquitto-data:/d alpine:3.20 sh -c 'chown 1883:1883 /d/dynamic-security.json && chmod 600 /d/dynamic-security.json && ls -la /d/dynamic-security.json'" + +step "3. Rewrite mosquitto.conf for the plugin" +"${SSH[@]}" "cd $DIR && python3 - <<'PY' +import re +p = 'mosquitto/config/mosquitto.conf' +s = open(p).read() +s = re.sub(r'^per_listener_settings\s+true\s*$', 'per_listener_settings false', s, flags=re.M) +s = re.sub(r'^(password_file|acl_file)\s+.*\n', '', s, flags=re.M) +if 'mosquitto_dynamic_security' not in s: + s = s.rstrip('\n') + '\n\n# Logins and topic permissions live in the dynamic-security plugin now.\n# The server creates a shop\'s login over the control topic; nothing is\n# edited by hand and nothing is reloaded.\nplugin /usr/lib/mosquitto_dynamic_security.so\nplugin_opt_config_file /mosquitto/data/dynamic-security.json\n' +open(p, 'w').write(s) +print(open(p).read()) +PY" + +step "4. Restart the broker" +"${SSH[@]}" "cd $DIR && docker compose restart mosquitto && sleep 4 && docker logs --tail 8 behavision-mqtt 2>&1 | grep -i 'error\|plugin\|running\|connected' | tail -6" + +step "5. Prove the server and the health probe are back" +"${SSH[@]}" "cd $DIR && sleep 6 && docker logs --since 30s behavision-backend 2>&1 | grep -i 'broker\|subscribed' | tail -3; docker inspect behavision-mqtt --format 'health: {{.State.Health.Status}}' 2>/dev/null || true; docker logs --since 40s behavision-mqtt 2>&1 | grep -i 'not authori\|denied' | head -3 || true" +echo +echo "If step 5 shows 'subscribed to bv/#' and no 'not authorised', the cutover is done." +echo "Anything wrong: ROLLBACK=1 server/broker-cutover.sh"