homelab-codex-ws/docs/backlog.md
Oskar Kapala 4055a8ffab docs(backlog): oznacz kroki 4+5 Prometheus jako ZROBIONE, dopisz tech-debt log PROMETHEUS_URL
Kroki 4 (reguły liveness d417000) i 5 (watchdog poll 62d6fc0) z planu monitoringu zamknięte.
Nowy wpis aktywny: brain-watchdog nie loguje PROMETHEUS_URL przy starcie — utrudnia weryfikację.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 19:29:24 +02:00

19 KiB
Raw Blame History

Tech-debt backlog

Centralny tracker tech-długu i znanych usterek. Wpisy ze sesji — dodawaj z datą i kontekstem.


Plan: Monitoring floty — Prometheus jako źródło prawdy

Data: 2026-06-22 Źródło: sesja 2026-06-22 (docs/sessions/2026-06-22.md) Decyzja: Prometheus (pull, up{}) zastępuje warstwę WYKRYWANIA liveness (node-agent shipper + rsync/ssh + event-store + observer prune/checkpoint + ręczne TTL) — przyczynę nawracających awarii (uid≠1000, ślepy ssh-mount, bloat ~242k eventów, race prune↔checkpoint, NOMINAL-bez-TTL). Osobny fleet-Prometheus pod GitOps, nie adopcja domowego instance PIHA. BEZ Alertmanagera — alert przez brain-watchdog. Placement: VPS. Granica: zostają supervisor (remediacja), observer/panel, ha-diag-agent, historia incydentów, out-of-band watchdog.

Zastępuje wcześniejszy szkic (blackbox + Alertmanager) z sesji 2026-06-17.

Kroki (priorytetowo):

  1. Scaffold serwisu fleet-prometheus pod GitOps (worktree task/fleet-prometheus, wzorzec services/vikunja/): compose + env.example + service.yaml + README + healthcheck.sh; rejestracja w hosts/vps/services.yaml + inventory/topology.yaml; exposure tailscale-internal; pusty scrape na start (self + lokalny node_exporter VPS).
  2. ZROBIONE (2026-06-26, commit 7d4014e) — Inwentaryzacja nodów floty 100.x do scrape. Dodane: piha/solaria/lustro (node: label), vps zachowany. Saturn pominięty (workstation), chelsty/chelsty-infra pominięte (node_exporter down z VPS → osobny wpis).
  3. Container-layer exporter (cAdvisor lub lekki docker-state) — node_exporter nie widzi kontenerów.
  4. ZROBIONE (2026-06-30, commit d417000) — Reguły liveness (up==0 for: 5m). rules/liveness.yml: NodeDown expr up{node=~"vps|piha"}==0 for 5m severity critical. Only always-on (vps, piha); solaria/lustro świadomie wykluczone (intermittent → anomaly detection). Deploy: fleet-prometheus Recreated (zmiana compose), reguła inactive=poprawnie.
  5. ZROBIONE (2026-06-30, commit 62d6fc0) — brain-watchdog: drugie wejście — poll Prometheus /api/v1/alerts (firing) → Telegram. Architektura A: dwa niezależne tory, mózg NIETKNIĘTY, debounce per-alert (klucz alertname:node w state.json). 12 testów pass. PENDING: potwierdzenie end-to-end przy pierwszym realnym firing.
  6. Rotacja tokenu HAOS w domowym prom (plaintext).
  7. Przepięcie observer / panel agents.okit.pl na Prometheus jako źródło — największy znak zapytania przy cutoverze.
  8. Parallel-run obok rury eventowej; cutover dopiero gdy Prometheus-truth się udowodni.

Aktywne

brain-watchdog: brak logu PROMETHEUS_URL przy starcie

Data: 2026-06-30 Źródło: sesja 2026-06-30 (docs/sessions/2026-06-30-prometheus-liveness.md) Problem: log startowy brain-watchdoga nie wypisuje PROMETHEUS_URL — utrudnia potwierdzenie, że polling jest aktywny. Diagnoza "działa" opiera się na braku błędów i zawartości .env, nie na logach. Pierwszy realny firing to jedyne pełne end-to-end potwierdzenie. Fix: dorzucić przy starcie jedną linię logu: PROMETHEUS_URL set, polling enabled lub analogicznego disabled — tak jak robione dla HEALTHCHECKS_URL.


Gotchas (z 2026-06-30 — migracja kapala.org → Cloudflare/wildcard)

