diff --git a/docs/superpowers/specs/2026-08-19-startup-env-check-design.md b/docs/superpowers/specs/2026-08-19-startup-env-check-design.md new file mode 100644 index 0000000..b97dc35 --- /dev/null +++ b/docs/superpowers/specs/2026-08-19-startup-env-check-design.md @@ -0,0 +1,98 @@ +# Startup Environment Check + Self-Heal + +Date: 2026-08-19 + +## Problem + +Rebooting the Pineapple left Mark VIII in a broken state: the boot-persistent +procd service came back, but nothing reconciled pineapd/UCI/monitors, so the UI +reported state that did not match the device (pool "off" while actually +broadcasting), native recon scans failed, and scans only returned 5GHz results. + +Three observed failures: + +1. **native recon scan failed** — `h_recon_start` proxies + `/api/pineap/recon/new`; it returns 502 when the daemon fails (typically + pineapd down/crash-looping). Nothing verifies pineapd health before the user + starts a scan. +2. **scans only showed 5GHz** — radio0's AP interfaces (wlan0open/wlan0wpa) + pin `wlan0mon` to one 2.4GHz channel (a phy's channel is held by its AP + interface), so 2.4GHz results mostly vanish while 5GHz `wlan1mon` hopping + keeps producing results. The band UCI config itself is correct. +3. **pool "on" while UI shows "off"** — the UI's advertise state is derived + only from UCI `pineapd.@ssidpool[0].disable`. Nothing syncs pineapd's + *runtime* pool broadcast to match UCI. UCI can say disabled while pineapd is + live-broadcasting. + +## Design decisions + +- Keep boot auto-start; the service self-heals at every startup instead. +- The env check auto-fixes everything fixable, re-verifies, and only fails hard + on core deps (daemon unreachable after fix, pineapd down after restart, recon + DB unreadable). +- 5GHz-only scans are handled by verify + warn (no scan-time AP pausing). + +## Implementation + +### 1. `env_check()` in server.py (single source of truth) + +Runs in order and returns `[{step, ok, detail, action}]` where `ok` is one of +`pass`, `fixed`, `warn`, `fail`: + +1. **Daemon reachable** — `daemon_sock_call('GET', '/api/pineap/get_config')`. + Report-only. +2. **pineapd alive** — `pidof pineapd`. If down: stabilize UCI, restart + `/etc/init.d/pineapd`, re-verify. Core: if still down after restart -> fail. +3. **Sane-off UCI defaults** — reuse the `_stabilize_pineapd` wanted-dict (pool + `disable=1` + clear pool list, `wlan2mon.disable=1`/`hop=0`, + `wlan1mon.bands=5`/`hop=0`, `wlan0mon.bands=2`). Write + commit when + missing. Refactor so the health monitor and env check share the wanted-dict. +4. **Runtime pool sync** — when UCI says pool disabled, call + `_pineap('SSIDPOOL', 'DISABLE')` so pineapd's runtime broadcast matches the + UI. Report the resulting runtime state as `pool_runtime`. +5. **Monitors up** — `_bring_monitors_up()` for wlan0mon/wlan1mon. +6. **Recon DB readable** — read-only scan-count query against `RECON_DB`. + Report row count. Core: unreadable -> fail. +7. **2.4GHz sampling** — report enabled radio0 APs (wlan0open/wlan0wpa). + Warn: "2.4GHz under-sampled while an OpenAP/Evil WPA AP is up on radio0". + +### 2. CLI mode `server.py --env-check` + +`if __name__ == '__main__'` branch: run `env_check()`, print verbose +`[PASS] step — detail` / `[FIXED] ...` / `[WARN] ...` / `[FAIL] ...` lines to +stdout, exit 0 (all pass/warn) or 1 (any core fail). + +### 3. Startup self-heal + +`serve()` runs `env_check()` before binding, stores the report in module state, +and logs to `/tmp/pagerwebui.log` (covers boot + procd respawn). + +### 4. API + UI + +- `GET /api/health` gains `env` (last env-check report + timestamp + + pass/fixed/warn/fail counts) and `pool_runtime` (actual runtime broadcast + state after sync). +- `GET /api/recon/status` gains `wlan0_pinned` (true when a radio0 AP is + enabled); the recon scan bar shows a 2.4GHz under-sampling warning pill. +- `h_recon_start` failure includes `detail` in the user-facing error. + +### 5. payload.sh + +Before starting the service (both foreground and background paths), run +`python3 "$SCRIPT_DIR/server.py" --env-check`, show output verbosely on the +payload screen, and abort with a red message on exit 1. Bump header version to +match the current release. + +### 6. Tests + +- `tests/test_env_check.py` — fake `device_run` UCI harness (pattern from + `test_health.py`): defaults applied when missing / idempotent when set, pool + list cleared, runtime SSIDPOOL disable invoked when UCI says disabled, + monitors brought up, pineapd restarted when down, DB unreadable -> fail, + CLI `--env-check` prints and exits correctly. +- `test_health.py` updated for the shared stabilization refactor. + +## Out of scope + +- No scan-time AP pausing / radio0 AP teardown. +- No changes to radio1 / evil-twin / enterprise config handling.