#!/bin/sh
set -eu
umask 077

CONF="/etc/router-egress-emergency-refresh.conf"
RUNNER="/usr/local/sbin/router-egress-emergency-refresh.sh"
HOOK="/usr/local/sbin/router-egress-emergency-decision-hook.sh"
HELPER="/usr/local/lib/router-egress-recovery-state.sh"
POOL="/root/hmn/cache/ok-awg1-strict-foreign-latest.tsv"

HOOK_INIT="/etc/init.d/router-egress-emergency-decision"
WATCHER_INIT="/etc/init.d/router-egress-health-repair"
CRON="/etc/crontabs/root"

PATCHED=false
ROLLBACK=""

trace() {
  printf '__TRACE__ stage=%s\n' "$1"
}

fact() {
  printf '__FACT__ %s=%s\n' "$1" "$2"
}

json_block() {
  echo "__JSON_BEGIN__ $1"
  printf '%s\n' "$2"
  echo "__JSON_END__ $1"
}

bool_cmd() {
  if "$@" >/dev/null 2>&1; then
    printf true
  else
    printf false
  fi
}

commit_value() {
  (
    unset EMERGENCY_COMMIT_ENABLED
    . "$CONF"

    case "${EMERGENCY_COMMIT_ENABLED:-0}" in
      1|true|TRUE|yes|YES|on|ON)
        printf true
        ;;
      *)
        printf false
        ;;
    esac
  )
}

repair_counter() {
  (
    unset REG_STATE_DIR
    . "$HELPER"
    reg_daily_repair_get
  )
}

endpoint_value() {
  interface="$1"

  wg show "$interface" endpoints 2>/dev/null |
    awk '
      NF >= 2 {
        print $NF
        exit
      }
    '
}

strict_ping() {
  interface="$1"
  attempt=1

  while [ "$attempt" -le 3 ]; do
    if ping \
      -I "$interface" \
      -c 1 \
      -W 4 \
      1.1.1.1 \
      >/dev/null 2>&1
    then
      return 0
    fi

    attempt=$((attempt + 1))
    sleep 1
  done

  return 1
}

rollback_on_error() {
  rc="$?"

  trap - EXIT

  if [ "$rc" -ne 0 ] &&
     [ "$PATCHED" = true ] &&
     [ -n "$ROLLBACK" ] &&
     [ -x "$ROLLBACK" ]
  then
    if sh "$ROLLBACK"; then
      echo "__AUTO_ROLLBACK__ restored=true"
    else
      echo "__AUTO_ROLLBACK__ restored=false"
    fi
  fi

  exit "$rc"
}

trap rollback_on_error EXIT

trace required_files

for path in \
  "$CONF" \
  "$RUNNER" \
  "$HOOK" \
  "$HELPER" \
  "$POOL" \
  "$HOOK_INIT" \
  "$WATCHER_INIT" \
  "$CRON"
do
  [ -e "$path" ] || {
    echo "__ERROR__ missing=$path"
    exit 21
  }
done

trace command_contract

for command_name in \
  sh \
  awk \
  sed \
  grep \
  cp \
  mv \
  chmod \
  chown \
  sha256sum \
  date \
  uci \
  wg \
  ip \
  ping
do
  command -v "$command_name" >/dev/null 2>&1 || {
    echo "__ERROR__ command_missing=$command_name"
    exit 22
  }
done

if command -v stat >/dev/null 2>&1; then
  echo "__ERROR__ unexpected_stat_dependency"
  exit 23
fi

POOL_MTIME_TEST="$(
  date -r "$POOL" +%s
)"

case "$POOL_MTIME_TEST" in
  ''|*[!0-9]*)
    echo "__ERROR__ date_r_invalid=$POOL_MTIME_TEST"
    exit 24
    ;;
esac

fact stat_command_present false
fact pool_mtime_method date_r

trace scheduler_contract

fact hook_running "$(bool_cmd "$HOOK_INIT" running)"
fact hook_enabled "$(bool_cmd "$HOOK_INIT" enabled)"
fact watcher_running "$(bool_cmd "$WATCHER_INIT" running)"
fact watcher_enabled "$(bool_cmd "$WATCHER_INIT" enabled)"

SCHEDULER_LINES="$(
  grep -Ec \
    'router-egress-emergency-decision-hook\.sh' \
    "$CRON" ||
  true
)"

HOOK_COMMIT_REFS="$(
  grep -Ec -- \
    '--commit|EMERGENCY_COMMIT_ENABLED' \
    "$HOOK" ||
  true
)"

fact emergency_scheduler_lines "$SCHEDULER_LINES"
fact hook_commit_flag_refs "$HOOK_COMMIT_REFS"

trace pre_state

COMMIT_PRE="$(commit_value)"
COUNTER_PRE="$(repair_counter)"
RUNNER_PRE="$("$RUNNER" --dry-run)"
HOOK_PRE="$("$HOOK")"

CONFIG_HASH_PRE="$(
  sha256sum "$CONF" |
    sed 's/[[:space:]].*$//'
)"

RUNNER_HASH_PRE="$(
  sha256sum "$RUNNER" |
    sed 's/[[:space:]].*$//'
)"

HOOK_HASH_PRE="$(
  sha256sum "$HOOK" |
    sed 's/[[:space:]].*$//'
)"

