#!/usr/bin/env bash
set -u
umask 077

STEP="STEP_050M07R08_VM101_READ_ONLY_INSTALL_PREFLIGHT"

TOKEN="e94a0859747d7b96f29c7fdafc2d0351ba603bb0a7e9e5a4"
ROOT="/opt/router-ops"
STATE="$ROOT/state"
PUBROOT="$ROOT/public/r/$TOKEN"
PUBLIC_BASE="https://helena-background-beam-harry.trycloudflare.com/r/$TOKEN"

WORKFLOW_STATE="$STATE/current-m07-workflow.env"

TS="$(date -u +%Y%m%d-%H%M%S)"
REPORT_SLUG="${TS}_step050m07r08_vm101_read_only_install_preflight"
REPORT_DIR="$PUBROOT/$REPORT_SLUG"

TRYCF_REPORT="$PUBLIC_BASE/$REPORT_SLUG/"
REPORT_TXT="${TRYCF_REPORT}report.txt"
FACTS_JSON="${TRYCF_REPORT}facts.json"

VM101_REFERENCE="$PUBLIC_BASE/vm101-reference/current/"
VM101_CANONICAL="$PUBLIC_BASE/vm101-canonical/current/"

REMOTE_SCRIPT="/tmp/r08-vm101-read-only-preflight.sh"
REMOTE_RAW="$REPORT_DIR/vm101-preflight.env"
SSH_STDERR="$REPORT_DIR/ssh.stderr"

BUNDLE="UNRESOLVED"
ARCHIVE="UNRESOLVED"

EXPECTED_SOURCE_SHA="UNRESOLVED"
EXPECTED_CANDIDATE_SHA="UNRESOLVED"
EXPECTED_RUNTIME_SHA="UNRESOLVED"
EXPECTED_ARCHIVE_SHA="UNRESOLVED"

LOCAL_BUNDLE_OK=false
SSH_TRANSPORT_OK=false
REMOTE_PREFLIGHT_OK=false
WORKFLOW_STATE_UPDATED=false
ALL_OK=false

SSH_RC=255
FAILURE_REASON="NONE"

mkdir -p "$REPORT_DIR"

cp -a "$0" "$REPORT_DIR/step.sh"
chmod 600 "$REPORT_DIR/step.sh"

value() {
  local file="$1"
  local key="$2"

  sed -n "s/^${key}=//p" "$file" |
    tail -n1
}

fail_local() {
  FAILURE_REASON="$1"
  return 1
}

if [ "$(id -un)" != "ops" ]; then
  fail_local "WRONG_USER"
elif [ ! -s "$WORKFLOW_STATE" ]; then
  fail_local "WORKFLOW_STATE_MISSING"
