diff --git a/documentation/audits/r1292-qb-normal-slot-2026-10-26/tester-1-live-run1-instrument-failed.txt b/documentation/audits/r1292-qb-normal-slot-2026-10-26/tester-1-live-run1-instrument-failed.txt new file mode 100644 index 00000000..3ec58a15 --- /dev/null +++ b/documentation/audits/r1292-qb-normal-slot-2026-10-26/tester-1-live-run1-instrument-failed.txt @@ -0,0 +1,17 @@ +rc=0 + [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'QBT_ADMIN_PASSWORD'] + [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} + [1] deployed, controller state=running, pinned={'qbittorrent': 'qbittorrentofficial/qbittorrent-nox:5.2.4-1'} +1. qBittorrent installed: True + before: {'error': 'unreadable'} +2. alternative speed ON, alternative limit 500 KiB/s (the household's): ok ok + now: {'error': 'unreadable'} + [3. first tick] rule lines: 2026/10/26 12:18:53 [INFO] [upwatch] qBittorrent upload limit set to 5892096 B/s (50 % of the tested line, 94.3 Mbit/s) | record: (none) + after the first tick: {'error': 'unreadable'} +4. the household switches back to normal speed: ok {'error': 'unreadable'} + after the next tick: {'error': 'unreadable'} | record: | lines: (none at INFO) +VERDICT: FAIL + [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'qbittorrent', 'volumes_removed': ['qbittorrent_qbittorrent_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'userdata_kept': ['/mnt/felhom-driv +5. leftovers: {'containers': '', 'volumes': '', 'appyaml': '', 'appdata': '', 'kept': '', 'subdomain': '404'} + +[exited with code 0] diff --git a/documentation/audits/r1292-qb-normal-slot-2026-10-26/tester-1-live.txt b/documentation/audits/r1292-qb-normal-slot-2026-10-26/tester-1-live.txt new file mode 100644 index 00000000..6fcbad48 --- /dev/null +++ b/documentation/audits/r1292-qb-normal-slot-2026-10-26/tester-1-live.txt @@ -0,0 +1,14 @@ + [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'QBT_ADMIN_PASSWORD'] + [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} + [1] deployed, controller state=running, pinned={'qbittorrent': 'qbittorrentofficial/qbittorrent-nox:5.2.4-1'} +1. qBittorrent installed: True + before: {'up_limit': 0, 'alt_up_limit': 10240, 'mode_alt': False} +2. alternative speed ON, alternative limit 500 KiB/s (the household's): ok ok + now: {'up_limit': 0, 'alt_up_limit': 512000, 'mode_alt': True} + [3. first tick] rule lines: 2026/10/26 12:53:06 [INFO] [upwatch] qBittorrent upload limit set to 5892096 B/s (50 % of the tested line, 94.3 Mbit/s) | record: {'box_set_bytes': 5892096, 'line_mbps': 94.28828951955792, 'set_at': '2026-10-26T12:53:06Z'} + after the first tick: {'up_limit': 5892096, 'alt_up_limit': 512000, 'mode_alt': True} +4. the household switches back to normal speed: ok {'up_limit': 5892096, 'alt_up_limit': 512000, 'mode_alt': False} + after the next tick: {'up_limit': 5892096, 'alt_up_limit': 512000, 'mode_alt': False} | record: {'box_set_bytes': 5892096, 'line_mbps': 94.28828951955792, 'set_at': '2026-10-26T12:53:06Z'} | lines: (none at INFO) +VERDICT: PASS + [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'qbittorrent', 'volumes_removed': ['qbittorrent_qbittorrent_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'userdata_kept': ['/mnt/felhom-drives/adatlem +5. leftovers: {'containers': '', 'volumes': '', 'appyaml': '', 'appdata': '', 'kept': '', 'subdomain': '404'} diff --git a/documentation/audits/r1295-pbsdr-selfcheck-2026-10-26/ep0-install-v1.4.0.txt b/documentation/audits/r1295-pbsdr-selfcheck-2026-10-26/ep0-install-v1.4.0.txt new file mode 100644 index 00000000..0d92591c --- /dev/null +++ b/documentation/audits/r1295-pbsdr-selfcheck-2026-10-26/ep0-install-v1.4.0.txt @@ -0,0 +1,11 @@ +2026-10-26T13:09:21Z +syntax-ok +-rwxr-xr-x 1 root root 17964 Oct 26 13:09 /usr/local/bin/felhom-tenantsync +-rwxr-xr-x 1 root root 8253 Jul 10 18:57 /usr/local/bin/felhom-tenantsync.bak-1.0.0 +-rwxr-xr-x 1 root root 10480 Jul 17 11:12 /usr/local/bin/felhom-tenantsync.bak-1.1.0 +-rwxr-xr-x 1 root root 12004 Jul 17 19:15 /usr/local/bin/felhom-tenantsync.bak-1.2.0 +-rwxr-xr-x 1 root root 15542 Oct 19 14:07 /usr/local/bin/felhom-tenantsync.v1.3.0 +751b3c26f3e4c872 +# felhom-tenantsync v1.4.0 — the offsi +{"status":"ok","fp":"c6:07:28:3f"} +{"status":"ok","unpruned_checked":true,"unpruned":[]} diff --git a/documentation/audits/release-monday-2026-10-26/tools/r1292_live.py b/documentation/audits/release-monday-2026-10-26/tools/r1292_live.py new file mode 100644 index 00000000..6675da36 --- /dev/null +++ b/documentation/audits/release-monday-2026-10-26/tools/r1292_live.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""r1292_live.py — R-1292 live on a box with a TESTED line (Tester 1; 9202 has none, so the rule never acts there): +a THROWAWAY qBittorrent; the household's „alternative speed" switched ON with an alternative limit of 500 KiB/s; then +the controller's 50 % rule (its 15-minute tick) must set the NORMAL limit (`up_limit`) and leave the alternative alone; +then the household switches back to normal speed, and the next tick must NOT read that as „the household changed it". +qBittorrent's own API from inside its container (the admin password rides an env var, never printed). Removed through +the product at the end (data and backups). + +usage: SC= SWEEP_EV= TARGET=tester-1 DRIVE_ROOT= python3 r1292_live.py""" +import json, os, sys, time +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools")) +import sweep as sw # noqa: E402 + +w = sw.w +APP = "qbittorrent" + + +def qb(path, data=None): + """One qBittorrent API call inside its container, through /root/qbtool.sh in the guest (the password via env QP).""" + pw = (w.GENERATED.get(APP) or {}).get("QBT_ADMIN_PASSWORD", "") + arg = " '" + data + "'" if data else "" + return sw.g(f"QP='{pw}' sh /root/qbtool.sh {path}{arg}").strip() + + +def record(): + """The 50 % rule's record (settings.json upwatch.qb_limit), parsed — never a grep on a JSON file.""" + raw = sw.g("docker exec felhom-controller cat /opt/docker/felhom-controller/data/settings.json") + try: + return (json.loads(raw).get("upwatch") or {}).get("qb_limit") + except ValueError: + return "unreadable" + + +def prefs(): + try: + p = json.loads(qb("app/preferences")) + return {"up_limit": p.get("up_limit"), "alt_up_limit": p.get("alt_up_limit"), "mode_alt": qb("transfer/speedLimitsMode") == "1"} + except ValueError: + return {"error": "unreadable"} + + +def rule_lines(since): + return sw.g(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'qBittorrent (upload )?limit' | cut -c1-200").strip() + + +def wait_tick(since, say, label, cap=1100): + for _ in range(cap // 20): + time.sleep(20) + out = sw.g(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'upwatch' | grep -vE 'DEBUG' | tail -1") + # the 15-minute tick also logs the hold seeder; any upwatch line after `since` with a qBittorrent verdict counts + ln = rule_lines(since) + if ln: + break + st = record() + say(f" [{label}] rule lines: {rule_lines(since) or '(none at INFO)'} | record: {st or '(none)'}") + return st + + +def main(): + say = print + w.say = say + w.login() + t0 = int(time.time()) + try: + ok = w.deploy(APP, "torrent") and w.wait_app("torrent", "/", tries=90, delay=5) + say("1. qBittorrent installed:", ok) + before = prefs() + say(" before:", before) + if "error" in before: + raise SystemExit("INSTRUMENT: qBittorrent's preferences are unreadable — no verdict (fix the probe first)") + say("2. alternative speed ON, alternative limit 500 KiB/s (the household's):", + qb("app/setPreferences", "json=" + json.dumps({"alt_up_limit": 512000})) or "ok", + qb("transfer/toggleSpeedLimitsMode", "x=1") or "ok") + say(" now:", prefs()) + rec1 = wait_tick(t0, say, "3. first tick") + p1 = prefs() + say(" after the first tick:", p1) + say("4. the household switches back to normal speed:", qb("transfer/toggleSpeedLimitsMode", "x=1") or "ok", prefs()) + t1 = int(time.time()) + time.sleep(960) # one full 15-minute tick; a "kept" decision logs nothing, a "household" one logs at INFO + rec2 = record() + say(" after the next tick:", prefs(), "| record:", rec2, "| lines:", rule_lines(t1) or "(none at INFO)") + verdict = ("PASS" if p1.get("up_limit", 0) and abs(p1["up_limit"] - 94.3 * 0.5 * 1e6 / 8) < 2048 and p1.get("alt_up_limit") == 512000 + and isinstance(rec2, dict) and not rec2.get("household") else "FAIL") + say("VERDICT:", verdict) + finally: + sw.remove(APP, keep=False, say=say) + say("5. leftovers:", sw.leftovers(APP, "torrent")) + + +if __name__ == "__main__": + main() diff --git a/hub/CHANGELOG.md b/hub/CHANGELOG.md index b4674d5b..fa6a4100 100644 --- a/hub/CHANGELOG.md +++ b/hub/CHANGELOG.md @@ -1,4 +1,4 @@ -## v0.172.0 (unreleased, 2026-10-26) — a fresh install's whole-guest off-site set-up never fails silently (R-1295) +## v0.172.0 (2026-10-26) — a fresh install's whole-guest off-site set-up never fails silently (R-1295) — RELEASED 2026-10-26 13:08 UTC on the operator's yes (image sha256:7c8d129a…, from `232f42d2`, CI 2366; rc.1 ran the live proof) - **Cause (measured, `documentation/audits/r1295-pbsdr-selfcheck-2026-10-26/`):** PBS's proxy caches permissions for up to 5 s and an ACL-only change does not clear the cache; under concurrent requests ep0's self-check of a new token is diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index aa2f4b83..845ba5f9 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,10 +1,10 @@ -## 2026-10-26 — felhom-tenantsync v1.4.0: the self-check waits out the PBS permission cache (R-1295) — committed, NOT installed on ep0 +## 2026-10-26 — felhom-tenantsync v1.4.0: the self-check waits out the PBS permission cache (R-1295) — INSTALLED on ep0 2026-10-26 13:09Z on the operator's yes (sha256 `751b3c26…` = the repository's; v1.3.0 kept as `.v1.3.0`; `fingerprint` and `usage` ok) - **`felhom-tenantsync.sh` v1.4.0:** the new token is re-checked every 0.5 s for up to 15 s (a log line per try) before the old regenerate-once-and-rollback; the transient admin token waits the same way before its first namespace list (under load it met the same cache and a wrong `namespace create` died). Test `test_tenantsync_selfcheck.py` (19 checks; v1.3.0 fails 11). Live on a throwaway PBS 4.2.5 under load, the hub's exact order, 30 pairs: v1.3.0 failed 11, v1.4.0 - failed 0 (`documentation/audits/r1295-pbsdr-selfcheck-2026-10-26/`). **Installing it on ep0 is the operator's yes.** + failed 0 (`documentation/audits/r1295-pbsdr-selfcheck-2026-10-26/`, `ep0-install-v1.4.0.txt`). ## 2026-10-25 — installer 1.36.1: the preflight words a refusal again — PUBLISHED 2026-10-25 evening on the operator's yes (`installer-v1.36.1` → `79ee3e33`, served sha256 `e18cdd17…`; the three refusal sentences seen live)