Files
ukmesh/scripts/unify-networks.sh
T
hermes-gadgetandgadgethd f445a24467 fix: close High findings #42 + #46 — public status DTO, split-infra resolver (#99)
* fix(api): allowlist public node status fields

* fix(ops): resolve split infrastructure containers exactly

* docs: add C5a mission handoff

---------

Co-authored-by: gadgethd <111318106+gadgethd@users.noreply.github.com>
2026-09-30 23:19:31 +01:00

112 lines
5.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
project_dir="$(cd -- "$script_dir/.." && pwd)"
cd "$project_dir"
if [ -n "${MESHCORE_INFRA_COMPOSE_FILE:-}" ]; then
infra_compose_file="$MESHCORE_INFRA_COMPOSE_FILE"
infra_project_dir="${MESHCORE_INFRA_PROJECT_DIR:-${MESHCORE_INFRA_DIR:-$(dirname -- "$infra_compose_file")}}"
else
infra_project_dir="${MESHCORE_INFRA_PROJECT_DIR:-${MESHCORE_INFRA_DIR:-${project_dir}/../meshcore-infra}}"
infra_compose_file="$infra_project_dir/docker-compose.yml"
fi
export MESHCORE_INFRA_PROJECT_DIR="$infra_project_dir"
export MESHCORE_INFRA_COMPOSE_FILE="$infra_compose_file"
source "$script_dir/lib/infra-compose.sh"
MODE="${1:-audit}"
timescaledb_container="$(resolve_infra_container timescaledb)"
PSQL=(docker exec -i "$timescaledb_container" psql -U "${POSTGRES_USER:-meshcore}" -d "${POSTGRES_DB:-meshcore}" -v ON_ERROR_STOP=1 -X)
LEGACY_SQL="'teesside','northeast'"
RUN_ID="${NETWORK_UNIFICATION_RUN_ID:-network-unification-$(date -u +%Y%m%dT%H%M%SZ)}"
if [[ ! "$RUN_ID" =~ ^[A-Za-z0-9._:-]+$ ]]; then
printf 'Unsafe NETWORK_UNIFICATION_RUN_ID: %s\n' "$RUN_ID" >&2
exit 2
fi
log() { printf '[%s] %s\n' "$(date -Is)" "$*"; }
scalar() { "${PSQL[@]}" -qAt -c "$1" | tr -d '[:space:]'; }
audit() {
log 'network-label counts'
"${PSQL[@]}" <<'SQL'
SELECT 'nodes' AS relation, network, COUNT(*) FROM nodes GROUP BY network
UNION ALL SELECT 'node_status_samples', network, COUNT(*) FROM node_status_samples GROUP BY network
UNION ALL SELECT 'packets', network, COUNT(*) FROM packets GROUP BY network
UNION ALL SELECT 'node_network_sightings', network, COUNT(*) FROM node_network_sightings GROUP BY network
UNION ALL SELECT 'path_prefix_priors', network, COUNT(*) FROM path_prefix_priors GROUP BY network
ORDER BY 1, 2;
SQL
log 'recent legacy-writer activity'
"${PSQL[@]}" -c "SELECT network, MAX(time) AS latest, COUNT(*) AS packets_15m FROM packets WHERE network IN (${LEGACY_SQL}) AND time > NOW() - INTERVAL '15 minutes' GROUP BY network ORDER BY network;"
}
verify() {
local remaining
remaining="$(scalar "SELECT (SELECT COUNT(*) FROM nodes WHERE network IN (${LEGACY_SQL})) + (SELECT COUNT(*) FROM node_status_samples WHERE network IN (${LEGACY_SQL})) + (SELECT COUNT(*) FROM packets WHERE network IN (${LEGACY_SQL})) + (SELECT COUNT(*) FROM node_network_sightings WHERE network IN (${LEGACY_SQL}));")"
audit
if [[ "$remaining" != '0' ]]; then
log "ERROR: ${remaining} authoritative rows still use legacy production labels"
return 1
fi
log 'verification passed: authoritative production labels are unified'
}
relabel_status_batches() {
local batch_size="${NETWORK_UNIFICATION_STATUS_BATCH_SIZE:-50000}"
local changed=1 total=0
while [[ "$changed" -gt 0 ]]; do
changed="$(scalar "WITH batch AS MATERIALIZED (SELECT ctid FROM node_status_samples WHERE network IN (${LEGACY_SQL}) LIMIT ${batch_size}), changed AS (UPDATE node_status_samples target SET network = 'ukmesh' FROM batch WHERE target.ctid = batch.ctid RETURNING 1) SELECT COUNT(*) FROM changed;")"
total=$((total + changed))
log "node_status_samples relabelled=${total} last_batch=${changed}"
done
}
mark_failed() {
local status=$?
trap - ERR
"${PSQL[@]}" -qAt -c "UPDATE network_unification_runs SET status='failed', detail=jsonb_build_object('exit_status', ${status}) WHERE run_id='${RUN_ID//\'/\'\'}';" >/dev/null || true
exit "$status"
}
apply() {
if [[ "${CONFIRM_NETWORK_UNIFICATION:-}" != 'ukmesh' ]]; then
log 'ERROR: set CONFIRM_NETWORK_UNIFICATION=ukmesh for the irreversible apply step'
return 2
fi
if [[ -z "${BACKUP_REFERENCE:-}" ]]; then
log 'ERROR: BACKUP_REFERENCE must identify a verified pre-cutover database snapshot'
return 2
fi
local active_legacy
active_legacy="$(scalar "SELECT COUNT(*) FROM packets WHERE network IN (${LEGACY_SQL}) AND time > NOW() - INTERVAL '15 minutes';")"
if [[ "$active_legacy" != '0' ]]; then
log "ERROR: ${active_legacy} legacy-labelled packets arrived in the last 15 minutes; update/stop legacy writers first"
return 3
fi
trap mark_failed ERR
"${PSQL[@]}" -qAt -c "INSERT INTO network_unification_runs (run_id,status,backup_reference) VALUES ('${RUN_ID//\'/\'\'}','running','${BACKUP_REFERENCE//\'/\'\'}') ON CONFLICT (run_id) DO UPDATE SET status='running';"
audit
log 'applying small-table and derived-table cutover'
"${PSQL[@]}" -f scripts/unify-networks-migration.sql
log 'relabeling node status history in bounded commits'
relabel_status_batches
log 'relabeling packet chunks with compression-state preservation'
scripts/relabel-packets-per-chunk.sh
verify
"${PSQL[@]}" -qAt -c "UPDATE network_unification_runs SET status='completed', completed_at=NOW(), detail=jsonb_build_object('verified_at', NOW()) WHERE run_id='${RUN_ID//\'/\'\'}';"
trap - ERR
log "cutover complete run_id=${RUN_ID}"
}
case "$MODE" in
audit) audit ;;
verify) verify ;;
apply) apply ;;
*) printf 'Usage: %s [audit|apply|verify]\n' "$0" >&2; exit 2 ;;
esac