else
  BUNDLE="$(
    value \
      "$WORKFLOW_STATE" \
      M07_REFRESH_POOL_SAFE_BUNDLE
  )"

  ARCHIVE="$(
    value \
      "$WORKFLOW_STATE" \
      M07_REFRESH_POOL_SAFE_ARCHIVE
  )"

  EXPECTED_SOURCE_SHA="$(
    value \
      "$WORKFLOW_STATE" \
      M07_REFRESH_POOL_SAFE_SOURCE_SHA256
  )"

  EXPECTED_CANDIDATE_SHA="$(
    value \
      "$WORKFLOW_STATE" \
      M07_REFRESH_POOL_SAFE_CANDIDATE_SHA256
  )"

  EXPECTED_ARCHIVE_SHA="$(
    value \
      "$WORKFLOW_STATE" \
      M07_REFRESH_POOL_SAFE_ARCHIVE_SHA256
  )"

  STATUS="$(
    value \
      "$WORKFLOW_STATE" \
      M07_REFRESH_POOL_SAFE_STATUS
  )"

  if [ "$STATUS" != "INSTALL_BUNDLE_READY_NOT_INSTALLED" ]; then
    fail_local "INSTALL_BUNDLE_STATUS_INVALID"

  elif [ ! -d "$BUNDLE" ]; then
    fail_local "INSTALL_BUNDLE_MISSING"

  elif [ ! -s "$ARCHIVE" ]; then
    fail_local "INSTALL_ARCHIVE_MISSING"

  elif [ ! -s "$BUNDLE/manifest.tsv" ] ||
       [ ! -s "$BUNDLE/SHA256SUMS" ] ||
       [ ! -s "$BUNDLE/install.sh" ] ||
       [ ! -s "$BUNDLE/verify.sh" ] ||
       [ ! -s "$BUNDLE/rollback.sh" ]; then
    fail_local "INSTALL_BUNDLE_INCOMPLETE"

  else
    EXPECTED_RUNTIME_SHA="$(
      awk -F '\t' \
        '$1 == "/usr/local/lib/router-egress-vm101-runtime.sh" {
          print $3
        }' \
        "$BUNDLE/manifest.tsv"
    )"

    PAYLOAD_TARGET_SHA="$(
      sha256sum \
        "$BUNDLE/payload/root/hmn/hmn-refresh-pool-safe.sh" |
      cut -d' ' -f1
    )"

    PAYLOAD_RUNTIME_SHA="$(
      sha256sum \
        "$BUNDLE/payload/usr/local/lib/router-egress-vm101-runtime.sh" |
      cut -d' ' -f1
    )"

    ACTUAL_ARCHIVE_SHA="$(
      sha256sum "$ARCHIVE" |
      cut -d' ' -f1
    )"

    if [ "$PAYLOAD_TARGET_SHA" != "$EXPECTED_CANDIDATE_SHA" ]; then
      fail_local "BUNDLE_TARGET_HASH_MISMATCH"

    elif [ "$PAYLOAD_RUNTIME_SHA" != "$EXPECTED_RUNTIME_SHA" ]; then
      fail_local "BUNDLE_RUNTIME_HASH_MISMATCH"

    elif [ "$ACTUAL_ARCHIVE_SHA" != "$EXPECTED_ARCHIVE_SHA" ]; then
      fail_local "BUNDLE_ARCHIVE_HASH_MISMATCH"

    elif ! (
      cd "$BUNDLE" &&
      sha256sum -c SHA256SUMS
    ) > "$REPORT_DIR/bundle-sha256-check.txt" 2>&1; then
      fail_local "BUNDLE_SHA256_VALIDATION_FAILED"

    elif ! sh -n "$BUNDLE/install.sh"; then
      fail_local "INSTALL_SCRIPT_SYNTAX_FAILED"

    elif ! sh -n "$BUNDLE/verify.sh"; then
      fail_local "VERIFY_SCRIPT_SYNTAX_FAILED"

    elif ! sh -n "$BUNDLE/rollback.sh"; then
      fail_local "ROLLBACK_SCRIPT_SYNTAX_FAILED"

    else
      LOCAL_BUNDLE_OK=true
    fi
  fi
fi

cat > "$REMOTE_SCRIPT" <<'REMOTE'
#!/bin/sh
set -u

EXPECTED_TARGET="$1"
EXPECTED_RUNTIME="$2"

TARGET="/root/hmn/hmn-refresh-pool-safe.sh"
LIB="/usr/local/lib/router-egress-vm101-runtime.sh"

overall=true

bool_fail() {
  overall=false
}

hash_file() {
  if command -v sha256sum >/dev/null 2>&1
  then
    sha256sum "$1" |
      awk '{print $1}'
  else
    busybox sha256sum "$1" |
      awk '{print $1}'
  fi
}

numeric_or_zero() {
  case "$1" in
    ''|*[!0-9]*)
      printf '%s\n' 0
      ;;
    *)
      printf '%s\n' "$1"
      ;;
  esac
}

