#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
BASE="${ROUTER_OPS_BASE:-/opt/router-ops}"; ROOT="${BUNDLE_ROOT:?}"; RUN_DIR="${ROUTER_DETACHED_RUN_DIR:?}"; CHILD_RESULT="${ROUTER_DETACHED_CHILD_RESULT:?}"; INCOMING="${ROUTER_STEP_INCOMING:-/home/ops/incoming}"; SELFTEST="${ROUTER_EXACT_WORKER_SELFTEST:-false}"
CASE=pass
if [[ "$SELFTEST" == true ]]; then case "${ROUTER_PUBLIC_ROOT:-}" in */fail-source/public) CASE=fail-source;; */fail-runtime/public) CASE=fail-runtime;; esac; fi
STEP=STEP_050M07P24_R01_DURABLE_PROFILE_PROVISIONING_BOUNDARY_PREFLIGHT
VM121_COMMIT=e3409bee5b1466839a7293af6ead97e515ca8f20; VM121_TREE=7ace7d88b02a457f91469394c6f1f8bdf0c528b5; VM121_COUNT=47
VM100_COMMIT=e865282f4837747b1babfac4ac199680c6b43d04; VM100_TREE=8f04afbbc77006fe117bf1228b1f4fc977be9a47; VM100_COUNT=33
CURRENT="$RUN_DIR/workflow-current.txt"; EVENTS="$RUN_DIR/workflow-events.tsv"; EVIDENCE="$RUN_DIR/evidence"; REPORT="$RUN_DIR/report"; PAYLOAD="$RUN_DIR/public-payload"; FINAL_ENV="$RUN_DIR/final-publication.env"; TARGET_DIR="$RUN_DIR/target"
mkdir -p "$EVIDENCE" "$REPORT" "$PAYLOAD" "$TARGET_DIR" "$INCOMING"; chmod 700 "$EVIDENCE" "$REPORT" "$PAYLOAD" "$TARGET_DIR"
source "$BASE/lib/router-step-workflow.sh"; source <("$BASE/bin/router-release-contract" env --root "$ROOT")
router_step_workflow_init "$CURRENT" "$EVENTS" preflight runtime_discovery report_publish archive
CORE_CHANGE_COMPLETE=false; CORE_CHANGE_REPEATED=false; SOURCE_CHANGED=false; STOP_DETAIL=unknown; STOP_HANDLED=false; STEP_REPORT_URL=unknown; LATEST_ARCHIVE=not_created; LATEST_ARCHIVE_SHA256=unknown
last_field(){ local k="$1" f="$2"; awk -F= -v k="$k" '$1==k{v=substr($0,index($0,"=")+1)} END{print v}' "$f"; }
write_child(){ local result="$1" rc="$2" tmp="${CHILD_RESULT}.tmp.$$"; { echo RESULT="$result"; echo WORKER_RC="$rc"; echo CORE_CHANGE_COMPLETE=false; echo CORE_CHANGE_REPEATED=false; echo SOURCE_CHANGED=false; echo LIVE_DATABASE_WRITTEN=false; echo DATABASE_SCHEMA_CHANGED=false; echo RUNTIME_PROVISIONING_ACTIVE=false; echo EXTERNAL_ONBOARDING_ACTIVE=false; echo STOP_DETAIL="$STOP_DETAIL"; echo VM121_COMMIT="$VM121_COMMIT"; echo VM121_TREE="$VM121_TREE"; echo VM121_FILE_COUNT="$VM121_COUNT"; echo VM100_COMMIT="$VM100_COMMIT"; echo VM100_TREE="$VM100_TREE"; echo VM100_FILE_COUNT="$VM100_COUNT"; echo STEP_REPORT_URL="$STEP_REPORT_URL"; echo LATEST_ARCHIVE="$LATEST_ARCHIVE"; echo LATEST_ARCHIVE_SHA256="$LATEST_ARCHIVE_SHA256"; } > "$tmp"; chmod 600 "$tmp"; mv "$tmp" "$CHILD_RESULT"; }
archive_latest(){ local log="$EVIDENCE/archive.log"; router_step_archive_latest_python "$BASE" "$INCOMING" "$(date -u +%Y%m%d-%H%M%S)" | tee "$log"; LATEST_ARCHIVE="$(last_field LATEST_ARCHIVE "$log")"; LATEST_ARCHIVE_SHA256="$(last_field LATEST_ARCHIVE_SHA256 "$log")"; }
write_stop_report(){ local rc="$1" line="$2" cmd="$3"; cat > "$REPORT/report.txt" <<EOF
RESULT=STOP_P24_R01_DURABLE_PROFILE_PROVISIONING_BOUNDARY_PREFLIGHT
REVISION=R01
STOP_RC=$rc
STOP_LINE=$line
STOP_DETAIL=$STOP_DETAIL
CORE_CHANGE_COMPLETE=false
SOURCE_CHANGED=false
LIVE_DATABASE_WRITTEN=false
DATABASE_SCHEMA_CHANGED=false
RUNTIME_PROVISIONING_ACTIVE=false
EXTERNAL_ONBOARDING_ACTIVE=false
VM121_COMMIT=$VM121_COMMIT
VM121_TREE=$VM121_TREE
VM100_COMMIT=$VM100_COMMIT
VM100_TREE=$VM100_TREE
EOF
printf 'STOP_COMMAND=%q\n' "$cmd" >> "$REPORT/report.txt"; cp "$REPORT/report.txt" "$REPORT/verify.txt"; cp "$ROOT/scripts/run-step.sh" "$REPORT/executed-step.sh"; cp "$ROOT/release-info.json" "$REPORT/release-info.json"; cp "$ROOT/rollback/README.md" "$REPORT/rollback.txt"; [[ ! -f "$EVIDENCE/vm121-preflight.txt" ]] || cp "$EVIDENCE/vm121-preflight.txt" "$REPORT/"; [[ ! -f "$EVIDENCE/vm100-preflight.txt" ]] || cp "$EVIDENCE/vm100-preflight.txt" "$REPORT/"; }
stop_handler(){ local rc="$1" line="$2" cmd="$3"; trap - ERR; [[ "$STOP_HANDLED" == false ]] || return "$rc"; STOP_HANDLED=true; write_stop_report "$rc" "$line" "$cmd"; set +e; if [[ "$SELFTEST" == true ]]; then DEST="${ROUTER_PUBLIC_ROOT:?}/latest/p24-r01-$CASE-selftest"; rm -rf "$DEST"; mkdir -p "$DEST"; cp -a "$REPORT"/. "$DEST"/; STEP_REPORT_URL="selftest://p24-r01-$CASE/"; archive_latest >/dev/null 2>&1; else args=(); while IFS= read -r f; do args+=("$f" "$(basename "$f")"); done < <(find "$REPORT" -maxdepth 1 -type f -print | LC_ALL=C sort); router_step_publish_payload step050m07p24_r01_durable_profile_provisioning_boundary_preflight_stop "$REPORT" "$PAYLOAD" "$CURRENT" "$EVENTS" "$FINAL_ENV" "${args[@]}" >/dev/null 2>&1 || true; [[ -f "$FINAL_ENV" ]] && STEP_REPORT_URL="$(last_field PUBLIC_URL "$FINAL_ENV")"; archive_latest >/dev/null 2>&1; fi; set -e; write_child STOP_P24_R01_DURABLE_PROFILE_PROVISIONING_BOUNDARY_PREFLIGHT "$rc"; return "$rc"; }
trap 'rc=$?; stop_handler "$rc" "$LINENO" "$BASH_COMMAND"; exit "$rc"' ERR
self_source(){ local machine="$1" head="$2" tree="$3" count="$4"; cat <<EOF
MODE=--check
MACHINE_ID=$machine
LOCAL_HEAD=$head
REMOTE_HEAD=$head
STAGED_CHANGE_COUNT=0
SIMULATED_TREE_SHA=$tree
SIMULATED_TREE_FILE_COUNT=$count
RESULT=PASS_${machine^^}_GIT_PUBLISH_CHECK
GIT_COMMIT=$head
GIT_TREE=$tree
GIT_FILE_COUNT=$count
COMMIT_CREATED=false
PUSH_PERFORMED=false
EOF
}
self_v121(){ cat <<'EOF'
P24_VM121_PROBE_SCHEMA=p24-r01-v1
BACKEND_HEALTH=PASS
DOMAIN_V2_SHA256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
CREDENTIAL_SERVICE_SHA256=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
AGENT_SHA256=cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
CREDENTIAL_SERVICE_PRESENT=true
EXISTING_AGENT_LIFECYCLE_ADAPTER_PRESENT=true
AGENT_DIRECT_RUNTIME_WRITE_PRESENT=false
AGENT_PATH_ENABLED=enabled
AGENT_PATH_ACTIVE=active
AGENT_TIMER_ENABLED=enabled
AGENT_TIMER_ACTIVE=active
AGENT_SERVICE_ACTIVE=inactive
PRODUCTION_KEYRING_PRESENT=true
PRODUCTION_KEYRING_MODE=600
PRODUCTION_KEYRING_SCHEMA=wg-paid-credential-keyring-v1
PRODUCTION_KEYRING_ACTIVE_VERSION=1
PRODUCTION_KEYRING_VERSION_COUNT=1
ALEMBIC_VERSION=0003_domain_v2_schema
BUSINESS_ROWS=0
CONNECTION_PROFILE_ROWS=0
PEER_CREDENTIAL_ROWS=0
PROVISIONING_JOB_ROWS=0
PROVISIONING_JOB_PENDING=0
PROVISIONING_JOB_RUNNING=0
CONNECTION_PROFILE_COLUMN_COUNT=11
CONNECTION_PROFILE_TUNNEL_IP_PRESENT=false
PROVISIONING_JOB_CONNECTION_PROFILE_ID_PRESENT=true
PROVISIONING_JOB_OPERATION_ID_PRESENT=true
PROVISIONING_JOB_DESIRED_GENERATION_PRESENT=true
PROVISIONING_JOB_NEXT_ATTEMPT_AT_PRESENT=true
LIVE_DATABASE_WRITTEN=false
DATABASE_SCHEMA_CHANGED=false
RUNTIME_PROVISIONING_ACTIVE=false
EXTERNAL_ONBOARDING_ACTIVE=false
NO_SECRET_VALUES_EMITTED=true
RESULT=PASS_P24_R01_VM121_PREFLIGHT
EOF
}
self_v100(){ cat <<'EOF'
P24_VM100_PROBE_SCHEMA=p24-r01-v1
PEER_LIFECYCLE_SCHEMA=router-wgpay-peer-lifecycle-config-v1
PEER_WIREGUARD_ENABLED=1
PEER_WIREGUARD_INTERFACE=wg_paid
PEER_WIREGUARD_POOL=10.253.0.0/16
PEER_AMNEZIAWG_ENABLED=0
PEER_AMNEZIAWG_POOL=10.254.0.0/16
LIFECYCLE_STATUS_RESULT=PASS_PEER_LIFECYCLE_STATUS
LIFECYCLE_INITIALIZED=true
LIFECYCLE_REGISTRY_VALID=true
LIFECYCLE_PROFILE_COUNT=5
LIFECYCLE_WIREGUARD_COUNT=5
LIFECYCLE_AMNEZIAWG_COUNT=0
LIFECYCLE_SELECTOR_REGISTRY_CONSISTENT=true
LIFECYCLE_MODE_ENABLE_PRESENT=true
LIFECYCLE_MODE_ENSURE_PRESENT=true
LIFECYCLE_MODE_DISABLE_PRESENT=true
LIFECYCLE_IDEMPOTENT_RECEIPTS_PRESENT=true
LIFECYCLE_RUNTIME_IP_CONFLICT_CHECK_PRESENT=true
LIFECYCLE_POOL_VALIDATION_PRESENT=true
REGISTRY_COUNT=5
REGISTRY_TUNNEL_IPS=10.253.1.10,10.253.1.11,10.253.1.12,10.253.1.13,10.253.1.14
WG_PAID_RUNTIME_PEER_COUNT=5
WG_PAID_RUNTIME_TUNNEL_IPS=10.253.1.10,10.253.1.11,10.253.1.12,10.253.1.13,10.253.1.14
ACTIVE_SELECTOR_COUNT=5
ACTIVE_SELECTOR_TUNNEL_IPS=10.253.1.10,10.253.1.11,10.253.1.12,10.253.1.13,10.253.1.14
LIFECYCLE_RECEIPT_COUNT=0
RUNTIME_MUTATED=false
SELECTOR_MUTATED=false
RESULT=PASS_P24_R01_VM100_PREFLIGHT
EOF
}
router_step_phase_begin preflight
if [[ "$SELFTEST" == true ]]; then self_source vm121 "$VM121_COMMIT" "$VM121_TREE" "$VM121_COUNT" > "$EVIDENCE/vm121-source.txt"; self_source vm100 "$VM100_COMMIT" "$VM100_TREE" "$VM100_COUNT" > "$EVIDENCE/vm100-source.txt"; [[ "$CASE" != fail-source ]] || sed -i 's/STAGED_CHANGE_COUNT=0/STAGED_CHANGE_COUNT=1/' "$EVIDENCE/vm121-source.txt"; else ssh -T -o BatchMode=yes -o ConnectTimeout=20 vm121 /usr/local/sbin/router-vm121-git-publish.sh --check > "$EVIDENCE/vm121-source.txt" 2>&1; ssh -T -o BatchMode=yes -o ConnectTimeout=20 pve-mgts "ssh -n -o BatchMode=yes -o ConnectTimeout=10 -i /root/.ssh/pve_to_openwrt_mgts_ed25519 root@10.71.100.1 /usr/local/sbin/router-vm100-git-publish.sh --check" > "$EVIDENCE/vm100-source.txt" 2>&1; fi
"$ROOT/scripts/source-check-validate.sh" "$EVIDENCE/vm121-source.txt" vm121 "$VM121_COMMIT" "$VM121_TREE" "$VM121_COUNT" > "$EVIDENCE/vm121-source-validation.txt" || { STOP_DETAIL=vm121_source; false; }
"$ROOT/scripts/source-check-validate.sh" "$EVIDENCE/vm100-source.txt" vm100 "$VM100_COMMIT" "$VM100_TREE" "$VM100_COUNT" > "$EVIDENCE/vm100-source-validation.txt" || { STOP_DETAIL=vm100_source; false; }
PKG="$ROOT/$ROUTER_RELEASE_TARGET_PACKAGE_PATH"; "$ROOT/scripts/target-apply.sh" "$ROUTER_RELEASE_TARGET_PACKAGE_SHA256" "$PKG" "$TARGET_DIR" > "$EVIDENCE/target-extract.txt"; router_step_phase_complete preflight exact_cross_machine_source_and_probe_verified
router_step_phase_begin runtime_discovery
if [[ "$SELFTEST" == true ]]; then self_v121 > "$EVIDENCE/vm121-preflight.txt"; self_v100 > "$EVIDENCE/vm100-preflight.txt"; [[ "$CASE" != fail-runtime ]] || sed -i 's/CONNECTION_PROFILE_TUNNEL_IP_PRESENT=false/CONNECTION_PROFILE_TUNNEL_IP_PRESENT=true/' "$EVIDENCE/vm121-preflight.txt"; else ssh -T -o BatchMode=yes -o ConnectTimeout=20 vm121 /bin/bash -s < "$TARGET_DIR/vm121-p24-r01-preflight.sh" > "$EVIDENCE/vm121-preflight.txt" 2>&1; ssh -T -o BatchMode=yes -o ConnectTimeout=20 pve-mgts "ssh -n -o BatchMode=yes -o ConnectTimeout=10 -i /root/.ssh/pve_to_openwrt_mgts_ed25519 root@10.71.100.1 'sh -s'" < "$TARGET_DIR/vm100-p24-r01-preflight.sh" > "$EVIDENCE/vm100-preflight.txt" 2>&1; fi
"$ROOT/scripts/runtime-preflight-validate.sh" "$EVIDENCE/vm121-preflight.txt" "$EVIDENCE/vm100-preflight.txt" > "$EVIDENCE/runtime-preflight-validation.txt" || { STOP_DETAIL=cross_machine_preflight_semantics; false; }
router_step_phase_complete runtime_discovery live_p24_boundary_facts_retained
router_step_phase_begin report_publish
REGISTRY_IPS="$(last_field REGISTRY_TUNNEL_IPS "$EVIDENCE/vm100-preflight.txt")"; WG_POOL="$(last_field PEER_WIREGUARD_POOL "$EVIDENCE/vm100-preflight.txt")"; REG_COUNT="$(last_field REGISTRY_COUNT "$EVIDENCE/vm100-preflight.txt")"; RUNTIME_COUNT="$(last_field WG_PAID_RUNTIME_PEER_COUNT "$EVIDENCE/vm100-preflight.txt")"; SELECTOR_COUNT="$(last_field ACTIVE_SELECTOR_COUNT "$EVIDENCE/vm100-preflight.txt")"; RECEIPTS="$(last_field LIFECYCLE_RECEIPT_COUNT "$EVIDENCE/vm100-preflight.txt")"; AGENT_PATH="$(last_field AGENT_PATH_ACTIVE "$EVIDENCE/vm121-preflight.txt")"; AGENT_TIMER="$(last_field AGENT_TIMER_ACTIVE "$EVIDENCE/vm121-preflight.txt")"
cat > "$REPORT/report.txt" <<EOF
RESULT=PASS_P24_R01_DURABLE_PROFILE_PROVISIONING_BOUNDARY_PREFLIGHT
REVISION=R01
CORE_CHANGE_COMPLETE=false
CORE_CHANGE_REPEATED=false
SOURCE_CHANGED=false
LIVE_DATABASE_WRITTEN=false
DATABASE_SCHEMA_CHANGED=false
RUNTIME_PROVISIONING_ACTIVE=false
EXTERNAL_ONBOARDING_ACTIVE=false
ALEMBIC_VERSION=0003_domain_v2_schema
BUSINESS_ROWS=0
CONNECTION_PROFILE_TUNNEL_IP_PRESENT=false
PROVISIONING_JOB_DOMAIN_V2_FIELDS_PRESENT=true
PRODUCTION_CREDENTIAL_SERVICE_PRESENT=true
EXISTING_VM121_AGENT_LIFECYCLE_ADAPTER_PRESENT=true
AGENT_DIRECT_RUNTIME_WRITE_PRESENT=false
VM121_AGENT_PATH_ACTIVE=$AGENT_PATH
VM121_AGENT_TIMER_ACTIVE=$AGENT_TIMER
VM100_COMMON_LIFECYCLE_PRESENT=true
VM100_LIFECYCLE_ENABLE_ENSURE_DISABLE=true
VM100_LIFECYCLE_IDEMPOTENT_RECEIPTS=true
VM100_LIFECYCLE_RUNTIME_IP_CONFLICT_CHECK=true
VM100_WIREGUARD_POOL=$WG_POOL
VM100_REGISTRY_COUNT=$REG_COUNT
VM100_RUNTIME_PEER_COUNT=$RUNTIME_COUNT
VM100_SELECTOR_COUNT=$SELECTOR_COUNT
VM100_REGISTRY_TUNNEL_IPS=$REGISTRY_IPS
VM100_RECEIPT_COUNT=$RECEIPTS
VM121_COMMIT=$VM121_COMMIT
VM121_TREE=$VM121_TREE
VM121_FILE_COUNT=$VM121_COUNT
VM100_COMMIT=$VM100_COMMIT
VM100_TREE=$VM100_TREE
VM100_FILE_COUNT=$VM100_COUNT
RUNTIME_IMPACT=false
COPY_VERIFY=PASS
NEXT_ACTION=P24 R02 Class C durable tunnel-IP field/allocation plus Domain V2 provisioning adapter built only from retained R01 facts; reuse existing VM100 lifecycle; no public onboarding
EOF
cp "$REPORT/report.txt" "$REPORT/verify.txt"; cp "$ROOT/scripts/run-step.sh" "$REPORT/executed-step.sh"; cp "$ROOT/release-info.json" "$REPORT/release-info.json"; cp "$ROOT/rollback/README.md" "$REPORT/rollback.txt"; cp "$EVIDENCE/vm121-preflight.txt" "$REPORT/"; cp "$EVIDENCE/vm100-preflight.txt" "$REPORT/"; cp "$EVIDENCE/runtime-preflight-validation.txt" "$REPORT/"; cp "$EVIDENCE/vm121-source-validation.txt" "$REPORT/"; cp "$EVIDENCE/vm100-source-validation.txt" "$REPORT/"
if [[ "$SELFTEST" == true ]]; then DEST="${ROUTER_PUBLIC_ROOT:?}/latest/p24-r01-pass-selftest"; rm -rf "$DEST"; mkdir -p "$DEST"; cp -a "$REPORT"/. "$DEST"/; STEP_REPORT_URL=selftest://p24-r01-pass/; else args=(); while IFS= read -r f; do args+=("$f" "$(basename "$f")"); done < <(find "$REPORT" -maxdepth 1 -type f -print | LC_ALL=C sort); router_step_publish_payload step050m07p24_r01_durable_profile_provisioning_boundary_preflight "$REPORT" "$PAYLOAD" "$CURRENT" "$EVENTS" "$FINAL_ENV" "${args[@]}"; STEP_REPORT_URL="$(last_field PUBLIC_URL "$FINAL_ENV")"; fi
router_step_phase_complete report_publish report_published
router_step_phase_begin archive; archive_latest; router_step_phase_complete archive latest_archived; STOP_DETAIL=none; write_child PASS_P24_R01_DURABLE_PROFILE_PROVISIONING_BOUNDARY_PREFLIGHT 0; trap - ERR; exit 0
