From eae47d99bf4add8f61d6f22fc727ca087166f0f0 Mon Sep 17 00:00:00 2001 From: c4ch3c4d3 Date: Sat, 22 Aug 2026 16:23:50 -0600 Subject: [PATCH] fix(reliability): encode str stdin in device_run; smoke drill path fixes + regression tests --- .../user/remote_access/pager-webui/server.py | 2 ++ scripts/smoke.sh | 29 ++++++++++++++----- tests/test_core.py | 12 ++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/payload/user/remote_access/pager-webui/server.py b/payload/user/remote_access/pager-webui/server.py index 5de49ac..1bf2780 100644 --- a/payload/user/remote_access/pager-webui/server.py +++ b/payload/user/remote_access/pager-webui/server.py @@ -107,6 +107,8 @@ SELF_PAYLOAD_KEY = os.environ.get('PAGER_SELF_PAYLOAD_KEY', 'user~remote_access~ def device_run(args, timeout=20, input_data=None): try: + if isinstance(input_data, str): + input_data = input_data.encode('utf-8') p = subprocess.run(args, input=input_data, capture_output=True, timeout=timeout) return p.returncode, p.stdout.decode('utf-8', 'replace'), p.stderr.decode('utf-8', 'replace') except FileNotFoundError: diff --git a/scripts/smoke.sh b/scripts/smoke.sh index 3fd2b30..88eb73f 100755 --- a/scripts/smoke.sh +++ b/scripts/smoke.sh @@ -39,6 +39,9 @@ set -u BASE=/mmc/mk8 REL="$BASE/releases/current" +# Release layout: /user///... — resolve the payload dir. +REL_PAY="$(find "$REL/user" -maxdepth 3 -name server.py 2>/dev/null | head -n 1)" +REL_PAY="${REL_PAY%/server.py}" LEGACY=/root/payloads/user/remote_access/pager-webui URL=http://127.0.0.1:8080 GUARD_INIT=/etc/init.d/mk8-guard @@ -221,9 +224,9 @@ check_monitors() { } check_versions() { - sv="$(grep '^SERVER_VERSION' "$REL/server.py" 2>/dev/null | head -n 1 \ + sv="$(grep '^SERVER_VERSION' "$REL_PAY/server.py" 2>/dev/null | head -n 1 \ | sed -e 's/^SERVER_VERSION = //' -e "s/'//g" | tr -d '\r')" - pv="$(grep '^#[ ]*[Vv]ersion:' "$REL/payload.sh" 2>/dev/null | head -n 1 \ + pv="$(grep '^#[ ]*[Vv]ersion:' "$REL_PAY/payload.sh" 2>/dev/null | head -n 1 \ | sed 's/^#[ ]*[Vv]ersion:[ ]*//' | tr -d '\r')" if [ -n "$sv" ] && [ "$sv" = "$pv" ]; then pass "versions: release server.py and payload.sh agree ($sv)" @@ -241,7 +244,7 @@ check_authed_api() { fail 'authed API: python3 not found for response check' return fi - body='{"password":"'"$PASS"'"}' + body='{"username":"root","password":"'"$PASS"'"}' code="$(curl -fsS -m 10 -o /dev/null -w '%{http_code}' \ -c "$JAR" -H 'Content-Type: application/json' \ -d "$body" "$URL/api/login" 2>/dev/null)" @@ -272,7 +275,7 @@ drill_bad_values() { uci set pineapd.wlan1mon.bands='2,5' uci commit pineapd - "$PY" "$REL/server.py" --reconcile >/dev/null 2>&1 + "$PY" "$REL_PAY/server.py" --reconcile >/dev/null 2>&1 got="$(uci_get pineapd.wlan1mon.bands)" if [ "$got" = "5" ]; then pass 'drill: reconcile repaired bands 2,5 -> 5' @@ -289,7 +292,7 @@ drill_bad_values() { done uci set "pineapd.@ssidpool[0].ssid=${list# }" uci commit pineapd - "$PY" "$REL/server.py" --reconcile >/dev/null 2>&1 + "$PY" "$REL_PAY/server.py" --reconcile >/dev/null 2>&1 got="$(uci_get 'pineapd.@ssidpool[0].ssid')" if [ -z "$got" ]; then pass 'drill: reconcile cleared oversized SSID pool' @@ -303,7 +306,7 @@ drill_bad_values() { uci -q delete 'pineapd.@ssidpool[0].ssid' fi uci commit pineapd - "$PY" "$REL/server.py" --reconcile >/dev/null 2>&1 + "$PY" "$REL_PAY/server.py" --reconcile >/dev/null 2>&1 } drill_role() { @@ -368,12 +371,24 @@ PYEOF } drill_watchdog() { - WDOG="$REL/mk8-watchdog.sh" + WDOG="$REL_PAY/mk8-watchdog.sh" [ -f "$WDOG" ] || WDOG="$LEGACY/mk8-watchdog.sh" if [ ! -f "$WDOG" ]; then fail 'drill: mk8-watchdog.sh not found in release or legacy dir' return fi + # Ensure a profile exists: snapshot the live config as the drill target. + if [ ! -d "$BASE/profiles" ] || [ -z "$(ls "$BASE/profiles" 2>/dev/null)" ]; then + "$PY" - "$REL_PAY" <<'PYEOF' >/dev/null 2>&1 || { +import sys +sys.path.insert(0, sys.argv[1]) +import mk8_profiles +mk8_profiles.snapshot('smoke-drill') +PYEOF + fail 'drill: could not snapshot a profile for watchdog drill' + return + } + fi prof=lastknown-good [ -d "$BASE/profiles/$prof" ] \ || prof="$(ls "$BASE/profiles" 2>/dev/null | head -n 1)" diff --git a/tests/test_core.py b/tests/test_core.py index 42a0566..16f63df 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -69,3 +69,15 @@ class DeviceRunTest(unittest.TestCase): if __name__ == '__main__': unittest.main() + + +class DeviceRunInputTest(unittest.TestCase): + def test_str_input_data_is_encoded(self): + rc, out, err = server.device_run(['cat'], input_data='uci import text') + self.assertEqual(rc, 0) + self.assertEqual(out, 'uci import text') + + def test_bytes_input_data_passes_through(self): + rc, out, err = server.device_run(['cat'], input_data=b'raw') + self.assertEqual(rc, 0) + self.assertEqual(out, 'raw')