echo "host=$(hostname)"
echo "user=$(id -un)"
echo "openwrt_release=$(
  . /etc/openwrt_release 2>/dev/null
  printf '%s' "${DISTRIB_RELEASE:-UNKNOWN}"
)"
echo "kernel=$(uname -r)"
echo "architecture=$(uname -m)"

if [ -s "$TARGET" ]
then
  target_present=true
  target_hash="$(hash_file "$TARGET" 2>/dev/null || true)"
else
  target_present=false
  target_hash="MISSING"
fi

echo "target_present=$target_present"
echo "target_sha256=$target_hash"

if [ "$target_present" = "true" ] &&
   [ "$target_hash" = "$EXPECTED_TARGET" ]
then
  target_hash_match=true
else
  target_hash_match=false
  bool_fail
fi

echo "target_hash_match=$target_hash_match"

if [ "$target_present" = "true" ] &&
   sh -n "$TARGET"
then
  target_syntax_ok=true
else
  target_syntax_ok=false
  bool_fail
fi

echo "target_syntax_ok=$target_syntax_ok"

if [ -e "$LIB" ]
then
  runtime_library_present=true
  runtime_hash="$(hash_file "$LIB" 2>/dev/null || true)"

  if [ "$runtime_hash" = "$EXPECTED_RUNTIME" ]
  then
    runtime_library_state="EXACT"
  else
    runtime_library_state="MISMATCH"
    bool_fail
  fi
else
  runtime_library_present=false
  runtime_hash="ABSENT"
  runtime_library_state="ABSENT"
fi

echo "runtime_library_present=$runtime_library_present"
echo "runtime_library_sha256=$runtime_hash"
echo "runtime_library_state=$runtime_library_state"

root_free_kb="$(
  df -k / 2>/dev/null |
    awk 'NR == 2 {print $4}'
)"

tmp_free_kb="$(
  df -k /tmp 2>/dev/null |
    awk 'NR == 2 {print $4}'
)"

root_free_kb="$(numeric_or_zero "$root_free_kb")"
tmp_free_kb="$(numeric_or_zero "$tmp_free_kb")"

echo "root_free_kb=$root_free_kb"
echo "tmp_free_kb=$tmp_free_kb"

if [ "$root_free_kb" -ge 4096 ]
then
  root_free_ok=true
else
  root_free_ok=false
  bool_fail
fi

if [ "$tmp_free_kb" -ge 32768 ]
then
  tmp_free_ok=true
else
  tmp_free_ok=false
  bool_fail
fi

echo "root_free_ok=$root_free_ok"
echo "tmp_free_ok=$tmp_free_ok"

if [ -x /usr/bin/amneziawg ]
then
  runtime_cli_ok=true
else
  runtime_cli_ok=false
  bool_fail
fi

echo "runtime_cli=/usr/bin/amneziawg"
echo "runtime_cli_ok=$runtime_cli_ok"

if ps w 2>/dev/null |
   grep -E \
     '[h]mn-refresh-pool-safe|[r]outer-egress-emergency-refresh|[r]ebalance-top5-apply' \
     >/dev/null
then
  conflicting_process=false
  bool_fail
else
  conflicting_process=false
fi

echo "conflicting_process=$conflicting_process"

healthy_count=0
route_count=0
interface_count=0

slot=1

while [ "$slot" -le 5 ]
do
  iface="vpn$slot"
  table="$((200 + slot))"

  if ip link show dev "$iface" >/dev/null 2>&1
  then
    iface_present=true
    interface_count="$((interface_count + 1))"
  else
    iface_present=false
    bool_fail
  fi

  endpoint="$(
    /usr/bin/amneziawg show "$iface" dump 2>/dev/null |
      awk 'NR == 2 {print $3; exit}'
  )"

  [ -n "$endpoint" ] ||
    endpoint="NONE"

  if ip route show table "$table" 2>/dev/null |
     grep -E "^default .*dev ${iface}([[:space:]]|$)" \
     >/dev/null
  then
    route_ok=true
    route_count="$((route_count + 1))"
  else
    route_ok=false
    bool_fail
  fi

  if [ "$iface_present" = "true" ] &&
     ping -I "$iface" -c 1 -W 2 1.1.1.1 \
       >/dev/null 2>&1
  then
    strict_ping_ok=true
    healthy_count="$((healthy_count + 1))"
  else
    strict_ping_ok=false
    bool_fail
  fi

  echo "${iface}_present=$iface_present"
  echo "${iface}_endpoint=$endpoint"
  echo "${iface}_table=$table"
  echo "${iface}_route_ok=$route_ok"
  echo "${iface}_strict_ping_ok=$strict_ping_ok"

  slot="$((slot + 1))"
