homelab-codex-ws/docs/backlog.md
Oskar Kapala 48107ef2c1 docs(backlog): zamknij deploy.sh vps bug + 2 nowe wpisy (prom-reload, chelsty node_exporter)
- 🔴 deploy.sh vps↔control-plane → Zamknięte (fix 3b71707, guard deploy-local.sh)
- krok 2 planu floty (targety 100.x) → ZROBIONE (7d4014e)
- NOWE: deploy-node.sh nie reloaduje config-driven serwisów (cicha rozbieżność deploy↔config)
- NOWE: zbadać chelsty/chelsty-infra node_exporter DOWN z VPS
- Problem B (ghost kontenery) pozostaje otwarty osobno

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:30:26 +02:00

303 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. Reguły liveness (`up==0 for: 5m`) w Prometheusie.
5. brain-watchdog: drugie wejście — odpyt Prometheus `/api/v1/alerts` (`firing`) → Telegram,
bez ruszania działającego toru `token → chat_id`.
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
### `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 <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-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/<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 -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).