#!/usr/bin/env bash set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${repo_root}" project_name="${MINEROPS_CONTROL_BOARD_MIGRATION_PROJECT:-minerops-control-board-migration-$$}" compose=(docker compose --project-name "${project_name}" --env-file .env.dev -f compose.dev.yaml) cleanup() { status=$? "${compose[@]}" down --volumes --remove-orphans >/dev/null 2>&1 || true exit "${status}" } trap cleanup EXIT run_alembic() { "${compose[@]}" run --rm --no-deps backend alembic "$@"; } [[ -f .env.dev ]] || { echo "Missing .env.dev" >&2; exit 1; } "${compose[@]}" up --detach postgres for attempt in $(seq 1 60); do "${compose[@]}" exec -T postgres sh -ec \ 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' >/dev/null 2>&1 && break (( attempt < 60 )) || exit 1 sleep 1 done "${compose[@]}" build backend run_alembic upgrade 0012_return_to_service "${compose[@]}" exec -T postgres sh -ec \ 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' <<'SQL' INSERT INTO users (id, username, display_name, password_hash, role_id) SELECT '00000000-0000-0000-0000-000000001401', 'board-migration', 'Board Migration', 'unused', id FROM roles WHERE name = 'Admin'; INSERT INTO miners ( id, asset_tag, current_mac, control_board_sn, lifecycle_status, status, first_seen_at, last_seen_at ) VALUES ( '00000000-0000-0000-0000-000000001402', 'BOARD-LEGACY', '02-AA-BB-CC-DD-01', 'LEGACY-BOARD', 'active', 'unknown', NULL, NULL ); SQL "${compose[@]}" exec -T postgres sh -ec \ 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' <<'SQL' INSERT INTO miners ( id, asset_tag, current_mac, lifecycle_status, status, first_seen_at, last_seen_at ) VALUES ( '00000000-0000-0000-0000-000000001404', 'BOARD-CONFLICT', '02:AA:BB:CC:DD:01', 'active', 'unknown', NULL, NULL ); SQL if preflight_error="$(run_alembic upgrade head 2>&1)"; then echo "Duplicate normalized MAC preflight did not stop the upgrade." >&2 exit 1 fi [[ "${preflight_error}" == *"normalized MAC values have multiple miner owners"* ]] "${compose[@]}" exec -T postgres sh -ec \ 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' <<'SQL' DELETE FROM miners WHERE id = '00000000-0000-0000-0000-000000001404'; SQL run_alembic upgrade head [[ "$(run_alembic current 2>/dev/null)" == *"0013_control_board_replacement"* ]] "${compose[@]}" exec -T postgres sh -ec \ 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' <<'SQL' DO $$ DECLARE roles_for_permission text[]; BEGIN IF to_regclass('public.miner_control_board_installations') IS NULL OR to_regclass('public.miner_mac_ownerships') IS NULL THEN RAISE EXCEPTION 'Task 004 tables are missing'; END IF; IF EXISTS (SELECT 1 FROM miner_control_board_installations) THEN RAISE EXCEPTION 'Migration fabricated installation history'; END IF; SELECT array_agg(role.name ORDER BY role.name) INTO roles_for_permission FROM role_permissions rp JOIN roles role ON role.id = rp.role_id JOIN permissions permission ON permission.id = rp.permission_id WHERE permission.key = 'miners.replace_control_board'; IF roles_for_permission <> ARRAY['Admin', 'Manager', 'Technician'] THEN RAISE EXCEPTION 'Unexpected permission roles: %', roles_for_permission; END IF; IF (SELECT miner_id FROM miner_mac_ownerships WHERE normalized_mac = '02aabbccdd01') <> '00000000-0000-0000-0000-000000001402'::uuid THEN RAISE EXCEPTION 'Legacy MAC ownership was not preserved'; END IF; END $$; SQL run_alembic downgrade 0012_return_to_service run_alembic upgrade head "${compose[@]}" exec -T postgres sh -ec \ 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' <<'SQL' UPDATE miners SET lifecycle_status = 'in_repair', current_mac = '02:AA:BB:CC:DD:02', control_board_sn = 'NEW-BOARD' WHERE id = '00000000-0000-0000-0000-000000001402'; INSERT INTO miner_network_identities ( miner_id, mac_address, source, is_current, first_seen_at, last_seen_at, associated_at ) VALUES ( '00000000-0000-0000-0000-000000001402', '02:AA:BB:CC:DD:02', 'control_board_replacement', true, NULL, NULL, now() ); INSERT INTO repairs (id, miner_id, reason, opened_by) VALUES ( '00000000-0000-0000-0000-000000001403', '00000000-0000-0000-0000-000000001402', 'CONTROL_BOARD', '00000000-0000-0000-0000-000000001401' ); INSERT INTO miner_control_board_installations ( miner_id, record_origin, installed_repair_id, mac_address, normalized_mac, board_serial, replacement_reason, installed_at, installed_by ) VALUES ( '00000000-0000-0000-0000-000000001402', 'replacement', '00000000-0000-0000-0000-000000001403', '02:AA:BB:CC:DD:02', '02aabbccdd02', 'NEW-BOARD', 'failed', now(), '00000000-0000-0000-0000-000000001401' ); SQL if downgrade_error="$(run_alembic downgrade 0012_return_to_service 2>&1)"; then echo "Downgrade discarded authoritative control-board history." >&2 exit 1 fi [[ "${downgrade_error}" == *"authoritative installation records or events"* ]] [[ "$(run_alembic current 2>/dev/null)" == *"0013_control_board_replacement"* ]] database="minerops_control_board_empty" "${compose[@]}" exec -T postgres sh -ec \ 'createdb -U "$POSTGRES_USER" '"${database}" "${compose[@]}" run --rm --no-deps -e POSTGRES_DB="${database}" backend alembic upgrade head [[ "$("${compose[@]}" run --rm --no-deps -e POSTGRES_DB="${database}" backend alembic current 2>/dev/null)" == *"0013_control_board_replacement"* ]]