#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
BASE="${ROUTER_OPS_BASE:-/opt/router-ops}"
FINAL_ROOT="${BUNDLE_ROOT:?}"
INCOMING="${ROUTER_STEP_INCOMING:-/home/ops/incoming}"
RUN_DIR="${ROUTER_DETACHED_RUN_DIR:?}"
CHILD_RESULT="${ROUTER_DETACHED_CHILD_RESULT:?}"
SELFTEST="${ROUTER_EXACT_WORKER_SELFTEST:-false}"
STEP='STEP_050M07R20QV_R01_VM100_WG_REMOTE_DEFAULT_CS4'; REVISION=R01
EXPECTED_PACKAGE_SHA="$(python3 - "$FINAL_ROOT/release-info.json" <<'PYINFO'
import json,sys
print(json.load(open(sys.argv[1],encoding='utf-8'))['target_package_sha256'])
PYINFO
)"
EXPECTED_FILE_SHA='d4bc25c4b9aa995799f6734a8c12bd260cf54e7450161f17edf5c156c60bd5da'
PACKAGE="$FINAL_ROOT/payload/vm100-wg-remote-default-cs4-r01-target-package.tar.gz"
WORK="$RUN_DIR/work"; EVIDENCE="$RUN_DIR/evidence"; REPORT="$RUN_DIR/report"; PAYLOAD="$RUN_DIR/public-payload"
CURRENT="$RUN_DIR/workflow-current.txt"; EVENTS="$RUN_DIR/workflow-events.tsv"; FINAL_ENV="$RUN_DIR/final-publication.env"
SOURCE_ENV="$RUN_DIR/vm100-source.txt"; PROJECT_ENV="$RUN_DIR/project-source.txt"; APPLY_ENV="$RUN_DIR/apply-result.txt"
CORE_CHANGE_COMPLETE=false; ROLLBACK_STATUS=not_required; CURRENT_PHASE=initialization; STOP_HANDLED=false; LATEST_ARCHIVE=not_created; LATEST_ARCHIVE_SHA256=unknown
mkdir -p "$WORK" "$EVIDENCE" "$REPORT" "$PAYLOAD" "$INCOMING"; chmod 700 "$WORK" "$EVIDENCE" "$REPORT" "$PAYLOAD"
source "$BASE/lib/router-step-workflow.sh"
router_step_workflow_init "$CURRENT" "$EVENTS" preflight backup apply postcheck source project_source report_build report_publish archive