done

echo "interface_count=$interface_count"
echo "route_count=$route_count"
echo "strict_healthy_count=$healthy_count"

if [ "$interface_count" -ne 5 ] ||
   [ "$route_count" -ne 5 ] ||
   [ "$healthy_count" -ne 5 ]
then
  overall=false
fi

echo "overall_pass=$overall"
echo "vm101_modified=false"
echo "files_written=false"
echo "refresh_ran=false"
echo "rebalance_ran=false"

exit 0
REMOTE

chmod 600 "$REMOTE_SCRIPT"

if [ "$LOCAL_BUNDLE_OK" = "true" ]; then
  ssh -T \
    -o BatchMode=yes \
    -o ConnectTimeout=10 \
    pve-mgts \
    "ssh -T \
      -o BatchMode=yes \
      -o ConnectTimeout=10 \
      -o StrictHostKeyChecking=yes \
      -i /root/.ssh/pve_to_openwrt_mgts_ed25519 \
      root@10.71.100.2 \
      'sh -s -- $EXPECTED_SOURCE_SHA $EXPECTED_RUNTIME_SHA'" \
    < "$REMOTE_SCRIPT" \
    > "$REMOTE_RAW" \
    2> "$SSH_STDERR"

  SSH_RC=$?

  if [ "$SSH_RC" -eq 0 ]; then
    SSH_TRANSPORT_OK=true

    REMOTE_OVERALL="$(
      sed -n 's/^overall_pass=//p' "$REMOTE_RAW" |
      tail -n1
    )"

    if [ "$REMOTE_OVERALL" = "true" ]; then
      REMOTE_PREFLIGHT_OK=true
    else
      FAILURE_REASON="REMOTE_PREFLIGHT_FAILED"
    fi
  else
    FAILURE_REASON="SSH_TRANSPORT_FAILED"
  fi
fi

if [ "$LOCAL_BUNDLE_OK" != "true" ] &&
   [ "$FAILURE_REASON" = "NONE" ]
then
  FAILURE_REASON="LOCAL_BUNDLE_VALIDATION_FAILED"
fi

if [ "$LOCAL_BUNDLE_OK" = "true" ] &&
   [ "$SSH_TRANSPORT_OK" = "true" ] &&
   [ "$REMOTE_PREFLIGHT_OK" = "true" ]
then
  ALL_OK=true
  DECISION="PASS_${STEP}"
  EXECUTION="PASS"
  OPERATION="VM101_READ_ONLY_INSTALL_PREFLIGHT_PASSED"
else
  ALL_OK=false
  DECISION="STOP_${STEP}_${FAILURE_REASON}"
  EXECUTION="STOP"
  OPERATION="VM101_INSTALL_PREFLIGHT_NOT_READY"
fi

python3 - \
  "$REPORT_DIR/facts.json" \
  "$REMOTE_RAW" \
  "$STEP" \
  "$DECISION" \
  "$EXECUTION" \
  "$OPERATION" \
  "$FAILURE_REASON" \
  "$BUNDLE" \
  "$ARCHIVE" \
  "$EXPECTED_SOURCE_SHA" \
  "$EXPECTED_CANDIDATE_SHA" \
  "$EXPECTED_RUNTIME_SHA" \
  "$EXPECTED_ARCHIVE_SHA" \
  "$LOCAL_BUNDLE_OK" \
  "$SSH_TRANSPORT_OK" \
  "$REMOTE_PREFLIGHT_OK" \
  "$SSH_RC" \
  "$ALL_OK" \
  "$TRYCF_REPORT" \
  "$REPORT_TXT" \
  "$FACTS_JSON" <<'PY'
