From 78aab64af06ba61026860d5a216a308ac19fcefa Mon Sep 17 00:00:00 2001 From: c4ch3c4d3 Date: Sat, 22 Aug 2026 14:08:25 -0600 Subject: [PATCH] fix(reliability): watchdog max lifetime + serialized gate entry (review fixes) --- .../remote_access/pager-webui/mk8-watchdog.sh | 11 +++++++--- .../remote_access/pager-webui/mk8_gate.py | 20 ++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/payload/user/remote_access/pager-webui/mk8-watchdog.sh b/payload/user/remote_access/pager-webui/mk8-watchdog.sh index cdb572e..cdcd672 100755 --- a/payload/user/remote_access/pager-webui/mk8-watchdog.sh +++ b/payload/user/remote_access/pager-webui/mk8-watchdog.sh @@ -1,9 +1,10 @@ #!/bin/sh -# Usage: mk8-watchdog.sh -PROFILE="$1"; IV="${2:-5}"; FA="${3:-6}"; HA="${4:-6}" +# Usage: mk8-watchdog.sh [max_ticks] +# Exits quietly after max_ticks healthy ticks so sentinels cannot accumulate. +PROFILE="$1"; IV="${2:-5}"; FA="${3:-6}"; HA="${4:-6}"; MT="${5:-120}" DIR="/root/payloads/user/remote_access/pager-webui" [ -f "$DIR/server.py" ] || DIR="/mmc/mk8/releases/current" -fails=0; oks=0; tripped=0 +fails=0; oks=0; tripped=0; ticks=0 probe() { curl -fsS -m 3 http://127.0.0.1:8080/ >/dev/null 2>&1 && { ip link show wlan0mon >/dev/null 2>&1 || @@ -12,6 +13,10 @@ probe() { while true; do if probe; then fails=0 + ticks=$((ticks + 1)) + if [ "$tripped" = "0" ] && [ "$ticks" -ge "$MT" ]; then + exit 0 + fi if [ "$tripped" = "1" ]; then oks=$((oks + 1)) if [ "$oks" -ge "$HA" ]; then diff --git a/payload/user/remote_access/pager-webui/mk8_gate.py b/payload/user/remote_access/pager-webui/mk8_gate.py index 6e95c32..7401cb2 100644 --- a/payload/user/remote_access/pager-webui/mk8_gate.py +++ b/payload/user/remote_access/pager-webui/mk8_gate.py @@ -1,11 +1,14 @@ """Risky-operation gate: preflight config snapshot + detached rollback watchdog.""" import shlex import subprocess +import threading WATCHDOG = '/root/payloads/user/remote_access/pager-webui/mk8-watchdog.sh' FAIL_AFTER = 6 # consecutive local-liveness failures -> rollback HEALTHY_AFTER = 6 # consecutive successes after failure -> promote INTERVAL = 5 # seconds between probes +MAX_TICKS = 120 # watchdog self-exits after this many quiet ticks +_ENTER_LOCK = threading.Lock() # Dormant until an entrypoint (serve() / CLI ops) flips it on, so importing # this module never snapshots or spawns anything. @@ -27,20 +30,23 @@ def watchdog_decision(state): def _spawn_watchdog(name): - cmd = ('setsid sh %s %s %d %d %d >/dev/null 2>&1 &' + cmd = ('setsid sh %s %s %d %d %d %d >/dev/null 2>&1 &' % (shlex.quote(WATCHDOG), shlex.quote(name), - INTERVAL, FAIL_AFTER, HEALTHY_AFTER)) + INTERVAL, FAIL_AFTER, HEALTHY_AFTER, MAX_TICKS)) return subprocess.Popen(cmd, shell=True, start_new_session=True) def enter(op): - """Snapshot + spawn watchdog. Returns profile name or None when disabled.""" + """Snapshot + spawn watchdog. Returns profile name or None when disabled. + Serialized so concurrent gated ops cannot interleave snapshots or spawn + racing watchdogs.""" if not ENABLED: return None - import mk8_profiles - name = mk8_profiles.auto_name(op) - mk8_profiles.snapshot(name) - _spawn_watchdog(name) + with _ENTER_LOCK: + import mk8_profiles + name = mk8_profiles.auto_name(op) + mk8_profiles.snapshot(name) + _spawn_watchdog(name) try: import mk8_events mk8_events.log_event('gate', msg='preflight snapshot %s' % name)