write_child() {
  local result="$1" rc="$2" tmp="${CHILD_RESULT}.tmp.$$"
  { echo "RESULT=$result"; echo "WORKER_RC=$rc"; echo "CORE_CHANGE_COMPLETE=$CORE_CHANGE_COMPLETE"; echo "ROLLBACK_STATUS=$ROLLBACK_STATUS"; echo "LATEST_ARCHIVE=$LATEST_ARCHIVE"; echo "LATEST_ARCHIVE_SHA256=$LATEST_ARCHIVE_SHA256"; } > "$tmp"
  chmod 600 "$tmp"; mv -f "$tmp" "$CHILD_RESULT"
}
archive_latest() {
  local log="$EVIDENCE/archive-latest.log"
  router_step_archive_latest_python "$BASE" "$INCOMING" "$(date -u +%Y%m%d-%H%M%S)" | tee "$log"
  LATEST_ARCHIVE="$(awk -F= '$1=="LATEST_ARCHIVE"{print substr($0,index($0,"=")+1)}' "$log" | tail -n1)"
  LATEST_ARCHIVE_SHA256="$(awk -F= '$1=="LATEST_ARCHIVE_SHA256"{print $2}' "$log" | tail -n1)"
}
selftest_publish_dir() {
  local label="$1" src="$2" dir
  dir="${ROUTER_PUBLIC_ROOT:?}/latest/$label"
  rm -rf "$dir"; mkdir -p "$dir"; cp -a "$src"/. "$dir"/; chmod -R u+rwX,go+rX "$dir"
  printf 'selftest://%s/
' "$label"
}
build_stop_report() {
  local result="$1" line="$2" rc="$3" command="$4"
  mkdir -p "$REPORT"
  { echo "RESULT=$result"; echo "REVISION=$REVISION"; echo "STOP_PHASE=$CURRENT_PHASE"; echo "STOP_LINE=$line"; echo "STOP_RC=$rc"; printf 'STOP_COMMAND=%q
' "$command"; echo "CORE_CHANGE_COMPLETE=$CORE_CHANGE_COMPLETE"; echo "ROLLBACK_STATUS=$ROLLBACK_STATUS"; echo "RUNTIME_IMPACT=$CORE_CHANGE_COMPLETE"; } > "$REPORT/report.txt"
  cp "$CURRENT" "$REPORT/workflow-current.txt"; cp "$EVENTS" "$REPORT/workflow-events.tsv"; cp "$FINAL_ROOT/scripts/run-step.sh" "$REPORT/executed-step.sh"
  python3 - "$REPORT/facts.json" "$result" "$CURRENT_PHASE" "$CORE_CHANGE_COMPLETE" <<'PYSTOP'
import json,sys
p,result,phase,core=sys.argv[1:]
json.dump({"result":result,"phase":phase,"core_change_complete":core=="true"},open(p,"w"),indent=2); open(p,"a").write("\n")
PYSTOP
  cp "$REPORT/report.txt" "$REPORT/verify.txt"; printf 'Rollback status: %s
' "$ROLLBACK_STATUS" > "$REPORT/rollback.txt"
}
publish_stop() {
  local result="$1"
  if [[ "$SELFTEST" == true ]]; then
    selftest_publish_dir r20qv-r01-stop "$REPORT" >/dev/null
  else
    set +e
    router_step_publish_payload step050m07r20qv_r01_vm100_wg_remote_default_cs4_stop "$REPORT" "$PAYLOAD" "$CURRENT" "$EVENTS" "$FINAL_ENV"       "$REPORT/report.txt" report.txt "$REPORT/facts.json" facts.json "$REPORT/verify.txt" verify.txt "$REPORT/rollback.txt" rollback.txt "$REPORT/executed-step.sh" executed-step.sh "$REPORT/workflow-current.txt" workflow-current.txt "$REPORT/workflow-events.tsv" workflow-events.tsv
    set -e
  fi
  set +e; archive_latest; set -e
}
stop_handler() {
  local rc="$1" line="$2" command="$3"
  trap - ERR
  [[ "$STOP_HANDLED" == false ]] || exit "$rc"; STOP_HANDLED=true
  set +e; router_step_phase_fail "$CURRENT_PHASE" "rc=$rc" 2>/dev/null || true; set -e
  local result=STOP_R20QV_VM100_WG_REMOTE_DEFAULT_CS4
  build_stop_report "$result" "$line" "$rc" "$command"
  publish_stop "$result"
  write_child "$result" "$rc"
  cat "$REPORT/report.txt"
  exit "$rc"
}
trap 'rc=$?; stop_handler "$rc" "$LINENO" "$BASH_COMMAND"' ERR

vm100_script() { ssh -o BatchMode=yes -o ConnectTimeout=15 pve-mgts "ssh -T -i /root/.ssh/pve_to_openwrt_mgts_ed25519 -o BatchMode=yes -o ConnectTimeout=15 root@10.71.100.1 'sh -s'"; }
vm101_script() { ssh -o BatchMode=yes -o ConnectTimeout=15 pve-mgts "ssh -T -i /root/.ssh/pve_to_openwrt_mgts_ed25519 -o BatchMode=yes -o ConnectTimeout=15 root@10.71.100.2 'sh -s'"; }
expand_apply() { python3 - "$FINAL_ROOT/scripts/apply-vm100.sh" "$PACKAGE" "$WORK/apply-expanded.sh" <<'PYEXPAND'
from pathlib import Path
import base64,sys
src,pkg,out=map(Path,sys.argv[1:])
t=src.read_text(); marker='__ROUTER_TARGET_PACKAGE_B64__'; assert t.count(marker)==1
t=t.replace(marker,base64.b64encode(pkg.read_bytes()).decode())
out.write_text(t); out.chmod(0o700)
PYEXPAND
}
setup_selftest_target() {
  FAKE="$WORK/fake"; FROOT="$FAKE/root"; FBIN="$FAKE/bin"; mkdir -p "$FROOT/etc/nftables.d" "$FROOT/etc/pbr.d" "$FROOT/etc/config" "$FBIN"
  cp "$FINAL_ROOT/tests/fixtures/default_lan_to_vpn.sh" "$FROOT/etc/pbr.d/default_lan_to_vpn.sh"; cp "$FINAL_ROOT/tests/fixtures/pbr.config" "$FROOT/etc/config/pbr"
  cat > "$FBIN/ip" <<'EOF_IP'
#!/bin/sh
case "$*" in 'link show dev wg_remote') exit 0;; 'route show table 201') echo 'default dev vpn1'; exit 0;; esac
exit 0
EOF_IP
  cat > "$FBIN/fw4" <<'EOF_FW4'