Data: 2026-06-30 Źródło: sesja 2026-06-30 (docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md)

  • NPM custom WS config + Websockets Support: NIE wklejać proxy_http_version 1.1; do Advanced gdy Websockets Support = ON. NPM dodaje tę dyrektywę sam → duplikat → nginx -t failuje → plik proxy_host/.conf się NIE generuje → "unrecognized name" mimo dobrego certu. Objaw mylący (wygląda jak problem certu/DNS). Diagnoza: strings /data/database.sqlite | grep <domena> → pole nginx_err.

  • Cloudflare auto-proxy na import: CF proxuje A/CNAME przy dodaniu strefy. DKIM CNAME (fm1/2/3._domainkey) proxied = zepsuty podpis maila. Zawsze przełączyć na DNS only (szara chmurka) przed aktywacją. Reserved/CGNAT IP (Tailscale 100.x) CF wymusza DNS only automatycznie.


Migracja okit.pl → Cloudflare (większy projekt, firmowa domena)

Data: 2026-06-30 Źródło: sesja 2026-06-30 (docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md) Naprawi wszystkie wygasłe certy okit.pl naraz (HTTP-01 failuje przy mesh DNS): ap, audiobooks, code-server, dysk, forgejo, ha-embed, hagc, ngpm, node-red, okit.pl, pihole, ha.okit.pl. Wzorzec jak kapala.org: NS na CF, wildcard *.okit.pl przez DNS-01, przepiąć hosty. UWAGA: okit.pl ma usługi publiczne (foty) — rozdzielić mesh-only od publicznych. Ostrożnie — firmowa domena.


