# 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 ` → 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 `error` → `System 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 `). 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: _control-plane-observer` → `set -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//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//.env" ]` + `--env-file` per-serwis przed `docker compose up`. Worktree `task/deploy-envfile-fix`, merge ff-only. --- ### Swap 2–4 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 -h` → `Swap: 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).