import json
import sys
from pathlib import Path

(
    output,
    remote_path,
    step,
    decision,
    execution,
    operation,
    failure_reason,
    bundle,
    archive,
    source_sha,
    candidate_sha,
    runtime_sha,
    archive_sha,
    local_ok,
    ssh_ok,
    remote_ok,
    ssh_rc,
    all_ok,
    report,
    report_txt,
    facts,
) = sys.argv[1:]

remote = {}

path = Path(remote_path)

if path.is_file():
    for line in path.read_text(
        encoding="utf-8",
        errors="replace",
    ).splitlines():
        if "=" in line:
            key, value = line.split("=", 1)
            remote[key] = value

for key, value in list(remote.items()):
    if value == "true":
        remote[key] = True
    elif value == "false":
        remote[key] = False
    elif value.isdigit():
        remote[key] = int(value)

data = {
    "schema": "router-step-facts-v1",
    "step": step,
    "assessment": {
        "decision": decision,
        "step_execution": execution,
        "operation_result": operation,
        "production_health": (
            "HEALTHY_5_OF_5"
            if remote.get("strict_healthy_count") == 5
            else "NOT_CONFIRMED_5_OF_5"
        ),
        "milestone_status": "M07_IN_PROGRESS",
        "all_ok": all_ok == "true",
        "failure_reason": (
            None
            if execution == "PASS"
            else failure_reason
        ),
    },
    "bundle": {
        "path": bundle,
        "archive": archive,
        "expected_live_source_sha256":
            source_sha,
        "candidate_sha256":
            candidate_sha,
        "runtime_library_sha256":
            runtime_sha,
        "archive_sha256":
            archive_sha,
        "local_validation_passed":
            local_ok == "true",
    },
    "transport": {
        "path":
            "router-ops -> pve-mgts -> VM101",
        "ssh_rc": int(ssh_rc),
        "ssh_transport_ok":
            ssh_ok == "true",
    },
    "vm101_preflight": remote,
    "safety": {
        "read_only": True,
        "vm101_contacted":
            ssh_ok == "true",
        "vm101_modified": False,
        "files_written_on_vm101": False,
        "canonical_current_modified": False,
        "installation_performed": False,
        "refresh_ran": False,
        "rebalance_ran": False,
    },
    "next_step": (
        "INSTALL_REFRESH_POOL_SAFE_WITH_AUTOMATIC_ROLLBACK"
        if all_ok == "true"
        else "REPAIR_VM101_INSTALL_PREFLIGHT"
    ),
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts,
    },
}

Path(output).write_text(
    json.dumps(
        data,
        ensure_ascii=False,
        indent=2,
    ) + "\n",
    encoding="utf-8",
)
PY

cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=${DECISION}
step_execution=${EXECUTION}
operation_result=${OPERATION}
milestone_status=M07_IN_PROGRESS
all_ok=${ALL_OK}
failure_reason=${FAILURE_REASON}

bundle:
  path=${BUNDLE}
  archive=${ARCHIVE}
  expected_live_source_sha256=${EXPECTED_SOURCE_SHA}
  candidate_sha256=${EXPECTED_CANDIDATE_SHA}
  runtime_library_sha256=${EXPECTED_RUNTIME_SHA}
  archive_sha256=${EXPECTED_ARCHIVE_SHA}
  local_validation_passed=${LOCAL_BUNDLE_OK}

transport:
  path=router-ops -> pve-mgts -> VM101
  ssh_rc=${SSH_RC}
  ssh_transport_ok=${SSH_TRANSPORT_OK}