#!/bin/sh
[ "$1" = check ]
EOF_FW4
  cat > "$FBIN/firewall" <<'EOF_FIREWALL'
#!/bin/sh
[ "$1" = reload ] || exit 2
exit 0
EOF_FIREWALL
  cat > "$FBIN/nft" <<'EOF_NFT'
#!/bin/sh
root="${ROUTER_TARGET_ROOT:?}"
if [ "$1 $2 $3 $4 $5 $6" = 'list chain inet fw4 wg_remote_default_selector ' ] || [ "$1 $2 $3 $4 $5" = 'list chain inet fw4 wg_remote_default_selector' ]; then
  [ -f "$root/etc/nftables.d/91-wg-remote-default-selector.nft" ] || exit 1
  echo 'chain wg_remote_default_selector {'
  echo ' type filter hook prerouting priority mangle; policy accept;'
  echo ' iifname "wg_remote" ip dscp set cs4 counter packets 0 bytes 0 comment "WG_REMOTE_DEFAULT_SELECTOR_CS4"'
  echo '}'; exit 0
fi
if [ "$1 $2" = 'list ruleset' ]; then echo 'table inet fw4 { }'; exit 0; fi
exit 1
EOF_NFT
  cat > "$FBIN/git100" <<'EOF_G100'
#!/bin/sh
cat <<EOF
RESULT=PASS_VM100_GIT_PUBLISH_CHECK
GIT_COMMIT=2973959bbb942b23cf7fe3d68474111aa063d4f2
GIT_TREE=4b35b85779e10d5b1465b44060aef11a9c99c4aa
GIT_FILE_COUNT=29
EOF
EOF_G100
  chmod 700 "$FBIN"/*
}

CURRENT_PHASE=preflight; router_step_phase_begin preflight
if [[ "$SELFTEST" == true ]]; then
  setup_selftest_target
  ROUTER_TARGET_ROOT="$FROOT" ROUTER_IP_BIN="$FBIN/ip" ROUTER_NFT_BIN="$FBIN/nft" ROUTER_FW4_BIN="$FBIN/fw4" ROUTER_GIT_CHECK_BIN="$FBIN/git100" sh "$FINAL_ROOT/scripts/preflight-vm100.sh" > "$EVIDENCE/vm100-preflight.log"
  cat > "$EVIDENCE/vm101-preflight.log" <<'EOF_VM101'
RESULT=PASS_R20QV_VM101_READONLY_PREFLIGHT
HEALTHY_SLOTS=5
CS4_CANONICAL_TARGET=egress1
CS4_CANONICAL_INTERFACE=vpn1
CS4_CANONICAL_MARK=0x201
CS4_CANONICAL_TABLE=201
EOF_VM101
  if [[ -f "$INCOMING/selftest-force-stop" ]]; then false; fi
else
  vm100_script < "$FINAL_ROOT/scripts/preflight-vm100.sh" > "$EVIDENCE/vm100-preflight.log"
  vm101_script < "$FINAL_ROOT/scripts/preflight-vm101.sh" > "$EVIDENCE/vm101-preflight.log"
fi
grep -Fqx RESULT=PASS_R20QV_VM100_PREFLIGHT "$EVIDENCE/vm100-preflight.log"
grep -Fqx RESULT=PASS_R20QV_VM101_READONLY_PREFLIGHT "$EVIDENCE/vm101-preflight.log"
router_step_phase_complete preflight exact_baselines_and_runtime_prerequisites_verified

CURRENT_PHASE=backup; router_step_phase_begin backup
BACKUP_DIR="/root/router-step-backups/r20qv-$(date -u +%Y%m%d-%H%M%S)"
[[ "$SELFTEST" == true ]] && BACKUP_DIR="$WORK/fake/backup"
echo "VM100_BACKUP_DIR=$BACKUP_DIR" > "$EVIDENCE/backup.txt"
router_step_phase_complete backup backup_path_reserved_snapshot_skipped_not_required

CURRENT_PHASE=apply; router_step_phase_begin apply
expand_apply
if [[ "$SELFTEST" == true ]]; then
  ROUTER_TARGET_ROOT="$FROOT" ROUTER_FW4_BIN="$FBIN/fw4" ROUTER_FIREWALL_INIT="$FBIN/firewall" ROUTER_NFT_BIN="$FBIN/nft" sh "$WORK/apply-expanded.sh" "$EXPECTED_PACKAGE_SHA" "$BACKUP_DIR" | tee "$APPLY_ENV"
fi
# production nested ssh receives an argument-bound script on stdin.
if [[ "$SELFTEST" != true ]]; then
  { printf 'set -- %q %q
' "$EXPECTED_PACKAGE_SHA" "$BACKUP_DIR"; cat "$WORK/apply-expanded.sh"; } | vm100_script | tee "$APPLY_ENV"
fi
grep -Fqx RESULT=PASS_R20QV_VM100_APPLY "$APPLY_ENV"
grep -Fqx CORE_CHANGE_COMPLETE=true "$APPLY_ENV"; CORE_CHANGE_COMPLETE=true
ROLLBACK_STATUS=not_run_apply_verified
router_step_phase_complete apply target_file_firewall_reload_and_runtime_chain_verified

CURRENT_PHASE=postcheck; router_step_phase_begin postcheck
if [[ "$SELFTEST" == true ]]; then
  ROUTER_TARGET_ROOT="$FROOT" ROUTER_NFT_BIN="$FBIN/nft" ROUTER_FW4_BIN="$FBIN/fw4" sh "$FINAL_ROOT/scripts/verify-vm100.sh" "$EXPECTED_FILE_SHA" > "$EVIDENCE/vm100-postcheck.log"
else
  { printf 'set -- %q
' "$EXPECTED_FILE_SHA"; cat "$FINAL_ROOT/scripts/verify-vm100.sh"; } | vm100_script > "$EVIDENCE/vm100-postcheck.log"
  vm101_script < "$FINAL_ROOT/scripts/preflight-vm101.sh" > "$EVIDENCE/vm101-postcheck.log"
fi
grep -Fqx RESULT=PASS_R20QV_VM100_POSTCHECK "$EVIDENCE/vm100-postcheck.log"
router_step_phase_complete postcheck persistent_file_and_runtime_rule_verified

CURRENT_PHASE=source; router_step_phase_begin source
if [[ "$SELFTEST" == true ]]; then
  cat > "$SOURCE_ENV" <<'EOF_SOURCE'
RESULT=PASS_ROUTER_MACHINE_CLOSE
COMMIT=1111111111111111111111111111111111111111
TREE=2222222222222222222222222222222222222222
GIT_FILE_COUNT=30
VM100_PUBLIC_URL=selftest://vm100-source/
EOF_SOURCE
else
  "$BASE/bin/router-machine-close-safe" vm100 "$STEP: persistent wg_remote default selector cs4" | tee "$SOURCE_ENV"
fi
grep -Eq '^RESULT=PASS_ROUTER_MACHINE_CLOSE$' "$SOURCE_ENV"
VM100_PUBLIC_URL="$(awk -F= '$1=="VM100_PUBLIC_URL"{print substr($0,index($0,"=")+1)}' "$SOURCE_ENV" | tail -n1)"
VM100_COMMIT="$(awk -F= '$1=="COMMIT"{print $2}' "$SOURCE_ENV" | tail -n1)"
VM100_TREE="$(awk -F= '$1=="TREE"{print $2}' "$SOURCE_ENV" | tail -n1)"
VM100_COUNT="$(awk -F= '$1=="GIT_FILE_COUNT"{print $2}' "$SOURCE_ENV" | tail -n1)"
[[ -n "$VM100_PUBLIC_URL" && "$VM100_COMMIT" =~ ^[0-9a-f]{40}$ && "$VM100_TREE" =~ ^[0-9a-f]{40}$ && "$VM100_COUNT" =~ ^[0-9]+$ ]]
router_step_phase_complete source vm100_machine_git_committed_pushed_and_published

CURRENT_PHASE=project_source; router_step_phase_begin project_source
PS="$WORK/project-source"; mkdir -p "$PS"
cat > "$PS/WG_PAID_MGTS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4.md" <<EOF_PS
# WG Paid / MGTS — R20QV VM100 wg_remote default selector cs4

Result: PASS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4.

VM100 now contains /etc/nftables.d/91-wg-remote-default-selector.nft. Every IPv4 packet entering through wg_remote receives DSCP cs4. Existing VM100 PBR remains authoritative: internal 10.71.100.0/24 is ignored by PBR, RU destinations remain Direct, and other destinations selected for transit_vpn reach VM101 with cs4. VM101 was read-only and retains canonical mapping cs4 → egress1 → vpn1 → mark 0x201 → table 201 while the normal five-slot state is active.

No per-peer selector UI, topology/fallback change, Direct-helper change, WireGuard peer change, VM101 mutation, reboot or snapshot was performed.

VM100 commit: $VM100_COMMIT  
VM100 tree: $VM100_TREE  
VM100 managed file count: $VM100_COUNT  
VM100 source: $VM100_PUBLIC_URL
EOF_PS
if [[ "$SELFTEST" == true ]]; then
  PROJECT_SOURCE_POST_URL="$(selftest_publish_dir r20qv-project-source "$PS")"
else
  "$BASE/bin/router-publish-report" project_source_post_r20qv_vm100_wg_remote_default_cs4 "$PS/WG_PAID_MGTS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4.md" | tee "$PROJECT_ENV"
  PROJECT_SOURCE_POST_URL="$(awk -F= '$1=="PROJECT_SOURCE_POST_URL"{print substr($0,index($0,"=")+1)}' "$PROJECT_ENV" | tail -n1)"
fi
[[ -n "$PROJECT_SOURCE_POST_URL" ]]
router_step_phase_complete project_source implementation_checkpoint_published

CURRENT_PHASE=report_build; router_step_phase_begin report_build
cat > "$REPORT/report.txt" <<EOF_REPORT
RESULT=PASS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4
REVISION=R01
WORKFLOW_CONTRACT_VERSION=2
CORE_CHANGE_COMPLETE=true
RUNTIME_IMPACT=true
SNAPSHOT_CREATED=false
SNAPSHOT_REASON=not_required_single_file_transactional_reload
ROLLBACK_STATUS=$ROLLBACK_STATUS
VM100_GIT_COMMIT=$VM100_COMMIT
VM100_GIT_TREE=$VM100_TREE
VM100_GIT_FILE_COUNT=$VM100_COUNT
VM100_PUBLIC_URL=$VM100_PUBLIC_URL
VM101_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260801-095331_vm101_git_source_ac678d1b3164/
VM121_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260713-182700_vm121_git_source_93e0e5a31f09/
PROJECT_SOURCE_POST_URL=$PROJECT_SOURCE_POST_URL
LOCAL_PLAN_URL=https://reports.secret-studio.ru/latest/20260719-080444_local_architecture_plan_vm101_autonomous_hmn_recovery_r19/
GLOBAL_PLAN_URL=https://reports.secret-studio.ru/latest/20260711-123348_global_project_plan_wg_paid/
ACCESS_MAP_URL=https://reports.secret-studio.ru/latest/20260715-210236_access_map_canonical_current/
XS_MAP_URL=https://reports.secret-studio.ru/latest/20260711-123348_global_project_plan_wg_paid/sources/xs-map-report.txt
VM100_TARGET_FILE=/etc/nftables.d/91-wg-remote-default-selector.nft
VM100_TARGET_FILE_SHA256=$EXPECTED_FILE_SHA
WG_REMOTE_DEFAULT_SELECTOR=cs4
VM101_CS4_CANONICAL_PATH=egress1:vpn1:0x201:table201
PER_PEER_SELECTOR_IMPLEMENTED=false
VM101_MUTATED=false
VM121_MUTATED=false
REBOOT_EXECUTED=false
ZERO_HEALTHY_TEST_REPEATED=false
EOF_REPORT
python3 - "$REPORT/facts.json" "$VM100_COMMIT" "$VM100_TREE" "$VM100_COUNT" "$VM100_PUBLIC_URL" "$PROJECT_SOURCE_POST_URL" <<'PYFACTS'
import json,sys
p,c,t,n,u,ps=sys.argv[1:]
json.dump({"result":"PASS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4","core_change_complete":True,"runtime_impact":True,"vm100":{"commit":c,"tree":t,"file_count":int(n),"url":u},"project_source_post_url":ps,"selector":"cs4","interface":"wg_remote"},open(p,"w"),indent=2); open(p,"a").write("\n")
PYFACTS
printf 'RESULT=PASS_R20QV_VERIFY
TARGET_FILE_SHA256=%s
RUNTIME_CHAIN=wg_remote_default_selector
SELECTOR=cs4
' "$EXPECTED_FILE_SHA" > "$REPORT/verify.txt"
printf 'Automatic rollback was not run because apply and immediate runtime verification completed. Retained backup: %s
' "$BACKUP_DIR" > "$REPORT/rollback.txt"
cp "$FINAL_ROOT/scripts/run-step.sh" "$REPORT/executed-step.sh"; cp "$FINAL_ROOT/release-info.json" "$REPORT/release-info.json"; cp "$FINAL_ROOT/manifest.sha256" "$REPORT/bundle-manifest.sha256"; cp "$CURRENT" "$REPORT/workflow-current.txt"; cp "$EVENTS" "$REPORT/workflow-events.tsv"; cp "$FINAL_ROOT/payload/rootfs/etc/nftables.d/91-wg-remote-default-selector.nft" "$REPORT/91-wg-remote-default-selector.nft"; cp "$EVIDENCE"/*.log "$REPORT/" 2>/dev/null || true; cp "$SOURCE_ENV" "$REPORT/vm100-source.txt"
router_step_phase_complete report_build complete_step_payload_built

CURRENT_PHASE=report_publish; router_step_phase_begin report_publish
if [[ "$SELFTEST" == true ]]; then
  STEP_REPORT_URL="$(selftest_publish_dir r20qv-r01-pass "$REPORT")"
else
  router_step_publish_payload step050m07r20qv_r01_vm100_wg_remote_default_cs4 "$REPORT" "$PAYLOAD" "$CURRENT" "$EVENTS" "$FINAL_ENV"     "$REPORT/report.txt" report.txt "$REPORT/facts.json" facts.json "$REPORT/verify.txt" verify.txt "$REPORT/rollback.txt" rollback.txt "$REPORT/executed-step.sh" executed-step.sh "$REPORT/release-info.json" release-info.json "$REPORT/bundle-manifest.sha256" bundle-manifest.sha256 "$REPORT/workflow-current.txt" workflow-current.txt "$REPORT/workflow-events.tsv" workflow-events.tsv "$REPORT/91-wg-remote-default-selector.nft" 91-wg-remote-default-selector.nft "$REPORT/vm100-source.txt" vm100-source.txt
  STEP_REPORT_URL="$(awk -F= '$1=="PUBLIC_URL"{print substr($0,index($0,"=")+1)}' "$FINAL_ENV" | tail -n1)"
fi
router_step_phase_complete report_publish step_report_published_and_verified

CURRENT_PHASE=archive; router_step_phase_begin archive
archive_latest
[[ -f "$LATEST_ARCHIVE" ]]
router_step_phase_complete archive one_final_latest_archive_created
router_step_workflow_mark_complete
trap - ERR
write_child PASS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4 0
cat "$REPORT/report.txt"
echo STEP_REPORT_URL="$STEP_REPORT_URL"
echo LATEST_ARCHIVE="$LATEST_ARCHIVE"
echo LATEST_ARCHIVE_SHA256="$LATEST_ARCHIVE_SHA256"
