homelab-codex-ws/kb/decisions/backlog-zamkniete.md
oskar 01db57ab82 fix(kb): przepiecie wszystkich odwolan wewnetrznych po migracji
126 plikow (md, yaml, sh, py) odwolywalo sie do sciezek sprzed migracji.

  15  markdown-linkow [..](..) -> policzona sciezka WZGLEDNA wobec pliku
      odsylajacego (wczesniej czesc z nich byla repo-root-relative i nie
      rozwiazywala sie z katalogu, w ktorym lezala)
 200  odwolan tekstowych (backticki, proza, yaml, importy w kodzie)
      -> nowa sciezka repo-root-relative, zgodnie z konwencja repo
   5  linkow rodzenstwa (gole nazwy plikow, np. "](DEPLOY.md)") — dzialaly
      tylko w starym katalogu; przeliczone recznie

Objete m.in.: CLAUDE.md (scripts/onboard/README.md -> kb/runbooks/
node-onboarding-tool.md, docs/backlog.md -> kb/phases/backlog.md),
README.md, .claude/skills/, 20 session logow, kod jobow.

Ostatnie 5 odwolan pochodzi z tresci wciagnietej rebasem z origin/master
(session log 2026-07-31, override node-agenta na SOLARII, dwie pozycje
backlogu) — wskazywaly na docs/incidents/, docs/kb/modules/ i
services/narty27/README.md sprzed migracji.

Dodany wzajemny link miedzy kb/services/control-plane.md (stub kodu)
a kb/subsystems/control-plane.md (opis, deprecated) — dwa dokumenty o tym
samym systemie, latwe do pomylenia.

Weryfikacja na 790 plikach: 0 odwolan do starych sciezek,
0 martwych linkow markdown. Lint OKF: 190/190 plikow ZGODNE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:53:57 +02:00

12 KiB
Raw Blame History

