#!/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"