# 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 zamknięty 2026-07-02: poll POTWIERDZONY reconem (obraz zbudowany po `62d6fc0`, `PROMETHEUS_URL` w `.env` i w env kontenera, zero poll failed) — patrz `docs/infra/inventory-verify-2026-07-02.md`. ✅ END-TO-END UDOWODNIONE 2026-07-06 (Etap 0 cutoveru): tor Prometheus → watchdog → Telegram potwierdzony w produkcji testem `AlertTestEtap0` (firing → log polla → Telegram → rollback) — patrz `docs/sessions/2026-07-06.md`. 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 ### 🔴 npm@VPS panel admina :81 publicznie osiągalny z internetu **Data**: 2026-07-10 **Źródło**: sesja `scripts/npm/npm_api.py` (skrypt do zarządzania NPM przez REST API) **Problem**: `services/npm/docker-compose.yml` mapuje `81:81` bez ograniczenia do interfejsu — Docker bindem domyślnym wystawia to na `0.0.0.0`, czyli panel admina NPM@VPS jest osiągalny z publicznego IP (`135.181.153.108:81`), nie tylko przez Tailscale mesh (`100.95.58.48:81`). Panel admina (login+hasło, bez 2FA wymuszonego) nie powinien być publiczny. Brak `hosts/vps/runtime/npm/docker-compose.override.yml` ograniczającego bind. **Fix**: dodać override z bindem `127.0.0.1:81:81` (dostęp tylko przez Tailscale/SSH tunnel) albo `:81:81`, zachowując `80`/`443` publiczne (to jest ich rola). Zweryfikować po zmianie, że `npm_api.py --npm vps` nadal łączy się przez `100.95.58.48:81`. --- ### Ghosty B WRÓCIŁY — hash-prefixed control-plane na VPS nieposprzątane **Data**: 2026-07-06 **Źródło**: sesja 2026-07-06 (`docs/sessions/2026-07-06.md`) — snapshot panelu agents.okit.pl **Problem**: hash-prefixed kontenery control-plane znów widoczne na VPS — wpis „ZNIKNĘŁY (potwierdzone reconem 2026-07-02)" w Zamkniętych zdezaktualizowany. **Fix**: ręczny `docker rm` hash-prefixed kontenerów control-plane na VPS; przy okazji sprawdzić, skąd wróciły (fix A `3b71707` miał blokować źródło divergence). --- ### elasticsearch + diskover w stanie error na PIHA — observer zna usunięte serwisy **Data**: 2026-07-06 **Źródło**: sesja 2026-07-06 (`docs/sessions/2026-07-06.md`) — snapshot panelu agents.okit.pl **Problem**: elasticsearch i diskover usunięte z PIHA w module 0 (2026-07-02), ale observer wciąż je zna i raportuje `error` w panelu. World-state nie zapomina serwisów, które przestały istnieć — ta sama klasa błędu co „NOMINAL przed istnieniem" (patrz wpis z 2026-06-25). **Fix**: wyczyścić martwe wpisy z world_state / dodać wygaszanie serwisów nieobecnych w desired state i w dockerze. --- ### 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). --- ### 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. **Update 2026-07-02**: ghost kontenery (bug B) zniknęły z VPS — jeśli objaw wróci, hipoteza "źródłem są ghosty" jest już nieaktualna. UWAGA: ślepy supervisor na SATURN (brak mountu repo, patrz sesja 2026-07-02) to INNY przypadek — nie mylić z tym bugiem. --- ### 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 ### 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 `docs/infra/inventory-verify-2026-07-02.md` **Było**: martwe kontenery ze starych project-name'ów (`8547b46c0317_control-plane-supervisor` itp.) raportowane przez observera jako `error` → `System 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 `docs/infra/inventory-verify-2026-07-02.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**: `docs/infra/inventory-verify-2026-07-02.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: _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). --- ## Rozjazdy repo<->rzeczywistosc (z inwentaryzacji 2026-06-30) **Zrodlo**: `docs/infra/inventory-2026-06-30.md` (23 rozjazdy, pelna tabela tam). **Weryfikacja 2026-07-02**: `docs/infra/inventory-verify-2026-07-02.md` — bilans: 20 wciaz aktualnych, 2 zmienione, 1 wyjasniony (storage SOLARIA = partycja Windows dual-boot, NIE rozjazd — zdjety z listy). Ponizej te wymagajace akcji, pogrupowane wg ryzyka. Naprawa = osobny task/kilka. ### Grupa A — czyste docs, zero ryzyka - ✅ ZROBIONE (2026-07-02, commit `886bc85`) — **forgejo** `service.yaml owner_node`: saturn -> piha (biega na PIHA always-on) - ✅ ZROBIONE (2026-07-02, commit `886bc85`) — **mosquitto** `service.yaml owner_node`: piha -> vps (biega na VPS, nie na PIHA) - **capabilities SATURN**: RAM 8 -> 14GiB; dysk sd-card 64GB -> /dev/sda 159GB - **capabilities SOLARIA**: CPU 24 -> 32 nproc - **`hosts/saturn/services.yaml`** nie istnieje — 5 kontenerow bez deklaracji - **`hosts/vps/services.yaml`** niekompletne (4 z 9 z topology); **solaria** tez (brak planner-agent) ### Grupa B — wymaga decyzji - **npm x2**: PIHA (LAN ingress :80/:443) + VPS (public). Repo zna jedna (owner=vps). Decyzja: zostawic oba (intentional, wildcard cert via NPM@PIHA) czy usunac PIHA? Jesli oba zamierzone -> dodac piha do service.yaml + hosts/piha. - ✅ ZROBIONE (2026-07-02) — **control-plane na SATURN**: `docker compose down` (wolumeny zachowane). Supervisor byl SLEPY (brak mountu repo, WARNING loop "Hosts directory /repo/hosts does not exist" co 30s) — zero ryzyka zdublowanych remediacji przez te 3 dni. Jedyny control-plane = produkcyjny na VPS. - **ollama**: `service.yaml owner=solaria` ale NIE biega. Wdrozyc czy wyrzucic z repo? ### Grupa C — sprzatanie - **control-plane-ui healthcheck**: uzywa `curl` ktorego NIE MA w obrazie -> failuje w kolko -> UNHEALTHY + log spam (4.2G syslog na SATURN). Fix: wget/nc w healthcheck albo curl w Dockerfile. (Przyczyna rozjazdu #6 znaleziona przy gaszeniu dysku.) - **homeassistant5 na PIHA** (HA "ken" :8123) niedeklarowany -> dodac do hosts/piha + topology - **VPS**: outline-postgres-1 anonimowy image (4e6e670bb069) -> named tag; humanai-landing/mailer/umami do repo. ~~joplin-db postgres:18 -> 17/16~~ (ocena zdezaktualizowana 2026-07-02: PG18 GA od 09/2025, nie pre-release — bez akcji) - **PIHA: 33 shadow kontenery** poza GitOps (immich, vaultwarden, wikijs, actual, audiobookshelf, elasticsearch, grafana, prom, portainer, code-server, diskover...) -> audyt + stopniowo do hosts/piha/services.yaml - **zigbee2mqtt** topology mowi chelsty-infra, biega na PIHA -> poprawic topology - **stability-agent / node_exporter** owner_node single, biegaja wielomiejscowo -> per-host ### Followupy z weryfikacji + rozbrajania min (2026-07-02) **Zrodlo**: `docs/infra/inventory-verify-2026-07-02.md` + sesja 2026-07-02. Zgloszone przy fixie owner_node (`886bc85`), swiadomie NIE ruszone — osobne decyzje. - **forgejo** brak wpisu w `hosts/piha/services.yaml`; **mosquitto** brak w `hosts/vps/services.yaml` — schemat hostowy wymaga role/exposure/depends_on (miny #2/#3/#16 z audytu). - **mosquitto na VPS bez mem_limit override** w `hosts/vps/runtime/` — narusza konwencje CLAUDE.md (kazdy serwis VPS deklaruje mem_limit). - **drugi mosquitto na chelsty-infra** (offline'owa instancja) — pojedyncze `owner_node` jej nie opisuje; wzorzec per-host jak stability-agent / node_exporter (miny #17/#18). - **topology.yaml:75**: mosquitto zadeklarowany tez jako komponent ai-cluster — rozstrzygnac, czyj jest broker :1883. - **pi-watchtower-1 na LUSTRO w restart-loopie** (nowe z reconu; node-agent healthy). - **alias `lustro` nie rezolwuje z SOLARII** (nowe z reconu). - **fleet-prometheus bez formalnego override mem_limit** w `hosts/vps/runtime/` — limit siedzi w bazowym compose (kosmetyka). ### Po odchudzaniu PIHA (2026-07-02, faza 2 modulu 0) - **llm-gateway: zlokalizowac/zarchiwizowac zrodlo** — kod (wlasny FastAPI router -> Ollama@SOLARIA) moze zyc TYLKO w `/opt/llm-gateway` na PIHA, bez gita; przeszukanie PIHA i repo nie znalazlo innej kopii. Zarchiwizowac do repo/Forgejo zanim padnie nosnik. - **Prometheus@PIHA: target llm-gateway blednie nazwany `watchtower`** — celuje w :8080 i odpytuje `/v1/metrics`, dostaje wieczne 404 (llm-gateway nie serwuje metryk). Naprawic nazwe/endpoint albo usunac target. ### Tech debt SATURN (z gaszenia dysku 2026-06-30) - **`/opt/anaconda3` 16G** — najwiekszy pojedynczy zjadacz dysku (env-y Pythona). Decyzja Oskara kiedy/czy czyscic. - Dysk 91% -> 83% ugaszone (docker prune + journal + syslog), ale `/home` zaszyfrowany i ciasny strukturalnie. SATURN dzwiga dev + drugi control-plane + agent-webui — napiecie. ## Tech-debt: globalny porządek uid/gid/uprawnień we flocie (2026-07-10) **Diagnoza.** Flota NIE ma spójnej mapy uid/gid. "oskar" ma różne uid per host (PIHA: 1004, inne hosty: prawdopodobnie 1000/inne). Kontenery agentów zakładają uid 1000 (user "homelab"). Bind-mounty przenoszą SUROWE uid (nie nazwy) między hostem a kontenerem → gdy uid hosta ≠ uid zakładany przez kontener, pliki stają się "cudze" i wybucha cicha awaria (klucz nieczytelny, rsync nie tworzy plików, socket permission denied). To NIE są przypadki — to systemowy brak kanonicznej mapy uid/gid. **Historia incydentów (dowód że systemowe):** - 2026-07-10: node-agent PIHA (uid 1000 homelab) montował /home/oskar/.ssh (pliki uid 1004) → "Load key id_rsa: Permission denied" → rsync padał → 21 dni bez eventów (wykryte przez shadow-read). Fix: dedykowany /opt/homelab/agent-ssh chown 1000. - Wcześniej: oskar spoza grupy `aerbot` na VPS → rsync push nie tworzył plików → brak cleanup → 8-dniowa cicha awaria floty. Fix: usermod -aG aerbot oskar. - 2026-07-10 (świeże, PENDING): node-agent PIHA "Docker unavailable: PermissionError(13)" po recreate — agent nie czyta /var/run/docker.sock (grupa docker/uid). Osobny od shippingu (nie blokuje eventów), ale ten sam rodzaj problemu — do naprawy (grupa docker w kontenerze / gid socketu). - LUSTRO uid pi=1000 vs PIHA oskar=1004 — różne uid "pierwszego usera" per host. **Kierunek naprawy (do rozważenia, osobny projekt):** - Ustalić KANONICZNE uid/gid per rola: agent=1000 wszędzie; dedykowane grupy dla współdzielonych zasobów (aerbot dla events/rsync-sink na VPS, docker dla socketu). - Audyt `id ` na KAŻDYM hoście floty (saturn/solaria/piha/vps/lustro) — zmapować realne uid/gid, udokumentować rozjazdy. - Rozważyć deklaratywny zapis oczekiwanych uid/gid w hosts/*/host.yaml lub capabilities.yaml (żeby deploy mógł weryfikować/wymuszać spójność). - Agenci NIE powinni montować prywatnego .ssh użytkownika — zawsze dedykowany katalog z własnym kluczem pod właściwym uid (wzorzec z fixa 2026-07-10). ## Bug: checkpoint observera po ścieżce leksykalnej — kruchy, zatruwa węzeł na zawsze (2026-07-12) **Objaw.** PIHA była "martwa" dla observera ~34 dni mimo działającego node-agenta. Eventy dojeżdżały na VPS (7344 plików w /opt/homelab/events/piha/), ale observer ich NIE konsumował — `last_seen` nie drgnął, shadow-read logował `SHADOW_LIVENESS_MISMATCH node=piha event=dead prom=up` z rosnącym wiekiem. **Root cause.** `observer_checkpoint.json` trzyma per-węzeł ostatnio przetworzoną ŚCIEŻKĘ i porównuje ją LEKSYKALNIE (stringowo), awansując tylko "do przodu". Checkpoint PIHA utknął na `evt-unknown-1781254800-ha_update_available-homeassistant-951.json` (event z HA, który wpadł do katalogu piha/ z node="unknown"). Nowe eventy nazywają się `evt-piha--...`, a leksykalnie **"evt-piha-…" < "evt-unknown-…"** (bo `p` < `u`), więc KAŻDY nowy event był uznawany za starszy niż checkpoint i pomijany. **Fix doraźny (zastosowany).** Usunięcie wpisu `piha` z node_checkpoints + restart observera → 7344 eventy przetworzone, `last_seen_age` spadł z 2 082 036 s (~24 dni) do 19 s, status=online/fresh, mismatch zniknął. **Fix systemowy (DO ZROBIENIA).** Porównanie po ścieżce jest z gruntu kruche — wystarczy JEDEN plik o "większej" nazwie (inny node w katalogu, inny prefiks) żeby trwale zablokować węzeł. Checkpoint powinien opierać się na **timestampie** (jest w nazwie: `evt---`) albo mtime pliku, nie na kolejności alfabetycznej. Dodatkowo: zbadać, czemu eventy z node="unknown" (HA) trafiają do katalogu innego węzła. ## Bug: deploy-local.sh control-plane pada na ghost-kontenerach i zostawia mózg rozłożony (2026-07-12) **Objaw.** `bash deploy-local.sh` przerwał się w połowie: `Error response from daemon: No such container: 5ee6fec5455d_control-plane-ui` → deploy abort → **ZERO kontenerów control-plane** (`docker ps -a` pusty). Mózg całkowicie down. Zdarzyło się DWA RAZY tego samego dnia. Ratunek: ponowny `deploy-local.sh` (gdy ghost już zniknął, compose stawia czysto). **Root cause (podejrzenie).** Ten sam COMPOSE_PROJECT_NAME divergence / ghost hash-prefixed containers, co przy `deploy.sh vps` (naprawione guardem 3b71707). Compose próbuje Recreate kontenera pod nazwą `_control-plane-ui`, której Docker już nie zna → błąd → `set -e` przerywa deploy w połowie. **Fix (DO ZROBIENIA).** deploy-local.sh powinien być odporny: cleanup ghostów przed recreate (`docker compose down --remove-orphans` albo jawne usunięcie hash-prefixed kontenerów), ewentualnie jawny `-p` (COMPOSE_PROJECT_NAME) żeby nazwy były deterministyczne. Deploy mózgu NIE MOŻE zostawiać control-plane w stanie zero-kontenerów. ## Fix: paperless-worker@SOLARIA — dwa bugi configu, naprawione i zweryfikowane na żywo (2026-07-12) **Kontekst.** Moduł 3 (`services/paperless-worker/`, split-host OCR worker) był zdeployowany i brał zadania z kolejki, ale miał dwa bugi w compose: 1. **`command: celery ...` nie odpalał celery.** Obraz paperless-ngx (`/sbin/docker-entrypoint.sh`) routuje każdy argument NIE zaczynający się od `/` do `manage.py` — więc `celery` lądował jako nieznana subkomenda Django, nie jako program. Fix: `command: /usr/sbin/gosu paperless /usr/local/bin/celery ...` (ścieżka absolutna wchodzi w gałąź `exec "$@"` entrypointu; `gosu paperless` z przodu bo ta gałąź nie dostaje automatycznego gosu, inaczej proces poszedłby jako root i zepsuł właściciela plików na NFS). 2. **Brak współdzielonego `SCRATCH_DIR`.** Paperless@PIHA staguje wgrywany plik w `/tmp/paperless` (domyślny `SCRATCH_DIR`) i niesie tę ścieżkę w payloadzie zadania celery jako ścieżkę absolutną. Worker@SOLARIA miał własny, lokalny `/tmp/paperless` — gdy odbierał zadanie zamiast workera PIHA, padał `Cannot consume ...: File not found`. Fix: NFS volume `paperless_scratch` (ten sam wzorzec co `data/media/consume`) na `/opt/homelab/data/paperless/scratch` (już istniał na PIHA, `chown 1000:1000`), mount na `/tmp/paperless` po obu stronach. Oba fixy + uzasadnienie: `services/paperless/docker-compose.yml`, `services/paperless-worker/docker-compose.yml`, `services/paperless-worker/README.md`. Zweryfikowane end-to-end na żywo (branch `task/paperless-worker-fix`, jeszcze niezmergowany do master w momencie pisania tego wpisu): 3 dokumenty testowe wrzucone do `consume/` na PIHA, jeden odebrany i dokończony przez worker@SOLARIA (log: `ocrmypdf`/`tesseract` → `ConsumeTaskPlugin completed with: Success`), zero `File not found`. Dokumenty testowe usunięte po teście (`document.delete()` + ręczny cleanup plików) — produkcyjne 6 dokumentów nietknięte. **Otwarte (świadomie odłożone, nie blokuje działania):** - `hosts/solaria/services.yaml` i `inventory/topology.yaml` nie mają wpisu `paperless-worker` (SOLARIA ma tam tylko `node-agent`) — było zaplanowane w cutover checkliście README jako krok "przy deployu", ale nigdy nie zrobione. Bez tego wpisu supervisor/observer nie widzą tego serwisu w desired-state — drift (np. worker padnie i nie wstanie) nie zostanie automatycznie wykryty przez agent system, tylko przez brak przetwarzania kolejki. - Test formalnego fallbacku (stop worker@SOLARIA → kolejka mieli na PIHA → start → drenaż) nie był wykonany w tej sesji — mechanizm nie zmienił się tym fixem (był już OK), ale warto zweryfikować przy okazji. ## Nowe serwisy KB nie sa w monitoringu (desired-state) **Data:** 2026-07-12 **Problem:** Zdeployowane serwisy filaru dokumentow nie maja wpisow w `hosts/*/services.yaml` i `inventory/topology.yaml`, wiec supervisor/observer ich NIE WIDZA w desired-state: - `paperless` + `paperless-db` + `paperless-broker` (PIHA) — Deploy 1, 2026-07-10 - `paperless-worker` (SOLARIA) — Deploy 2, 2026-07-12 (SOLARIA ma tam tylko `node-agent`) **Skutek:** drift nie jest wykrywany. Jesli worker padnie i nie wstanie, albo paperless przestanie dzialac — agent system tego nie zglosi. Dowiesz sie dopiero po tym, ze kolejka nie jest przetwarzana / strona nie odpowiada. **Fix:** dodac wpisy do `hosts/piha/services.yaml`, `hosts/solaria/services.yaml`, `inventory/topology.yaml`. Zweryfikowac ze observer/supervisor je widza (healthcheck, liveness). Dotyczy tez przyszlych: nextcloud, gokapi. **Zasada na przyszlosc:** rejestracja w services.yaml/topology to CZESC deployu, nie osobny krok "kiedys" — inaczej kazdy nowy serwis to slepy punkt monitoringu.