okf type visibility status updated links
0.1 decision private active 2026-08-03
../phases/backlog.md

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 .envdocker 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/<node>/<action_id>.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/<node>/ 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 Źródło: sesja 2026-07-16 (docs/sessions/2026-07-16.md) — wątek control-plane Było: kontener supervisora healthy, ale pętla reconcile() nie tikała od ~24h, zero logów. Root cause zweryfikowany na /proc (State:S hrtimer_nanosleep, NIE deadlock): glob po EVENTS_DIR co cykl przy 358k plikach → cykl przekracza brak timeoutu → nigdy się nie kończy. Logi na DEBUG maskowały objaw. Naprawione: każdy cykl w ThreadPoolExecutor z future.result(timeout=90s, env SUPERVISOR_RECONCILE_TIMEOUT); try/except owija cykl (wyjątek nie zabija pętli); tick-log co 10 cykli (SUPERVISOR_TICK_LOG_EVERY) na INFO; healthcheck sprawdza świeżość heartbeat, nie tylko czy proces żyje. Zweryfikowane w boju: pętla tika (cycle #340→#480), cykl #1 timeoutował ale pętla kontynuowała = odporność działa. Lekcja: „healthy kontener ≠ tikająca pętla" — healthcheck musi sprawdzać świeżość ostatniego cyklu, nie samo czy proces odpowiada.


Event flood 358k plików + retencja martwa od fixu checkpointu — NAPRAWIONE (2026-07-16, commit dff76ec)

Data: 2026-07-16 Źródło: sesja 2026-07-16 (docs/sessions/2026-07-16.md) — wątek control-plane Było: EVENTS_DIR = 358k plików, 91% to service_healthy emitowany co cykl per serwis (stan-jako-zdarzenie, antywzorzec). Retencja _cleanup_control_plane_fs() była martwa od fixu checkpointu d5139c9 (2026-07-15, patrz „Bug: checkpoint observera po ścieżce leksykalnej" niżej) — porównanie str(ścieżka) <= checkpoint_int rzucało TypeError cicho łapany przez szeroki except → backlog rósł bez ograniczenia. Naprawiając checkpoint wczoraj, złamaliśmy retencję, która na nim polegała. Naprawione: (a) node-agent emituje service_healthy tylko na transition unhealthy→healthy (funkcja dla observer.process_event zachowana); (b) retencja naprawiona epoch-do-epoch; (c) scripts/maintenance/cleanup_event_backlog.py (dry-run + --apply). 150 testów pass. Cleanup wykonany na PIHA+VPS: 272 232 pliki usunięte, backlog 358k→12,7k. Wynik: reconcile supervisora przestał timeoutować. Lekcja: migracja typu pola (ścieżka→epoch int) musi audytować WSZYSTKICH konsumentów tego pola — szeroki except maskował dokładnie tę klasę regresji.


Ghost kontenery w panelu (Problem B) — ZNIKNĘŁY (potwierdzone reconem 2026-07-02)

⚠️ ZDEZAKTUALIZOWANE 2026-07-06: ghosty znów widoczne na VPS — patrz wpis „Ghosty B WRÓCIŁY" w Aktywnych (docs/sessions/2026-07-06.md).

Data: 2026-06-24 (wykryte), 2026-07-02 (zamknięte) Źródło: sesje 2026-06-24/25/26; recon kb/subsystems/fleet-inventory-verify.md Było: martwe kontenery ze starych project-name'ów (8547b46c0317_control-plane-supervisor itp.) raportowane przez observera jako errorSystem Status ERROR w panelu mimo zdrowego mózgu. Zamknięte: recon 2026-07-02 — 24 kontenery na VPS, ZERO hash-prefixed. Prawdopodobnie recreate'y z kolejnych deployów je zmiotły. Zero akcji ręcznej. Rozważenie czyszczenia obcych project-name przy deployu — już nieaktualne (fix A 3b71707 blokuje źródło divergence).


brain-watchdog: poll Prometheus — POTWIERDZONY (recon 2026-07-02)

Data: 2026-06-30 (pending), 2026-07-02 (zamknięte) Źródło: recon kb/subsystems/fleet-inventory-verify.md Było: log startowy nie wypisuje PROMETHEUS_URL → brak pewności, że polling aktywny; diagnoza opierała się na .env i braku błędów. Zamknięte: recon potwierdził — obraz zbudowany po 62d6fc0, PROMETHEUS_URL w .env I w env kontenera, zero poll failed w logach. Poll aktywny. Jednolinijkowy log startowy (polling enabled/disabled) pozostaje opcjonalną kosmetyką. Pełne end-to-end (firing → Telegram) POTWIERDZONE 2026-07-06 testem AlertTestEtap0 (Etap 0 cutoveru, docs/sessions/2026-07-06.md) — bez czekania na realną awarię.


Miny #1/#2/#3 z weryfikacji inwentaryzacji — ROZBROJONE (2026-07-02)

Źródło: kb/subsystems/fleet-inventory-verify.md, sesja docs/sessions/2026-07-02.md (tam szczegóły i lekcje).

  • #1 PIHA checkout: gałąź wciąż task/kb-gmail-import po resecie z 2026-06-30 (reset --hard przesuwa gałąź, nie przełącza) → checkout master && pull, 30 commitów nadrobione.
  • #2 control-plane na SATURN: supervisor ślepy (brak mountu repo) → docker compose down, wolumeny zachowane. Jedyny mózg = VPS.
  • #3 owner_node: forgejo→piha, mosquitto→vps (commit 886bc85).

🔴 KRYTYCZNY — deploy.sh vps niszczył control-plane — NAPRAWIONE (commit 3b71707)

Data: 2026-06-24 (wykryte), 2026-06-25 (incydent w produkcji), 2026-06-26 (naprawione) Źródło: sesja 2026-06-24 + 2026-06-25 + 2026-06-26 (docs/sessions/2026-06-26.md) Było (Problem A): control-plane w hosts/vps/services.yaml jako zwykły serwis pętli deploy-node.sh. Pętla używała innego COMPOSE_PROJECT_NAME niż deploy-local.sh (cwd=services/control-plane). Niezgodność → Recreate → No such container: <hash>_control-plane-observerset -e przerywa pętlę → observer/supervisor/executor/ui znikają. Każdy deploy.sh vps rozkładał mózg (potwierdzone w produkcji 2026-06-25). Naprawione: guard w deploy-node.sh pomijający serwisy z własnym services/<svc>/deploy-local.sh. control-plane ZOSTAJE w services.yaml (gate pytest+build nadal go testuje), pomijana jest tylko destrukcyjna pętla deployu. Potwierdzone w boju: deploy.sh vps wypisał Skipping control-plane: ma własną ścieżkę deployu, mózg Up 25h healthy, nietknięty. Pozostało osobno: ghosty z poprzednich rozjazdów (Problem B — patrz Aktywne).


Flaky testy control-plane — state-leak w pytest — NAPRAWIONE (commit 992ff7c)

Data: 2026-06-24 (zgłoszone), 2026-06-25 (naprawione) Źródło: sesja 2026-06-24 + sesja 2026-06-25 (docs/sessions/2026-06-25.md) Było: test_incident_lifecycle.py flaky przez state-leak — OBSERVER_STATE_FILE wyprowadzany przy imporcie, helpery patchowały STATE_DIR ale nie OBSERVER_STATE_FILE → checkpointy pisane na realny dysk /opt/homelab/state/ z ścieżkami otagowanymi numerem przebiegu pytest. Gate deploy.sh czerwony przy zdrowym kodzie. Naprawione: autouse monkeypatch fixture redirectujący WSZYSTKIE ścieżki stanu w tym OBSERVER_STATE_FILE; usunięto buggy _make_observer; posprzątano zatruty realny checkpoint. Weryfikacja: 6/6 przebiegów → 28 passed. Gate rzetelny.


deploy-node.sh — brak --env-file (bind 0.0.0.0) — NAPRAWIONE (commit 686aca7)

Data: 2026-06-25 Źródło: sesja 2026-06-25 (docs/sessions/2026-06-25.md) Było: deploy-node.sh nie przekazywał --env-file do compose up → zmienne .env (jak TAILSCALE_BIND_IP) interpolowały się do pustego stringa → bind 0.0.0.0 zamiast Tailscale IP. Potencjalna dziura na publicznym VPS. Naprawione: guard if [ -f "services/<svc>/.env" ] + --env-file per-serwis przed docker compose up. Worktree task/deploy-envfile-fix, merge ff-only.


Swap 24 GB na VPS — ZROBIONE

Data: 2026-06-22 (zgłoszone PENDING w sesji 2026-06-09 flota-recovery) Źródło: sesja 2026-06-22 (docs/sessions/2026-06-22.md) Było: VPS (3.7 GB RAM) z swap=0 → OOM 2026-06-01. Zrobione: /swapfile 4 GB aktywny i trwały (wpis w /etc/fstab); vm.swappiness=10 na żywo i w /etc/sysctl.conf. Weryfikacja: free -hSwap: 4.0Gi (0B used), /proc/swaps zawiera /swapfile. Uwaga: host-level one-off (nie czysto GitOps), udokumentowany jako celowy host-state w hosts/vps/host.yaml. Brak commitu kodu — zmiana host-side gotowcem.


Observer staleness — martwy node pokazywany NOMINAL

Data: 2026-06-08 (złapane), status: OTWARTY w sensie implementacji Problem: observer/supervisor trzyma ostatni znany stan; brak heartbeat TTL. Chelsty-infra milczy, ale status NOMINAL podważa zaufanie do panelu. Fix: heartbeat TTL → po przekroczeniu oznacz status stale lub down. Powiązane: brain-watchdog ślepy na per-node freshness. (Otwarty jako TODO implementacyjny — przeniesiony z sesji 2026-06-08)