NETWORK_HASH_PRE="$(
  uci export network |
    sha256sum |
    sed 's/[[:space:]].*$//'
)"

POOL_HASH_PRE="$(
  sha256sum "$POOL" |
    sed 's/[[:space:]].*$//'
)"

POOL_MTIME_PRE="$(
  date -r "$POOL" +%s
)"

fact commit_enabled_pre "$COMMIT_PRE"
fact repair_counter_pre "$COUNTER_PRE"

json_block runner_pre "$RUNNER_PRE"
json_block hook_pre "$HOOK_PRE"

fact config_hash_pre "$CONFIG_HASH_PRE"
fact runner_hash_pre "$RUNNER_HASH_PRE"
fact hook_hash_pre "$HOOK_HASH_PRE"
fact network_hash_pre "$NETWORK_HASH_PRE"
fact pool_hash_pre "$POOL_HASH_PRE"
fact pool_mtime_pre "$POOL_MTIME_PRE"

STRICT_PRE_ALL=true

for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
  endpoint="$(endpoint_value "$interface")"

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

  fact "endpoint_pre.${interface}" "$endpoint"

  if strict_ping "$interface"; then
    strict=true
  else
    strict=false
    STRICT_PRE_ALL=false
  fi

  fact "strict_pre.${interface}" "$strict"
done

fact strict_pre_all "$STRICT_PRE_ALL"

ROUTES_PRE_ALL=true

for table in 201 202 203 204 205; do
  if ip route show table "$table" 2>/dev/null |
    grep -q '^default '
  then
    route=true
  else
    route=false
    ROUTES_PRE_ALL=false
  fi

  fact "route_pre.${table}" "$route"
done

fact routes_pre_all "$ROUTES_PRE_ALL"

trace backup_and_patch

BACKUP="${CONF}.step050m06d.$$.bak"
TEMP="${CONF}.step050m06d.$$.tmp"
ROLLBACK="/root/rollback-step050m06d-$(date -u +%Y%m%d-%H%M%S).sh"

cp -a "$CONF" "$BACKUP"

cat > "$ROLLBACK" <<EOF
#!/bin/sh
set -eu
cp -a '$BACKUP' '$CONF'
echo 'STEP_050M06D config restored.'
EOF

chmod 700 "$ROLLBACK"

awk '
  BEGIN {
    replaced=0
  }

  /^[[:space:]]*EMERGENCY_COMMIT_ENABLED[[:space:]]*=/ {
    print "EMERGENCY_COMMIT_ENABLED=1"
    replaced++
    next
  }

  {
    print
  }

  END {
    if (replaced != 1) {
      exit 42
    }
  }
' "$CONF" > "$TEMP"

chmod 600 "$TEMP"
chown 0:0 "$TEMP"
mv "$TEMP" "$CONF"

PATCHED=true

fact rollback "$ROLLBACK"
fact rollback_exists "$(bool_cmd test -x "$ROLLBACK")"

trace post_state

COMMIT_POST="$(commit_value)"
COUNTER_POST="$(repair_counter)"
RUNNER_POST="$("$RUNNER" --dry-run)"
HOOK_POST="$("$HOOK")"

CONFIG_HASH_POST="$(
  sha256sum "$CONF" |
    sed 's/[[:space:]].*$//'
)"

RUNNER_HASH_POST="$(
  sha256sum "$RUNNER" |
    sed 's/[[:space:]].*$//'
)"

HOOK_HASH_POST="$(
  sha256sum "$HOOK" |
    sed 's/[[:space:]].*$//'
)"

NETWORK_HASH_POST="$(
  uci export network |
    sha256sum |
    sed 's/[[:space:]].*$//'
)"

POOL_HASH_POST="$(
  sha256sum "$POOL" |
    sed 's/[[:space:]].*$//'
)"

POOL_MTIME_POST="$(
  date -r "$POOL" +%s
)"

fact commit_enabled_post "$COMMIT_POST"
fact repair_counter_post "$COUNTER_POST"

json_block runner_post "$RUNNER_POST"
json_block hook_post "$HOOK_POST"

fact config_hash_post "$CONFIG_HASH_POST"
fact runner_hash_post "$RUNNER_HASH_POST"
fact hook_hash_post "$HOOK_HASH_POST"
fact network_hash_post "$NETWORK_HASH_POST"
fact pool_hash_post "$POOL_HASH_POST"
fact pool_mtime_post "$POOL_MTIME_POST"

STRICT_POST_ALL=true

for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
  endpoint="$(endpoint_value "$interface")"

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

  fact "endpoint_post.${interface}" "$endpoint"

  if strict_ping "$interface"; then
    strict=true
  else
    strict=false
    STRICT_POST_ALL=false
  fi

  fact "strict_post.${interface}" "$strict"
done

fact strict_post_all "$STRICT_POST_ALL"

ROUTES_POST_ALL=true

for table in 201 202 203 204 205; do
  if ip route show table "$table" 2>/dev/null |
    grep -q '^default '
  then
    route=true
  else
    route=false
    ROUTES_POST_ALL=false
  fi

  fact "route_post.${table}" "$route"
done

fact routes_post_all "$ROUTES_POST_ALL"

fact watcher_invoked false
fact commit_invoked false
fact refresh_ran false
fact rebalance_apply_ran false
fact direct_failopen_changed false

trace complete

trap - EXIT
exit 0