foty.kapala.org renew failuje (#47, expired 2026-06-19, HTTP-01)

Data: 2026-06-30 Źródło: sesja 2026-06-30 (docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md) Foty są PUBLICZNE celowo (zewn. dostęp za hasłem NPM) — port 80 powinien być dostępny, więc HTTP-01 powinno działać. Sprawdzić czemu failuje (DNS foty wskazuje na zły IP? port 80 zablokowany?). NIE przenosić na mesh.


Cleanup po błędnej ścieżce HA-Tailscale-addon (z 2026-06-29)

Data: 2026-06-30 Źródło: sesja 2026-06-30 (docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md)

  • ha-ken: Stop + Uninstall add-on Tailscale
  • Tailscale admin: usunąć node ha-ken (100.98.128.40)
  • 42.pl/okit.pl: usunąć rekord ha-ken → 87.205.110.38 (jeśli jest)

Stary ha.okit.pl (cert wygasł 6/28, teraz "Not Used")

Data: 2026-06-30 Źródło: sesja 2026-06-30 (docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md) Zostawić lub usunąć proxy host + DNS. Niepilne (martwy, nie szkodzi).


Stopniowa migracja usług domowych okit.pl → kapala.org (mesh-only)

Data: 2026-06-30 Źródło: sesja 2026-06-30 (docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md) dysk, audiobooks, budget, code-server, home, ha-embed, node-red... wg wzorca migracji usługi na kapala.org (patrz sesja: CF rekord A → 100.108.208.3 DNS only, NPM proxy host z wildcard *.kapala.org, Advanced PUSTE, weryfikacja grep+curl).


deploy-node.sh nie reloaduje config-driven serwisów (cicha rozbieżność deploy↔config)

Data: 2026-06-26 Źródło: sesja 2026-06-26 (docs/sessions/2026-06-26.md) Problem: po zmianie prometheus.yml (bez zmiany obrazu) deploy-node.sh NIE recreate'uje kontenera. Compose widzi "kontener działa, obraz ten sam" → zostawia Running, NIE podmienia configu → Prometheus trzyma stary config w pamięci. Deploy raportuje green, a zmiana configu nie wchodzi w życie. Dziś wymagało ręcznego docker compose ... up -d --force-recreate. Dotyczy KAŻDEGO serwisu config-driven bez zmiany obrazu (nie tylko Prometheus). Fix: po zmianie configu serwisu albo --force-recreate, albo POST /-/reload dla serwisów z lifecycle API (fleet-prometheus ma --web.enable-lifecycle). Rozważyć wykrywanie zmiany plików config w deploy i wymuszanie recreate.


Zbadać: chelsty + chelsty-infra node_exporter DOWN z VPS

Data: 2026-06-26 Źródło: sesja 2026-06-26 (docs/sessions/2026-06-26.md) Problem: przy inwentaryzacji targetów floty do fleet-prometheusa, node_exporter na chelsty i chelsty-infra był nieosiągalny z VPS — pominięte w scrape. To LTE edge (intermittent uplink), więc DOWN może być normą, ale wymaga rozróżnienia: brak node_exportera vs odcięty uplink vs zablokowany port. Fix: ustalić, czy node_exporter w ogóle działa na obu chelsty (compose/proces), czy jest osiągalny po Tailscale z VPS, i czy ma sens go scrape'ować mimo LTE (prawdopodobnie tak — up==0 na LTE = sygnał dla anomaly detection, nie fałszywy alarm).


Ghost kontenery w panelu (Problem B — pozostałość po project-name divergence)

Data: 2026-06-24 (wykryte), pozostaje otwarte po fixie Problemu A (2026-06-26) Źródło: sesja 2026-06-24 + 2026-06-25 + 2026-06-26 Problem: martwe kontenery ze starych project-name'ów (8547b46c0317_control-plane-supervisor, 12bd3059a70f_control-plane-observer itp.) są raportowane przez observera jako errorSystem Status ERROR w panelu mimo zdrowego realnego mózgu. Status: destrukcyjny deploy (Problem A) NAPRAWIONY (3b71707, patrz Zamknięte), ale ghosty z poprzednich rozjazdów wciąż wiszą. Fix: docker rm ghostów na VPS; rozważyć czyszczenie kontenerów z obcym project-name przy deployu.


Supervisor nie enqueue'uje akcji remediacji przy error-state

Data: 2026-06-25 (powtórka sygnału z 2026-06-19) Źródło: sesja 2026-06-25 (docs/sessions/2026-06-25.md) Problem: Action Queue pusta mimo System Status ERROR widocznego w panelu. Supervisor nie generuje container_restart / redeploy dla serwisów w stanie error. Objaw zaobserwowany co najmniej dwukrotnie — wymaga izolowanego dochodzenia. Podejrzane: supervisor może nie reagować na error-state jeśli źródłem są ghost kontenery (błędne project-name), nie realne health-check failures. Fix: zbadać osobno — sprawdzić, czy supervisor otrzymuje właściwe eventy od observera, czy ma własną logikę de-duplifikacji blokującą enqueue.


Rozjazd world-state observera: panel pokazuje serwis NOMINAL przed jego istnieniem

Data: 2026-06-25 Źródło: sesja 2026-06-25 (docs/sessions/2026-06-25.md) Problem: panel wykazał fleet-prometheus jako nominal na SOLARII zanim kontener w ogóle istniał — observer world_state rozjechany z dockerem. Artefakt rejestracji w manifeście bez realnego kontenera. Podobna klasa błędu jak ghost kontenery. Fix: observer powinien weryfikować faktyczny stan kontenera przy budowaniu world_state zamiast opierać się wyłącznie na zarejestrowanych serwisach.


🔴 BLOKUJĄCE — FLOTA-BOMBA: node-agent SSH mount ślepy po recreate

Data: 2026-06-11 Źródło: sesja lustro ssh shipping fix Problem: solaria/piha/chelsty to stare root kontenery node-agenta (piha Created 2026-05-27, uid 0) — sprzed dodania user: "1000:1000" do bazowego compose. Ich override montuje klucz SSH w /root/.ssh, co działa tylko dla uid 0. Pierwszy --force-recreate / reboot hosta / update obrazu przełączy kontener na uid 1000 (homelab, HOME=/home/homelab) i shipping eventów na VPS padnie z "Permission denied" — dokładnie jak na lustrze (naprawione a5a1352). ssh w _ship_events_to_vps() nie ma -i i szuka klucza w $HOME/.ssh. ⚠️ NIE RECREATE node-agenta na solaria/piha/chelsty przed fixem. Fix: ujednolicić mount → /home/homelab/.ssh we wszystkich hosts/*/runtime/node-agent/docker-compose.override.yml (wzór: hosts/lustro/) ALBO dodać -i $HOME/.ssh/id_rsa w _ship_events_to_vps().


ha-diag-agent deploy ZABLOKOWANY (placeholder token)

Data: 2026-06-11 Źródło: sesja — deploy config merged (5e9db5c), .env na piha utworzony (/opt/homelab/config/ha-diag-agent/.env, chmod 600) ale token = PLACEHOLDER. Blokada: chelsty-ha offline → brak tokenu i połączenia. Do decyzji: cel HA — chelsty-ha vs HA Ken (homeassistant5 na piha; z kontenera NIE localhost). Przed shadow_mode=false: target restartu w supervisorze = nazwa kontenera homeassistant5; curl endpointu HA z tokenem = HTTP 200.


observer-poison-quarantine — review brancha (78c9e4a)

Data: 2026-06-11 Źródło: sesja — patch Codexa zachowany na task/observer-poison-quarantine, NIE w master. Do zrobienia: zweryfikować, czy observer realnie wiesza się na malformed evencie (poison NIE był przyczyną awarii lustra — hipoteza niezweryfikowana, obalona przez verify-before-fix). Realny bug → merge; inaczej → drop brancha i worktree.


node_agent.py — drobne sprzątanie shippingu

Data: 2026-06-11 Źródło: sesja lustro ssh shipping fix

  1. Stale komentarz node_agent.py:546-548 — twierdzi, że kontener "runs as root"; nieaktualne od user: "1000:1000".
  2. Sukces shippingu na logger.debug → podnieść do info lub dodać licznik — działający shipping jest niewidoczny w logach przy INFO, co utrudniało diagnozę (cicha awaria wyglądała identycznie jak ciche działanie).

event-bloat: wyczyścić spłynięty backlog lustro na VPS

Data: 2026-06-11 Źródło: sesja — po fixie shippingu 7600+ plików backlogu spłynęło do /opt/homelab/events/lustro/ na VPS. Fix: wyczyścić stare pliki (observer już je przetworzył); docelowo polityka retencji w event-store.


rsync --omit-dir-times (node-agent)

Data: 2026-06-09 Źródło: flota recovery session Objaw: rsync exit code 23 po każdym push — set-times na katalogu /opt/homelab/events/ zwraca EPERM (oskar nie jest właścicielem katalogu; aerbot jest). Pliki są kopiowane poprawnie, ale exit 23 zaśmieca logi i może maskować prawdziwe błędy. Fix: dodać --omit-dir-times do wywołania rsync w node-agent.py. Lokalizacja: services/node-agent/src/node_agent.py — wywołanie rsync w pętli push. Update 2026-06-11: potwierdzone flotowo — każdy node loguje fałszywe "Event shipping failed" (rsync code 23) co cykl, mimo że pliki przechodzą; katalogi /opt/homelab/events/* na VPS należą do aerbot, klient nie ustawi na nich czasów.


Deklaratywny zapis oskar ∈ aerbot w manifeście VPS

Data: 2026-06-09 Źródło: flota recovery — root cause: oskar spoza grupy aerbot(1000) → rsync Permission denied Problem: przynależność do grupy jest zarządzana ręcznie (usermod -aG 1000 oskar ad-hoc). Brak gwarancji po przeinstalowaniu VPS lub zmianie usera. Fix: dodać do hosts/vps/host.yaml lub hosts/vps/capabilities.yaml sekcję users: oskar: groups: [aerbot] — i wyegzekwować w deploy/bootstrap skrypcie VPS. Alternatywa: zmienić właściciela /opt/homelab/events/ na oskar:oskar i zaktualizować node-agent deploy skrypty.


Rozdzielenie worktree per task (agent.sh)

Data: 2026-06-09 Źródło: sesja — homelab-codex-ws-node-onboarding używany raz dla task/node-onboarding, raz dla task/fix-event-bloat przez ręczne git checkout. Problem: jeden worktree współdzielony przez dwa branche = anty-wzorzec. git branch mogło wskazywać zły branch; + w listingu = pozornie "w innym worktree" ale nieprawda. Prowadzi do commitowania na złej gałęzi. Fix: egzekwować — jeden task = jeden worktree (agent.sh new <task-name>). Przy wejściu do worktree zawsze git branch --show-current i weryfikacja .agent-task. Długoterminowo: agent.sh new powinien odmawiać jeśli żądana gałąź jest już sprawdzona.


Zamknięte

🔴 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)

Anomaly detection liveness — mózg uczy się wzorca dobowego per node (pomysł 2026-06-26)

Idea: zamiast statycznych okien czasowych w regułach alertowych (np. "lustro 7-23"), mózg (supervisor/observer) czyta historię metryk z Prometheus (range queries / Grafana) i SAM wykrywa wzorzec dobowy każdego węzła. up==0 zgodne z nauczonym wzorcem offline (lustro zwykle off nocą, solaria nieregularnie) = NIE anomalia, nie alarmuj. up==0 odbiegające od wzorca = realna awaria → alert. Inteligencja w mózgu + dane jako źródło wzorca, nie sztywne godziny wpisywane ręcznie.

Warunek: wymaga TYGODNI historii metryk. fleet-prometheus postawiony 2026-06-25 → realne dopiero za ~2-4 tygodnie, gdy uzbiera się wzorzec dobowy.

Pułapka: uczący się system może przeoczyć realną awarię pokrywającą się z typowym oknem offline (statyczna reguła jest głupia, ale przewidywalna). Uwzględnić przy projektowaniu.

Na teraz: targety scrape'owane BEZ polityki alertowej, label tylko node:. Prometheus gromadzi historię. Anomaly detection = osobny świadomy projekt później (CC, z testami).