vm101:
$(sed 's/^/  /' "$REMOTE_RAW" 2>/dev/null || true)

safety:
  read_only=true
  vm101_modified=false
  files_written_on_vm101=false
  canonical_current_modified=false
  installation_performed=false
  refresh_ran=false
  rebalance_ran=false

next_step=$(
  if [ "$ALL_OK" = "true" ]; then
    echo "INSTALL_REFRESH_POOL_SAFE_WITH_AUTOMATIC_ROLLBACK"
  else
    echo "REPAIR_VM101_INSTALL_PREFLIGHT"
  fi
)

TRYCF_REPORT=${TRYCF_REPORT}
REPORT_TXT=${REPORT_TXT}
FACTS_JSON=${FACTS_JSON}
VM101_REFERENCE=${VM101_REFERENCE}
VM101_CANONICAL=${VM101_CANONICAL}
EOF

cat > "$REPORT_DIR/index.html" <<EOF
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>${STEP}</title>
</head>
<body style="font-family:system-ui;max-width:1000px;margin:40px auto">
<h1>${STEP}</h1>
<ul>
<li><a href="report.txt">report.txt</a></li>
<li><a href="facts.json">facts.json</a></li>
<li><a href="vm101-preflight.env">vm101-preflight.env</a></li>
<li><a href="ssh.stderr">ssh.stderr</a></li>
<li><a href="bundle-sha256-check.txt">bundle-sha256-check.txt</a></li>
<li><a href="step.sh">step.sh</a></li>
</ul>
</body>
</html>
EOF

find "$REPORT_DIR" \
  -type f \
  ! -name SHA256SUMS \
  -print0 |
  sort -z |
  xargs -0 sha256sum \
  > "$REPORT_DIR/SHA256SUMS"

chmod -R a+rX "$REPORT_DIR"

if [ "$ALL_OK" = "true" ]; then
  python3 - \
    "$WORKFLOW_STATE" \
    "$TRYCF_REPORT" <<'PY'
import sys
from pathlib import Path

path = Path(sys.argv[1])
report = sys.argv[2]

lines = path.read_text(
    encoding="utf-8",
    errors="replace",
).splitlines()

updates = {
    "M07_REFRESH_POOL_SAFE_STATUS":
        "VM101_READ_ONLY_PREFLIGHT_PASSED",
    "M07_REFRESH_POOL_SAFE_PREFLIGHT_REPORT":
        report,
    "M07_NEXT_STEP":
        "INSTALL_REFRESH_POOL_SAFE_WITH_AUTOMATIC_ROLLBACK",
}

result = []
seen = set()

for line in lines:
    if "=" not in line:
        result.append(line)
        continue

    key, _ = line.split("=", 1)

    if key in updates:
        result.append(f"{key}={updates[key]}")
        seen.add(key)
    else:
        result.append(line)

for key, value in updates.items():
    if key not in seen:
        result.append(f"{key}={value}")

path.write_text(
    "\n".join(result) + "\n",
    encoding="utf-8",
)
PY

  chmod 600 "$WORKFLOW_STATE"
  WORKFLOW_STATE_UPDATED=true
fi

echo
echo "decision=$DECISION"
echo "operation_result=$OPERATION"
echo "local_bundle_ok=$LOCAL_BUNDLE_OK"
echo "ssh_transport_ok=$SSH_TRANSPORT_OK"
echo "remote_preflight_ok=$REMOTE_PREFLIGHT_OK"
echo "workflow_state_updated=$WORKFLOW_STATE_UPDATED"
echo "vm101_modified=false"
echo "installation_performed=false"

echo
echo "TRYCF_REPORT=$TRYCF_REPORT"
echo "REPORT_TXT=$REPORT_TXT"
echo "FACTS_JSON=$FACTS_JSON"
echo "VM101_REFERENCE=$VM101_REFERENCE"
echo "VM101_CANONICAL=$VM101_CANONICAL"

exit 0
