diff --git a/docs/backlog.md b/docs/backlog.md index 4d444c8..b6cec6a 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -102,6 +102,127 @@ historia incydentów, out-of-band watchdog. ## Aktywne +### Executor: zepsuty JSON w `approved/` retry'owany w nieskończoność co 10s zamiast trafić do `failed/`/`rejected/` + +**Data**: 2026-07-23 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: podczas testów E2E remediacji bez SSH zepsuty JSON akcji w `approved/` +(przyczyna: podstawienie zmiennej lokalnej w cudzysłowie w zagnieżdżonym heredoc przez +ssh) powodował, że executor próbował go sparsować co cykl (10s), logując "Failed to +move ... to running: Expecting value" bez końca — plik nigdy nie trafiał do +`failed/`/`rejected/`. +**Fix**: executor powinien przenosić nieparsowalny JSON do `failed/` (albo dedykowanego +`malformed/`) po pierwszej próbie, nie zostawiać go do nieskończonego retry w `approved/`. + +--- + +### Uprawnienia `actions/` naprawione TYLKO na PIHA (ręcznie) — SOLARIA i lustro niezweryfikowane + +**Data**: 2026-07-23 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: fix uprawnień `/opt/homelab/actions` (grupa `pi` + setgid, patrz Zamknięte) +zastosowany ręcznie tylko na PIHA. SOLARIA (oskar tam podobno uid 1000 — do sprawdzenia +czy problem w ogóle występuje) i lustro (repo-less, patrz osobny wpis niżej) +niezweryfikowane — remediacja na tych węzłach może paść identycznie przy pierwszej +próbie. +**Fix docelowy**: node-agent powinien sam tworzyć `dispatch//` z właściwymi +prawami (grupa współdzielona + setgid) przy starcie, żeby to nie wracało za każdym +razem jako ręczna interwencja — patrz też „Tech-debt: globalny porządek uid/gid" niżej. + +--- + +### `deploy-local.sh` (control-plane): brak twardego checka, że `.env` istnieje i `TAILSCALE_BIND_IP` jest ustawione + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: przy fixie bindu `operator_ui.py:18180` odkryto footgun: bez `.env`, +`docker compose` tylko OSTRZEGA i po cichu wraca do bindu `0.0.0.0` zamiast failować — +dokładnie ten sam wzorzec błędu jak historyczny bug fleet-prometheus/deploy-node.sh +(brak `--env-file`, patrz Zamknięte, commit `686aca7`), tym razem w +`deploy-local.sh`/control-plane. +**Fix**: dodać twardy check w `deploy-local.sh` — `.env` musi istnieć i +`TAILSCALE_BIND_IP` musi być ustawiony, inaczej abort przed `docker compose up`. + +--- + +### `operator_ui.py` nie ma ŻADNEJ autoryzacji + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: po zamknięciu publicznego bindu 18180 (patrz Zamknięte, commit `9a5c160`) +`operator_ui.py` nadal przyjmuje `/action/mutate` (w tym przejście do `approved`) bez +żadnego uwierzytelnienia — chroni tylko granica Tailscale mesh, nie autoryzacja per +operator. +**Fix**: osobny temat — do zaprojektowania (token/basic auth/mTLS w mesh), poza +zakresem fixu bindu. + +--- + +### `alert_only` zapycha approval queue + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: przy reconie stanu wyjściowego 16 z 18 pending actions to były +liveness-transitions typu `alert_only`, nie realne decyzje do klikania — operator musi +przewijać szum, żeby znaleźć akcje, które faktycznie wymagają Approve/Reject. +**Fix**: rozważyć osobny widok/filtr dla `alert_only` w operator UI, albo +auto-acknowledge bez wejścia do tej samej kolejki co `container_restart`/`redeploy`. + +--- + +### Crash-loop nie generuje `container_restart` — gap detekcja→akcja + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: `node_exporter` na PIHA miał 1048 restartów i event `[high]`, ale +supervisor nie wygenerował żadnej akcji `container_restart` — crash-loop widoczny w +evencie nie przekłada się na akcję remediacyjną. +**Fix**: zbadać routing supervisora dla crash-loop sygnałów (`disk_pressure` i +`containers_not_running` mają jasne mapowanie na akcje w CLAUDE.md — crash-loop +najwyraźniej nie). + +--- + +### Telegram yes/no dla pending actions brakuje + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: brain-watchdog ma `send_telegram` (używane dla alertów Prometheus), ale +nic nie łączy go z `actions/pending` — operator musi wejść do operator UI, żeby +zatwierdzić/odrzucić akcję, zamiast dostać yes/no bezpośrednio na Telegramie. +**Fix**: rozszerzyć brain-watchdog (albo osobny konsument) o powiadomienie z +przyciskami approve/reject per pending action. + +--- + +### `homeassistant5` na PIHA: `Exited (0)`, nie podnosi się mimo `restart: unless-stopped` + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: kontener `homeassistant5` (legacy HA "ken", patrz cutover +`docs/backlog.md` sekcja "Cutover HA ken") zaobserwowany w stanie `Exited (0)` — exit +code 0 = czyste zatrzymanie, więc Docker `restart: unless-stopped` świadomie go nie +podnosi (to nie crash). Do zweryfikowania, czy to zamierzone wygaszenie z cutoveru czy +coś zatrzymało kontener niezamierzenie. +**Fix**: sprawdzić czy to spójne z planem wygaszenia `homeassistant5` (patrz cutover +HA ken, krok 2 „Wygaszenie homeassistant5") — jeśli tak, brak akcji; jeśli nie, +zbadać czemu wyszedł z kodem 0. + +--- + +### lustro: `node_agent.py` wymaga ręcznego wypchnięcia do `/opt/homelab/deploy/node-agent` (repo-less) + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Problem**: węzeł lustro nie ma repo/gita — `node_agent.py` (z fixami remediacji bez +SSH, commit `2dac154`) musi być ręcznie skopiowany do +`/opt/homelab/deploy/node-agent`, żeby dotrzeć na ten węzeł. Ryzyko rozjazdu +repo↔runtime identyczne jak przy innych repo-less węzłach. +**Fix**: ustalić mechanizm dystrybucji kodu node-agenta na węzły repo-less (rsync przy +deployu / paczka artefaktu / inny kanał) zamiast ręcznego kopiowania. + +--- + ### 🔴 npm@VPS panel admina :81 publicznie osiągalny z internetu **Data**: 2026-07-10 @@ -420,6 +541,68 @@ Długoterminowo: `agent.sh new` powinien odmawiać jeśli żądana gałąź jest ## Zamknięte +### Publiczny bind `operator_ui.py:18180` na VPS bez autoryzacji — NAPRAWIONE (2026-07-22, commit `9a5c160`) + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Było**: `operator_ui.py` (port 18180) nasłuchiwał na `0.0.0.0` na publicznym VPS +(`135.181.153.108`) bez żadnej autoryzacji. `curl` z zewnątrz do `/actions` zwracał +HTTP 200. `do_POST /action/mutate` przenosi akcje między stanami WŁĄCZNIE z +`"approved"` — dowolna osoba z internetu mogła zatwierdzić akcję remediacyjną. +Jedyne co chroniło do tej pory: executor nie umiał jeszcze wykonać akcji (brak SSH, +patrz wpis niżej) — przypadek, nie zabezpieczenie. +**Naprawione**: dual-bind wg wzorca fleet-prometheus — `127.0.0.1:18180:8080` + +`${TAILSCALE_BIND_IP}:18180:8080`, nowy `services/control-plane/env.example`. +Zweryfikowane po deployu: publiczny IP → HTTP 000, Tailscale → HTTP 200. Konsumenty +(node-agent VPS, materializer PIHA) nietknięte. +**Footgun zapamiętany**: brak `.env` → `docker compose` tylko OSTRZEGA i po cichu +wraca do bindu `0.0.0.0`, nie failuje — patrz wpis „deploy-local.sh: brak twardego +checka .env" w Aktywnych. +**Pozostaje osobno**: `operator_ui.py` nadal bez żadnej autoryzacji (bind zamknięty +chroni przed internetem, nie przed kimkolwiek w mesh Tailscale) — patrz Aktywne. + +--- + +### Remediacja floty bez SSH: executor→node-agent pull przez rsync — ZROBIONE (2026-07-22, commit `2dac154`), E2E potwierdzone 2026-07-23 + +**Data**: 2026-07-22 (implementacja), 2026-07-23 (pierwszy udany cykl E2E) +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Było**: łańcuch supervisor→pending→approved→running działał, ale executor próbował +`ssh oskar@{node} docker restart {container}` — kontener control-plane nie ma klienta +ssh, klucza, ani rozwiązywalnych nazw węzłów. Wykonanie zawsze failowało. +**Decyzja architektoniczna**: bez SSH w executorze (kontener na publicznym VPS z +powłoką na całą flotę = zły blast radius). Kierunek PULL: executor zleca (zapis +`actions/dispatch//.json`), node-agent na docelowym węźle wykonuje +lokalnie przez własny `docker.sock`, wynik wraca eventem `action_result` przez +istniejący rsync-push. VPS nigdy nie inicjuje połączenia do węzła. +**Zabezpieczenia**: walidacja `node == self.node_name`; whitelist typów akcji = +`{container_restart}`; guard przed restartem samego node-agenta; brak wykonywania +dowolnych poleceń z payloadu; idempotencja (ponowne zlecenie = no-op). 183 testy. +**Potwierdzone w boju (2026-07-23)**: cykl `test-e2e-b` — Executing → Dispatched → +Completed w 31 sekund, `node_exporter` na PIHA realnie zrestartowany (uptime 30h→ +minuty), zero połączeń SSH. Pierwszy w historii systemu pełny cykl remediacji. + +--- + +### Uprawnienia `actions/` na PIHA (uid/gid oskar 1004 vs kontener 1000) — NAPRAWIONE ręcznie (2026-07-23, poza repo) + +**Data**: 2026-07-23 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Było**: pierwszy test E2E remediacji bez SSH padał — agent logował `[Errno 13] +Permission denied: /opt/homelab/actions/dispatch` co cykl. `/opt/homelab/actions` +było `oskar:oskar drwxr-xr-x` (utworzone w maju), a działający wzorzec to +`/opt/homelab/events` = `oskar:pi drwxrwsr-x` (grupa `pi`, zapis grupowy, setgid). +Ten sam motyw uid/gid (host oskar 1004 vs kontener 1000) uderzył już czwarty raz — +patrz sekcja „Tech-debt: globalny porządek uid/gid/uprawnień we flocie". +**Naprawione (ręcznie, tylko na PIHA)**: `chgrp -R pi` + `chmod -R g+w` + `chmod g+s` +na `/opt/homelab/actions`. Executor zachował się poprawnie podczas awarii: po 300s +timeoutu przeniósł akcję do `failed` z czytelnym powodem, nic nie zawisło. +**Pozostaje osobno**: fix zastosowany TYLKO na PIHA — SOLARIA i lustro +niezweryfikowane; docelowy fix systemowy to node-agent tworzący +`dispatch//` z właściwymi prawami przy starcie — patrz Aktywne. + +--- + ### Supervisor: pętla zamrożona ~24h, healthy ale nie tika — NAPRAWIONE (2026-07-16, commit `409b583`) **Data**: 2026-07-16