Two config bugs found on the already-deployed split-host OCR worker (module 3): (1) `command: celery ...` was routed through manage.py by the image entrypoint because it didn't start with "/" — fixed with an absolute gosu+celery path. (2) SCRATCH_DIR (/tmp/paperless) was not shared over NFS like data/media/consume, so tasks picked up by worker@SOLARIA instead of worker@PIHA failed with "File not found" — fixed by adding a paperless_scratch NFS volume/bind mount on both sides. Verified live on PIHA + SOLARIA: test PDFs dropped into consume/ were split across both workers, the SOLARIA-picked task completed OCR with zero File not found errors, test documents cleaned up afterward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
35 KiB
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):
- Scaffold serwisu
fleet-prometheuspod GitOps (worktreetask/fleet-prometheus, wzorzecservices/vikunja/): compose +env.example+service.yaml+ README +healthcheck.sh; rejestracja whosts/vps/services.yaml+inventory/topology.yaml; exposuretailscale-internal; pusty scrape na start (self + lokalnynode_exporterVPS). - ✅ ZROBIONE (2026-06-26, commit
7d4014e) — Inwentaryzacja nodów floty100.xdo 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). - Container-layer exporter (cAdvisor lub lekki docker-state) —
node_exporternie widzi kontenerów. - ✅ 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. - ✅ 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 po62d6fc0,PROMETHEUS_URLw.envi w env kontenera, zero poll failed) — patrzdocs/infra/inventory-verify-2026-07-02.md. ✅ END-TO-END UDOWODNIONE 2026-07-06 (Etap 0 cutoveru): tor Prometheus → watchdog → Telegram potwierdzony w produkcji testemAlertTestEtap0(firing → log polla → Telegram → rollback) — patrzdocs/sessions/2026-07-06.md. - Rotacja tokenu HAOS w domowym prom (plaintext).
- Przepięcie observer / panel
agents.okit.plna Prometheus jako źródło — największy znak zapytania przy cutoverze. - 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 <tailscale_ip>: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 <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).
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
- Stale komentarz
node_agent.py:546-548— twierdzi, że kontener "runs as root"; nieaktualne oduser: "1000:1000". - Sukces shippingu na
logger.debug→ podnieść doinfolub 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
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-importpo 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-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 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) — forgejoservice.yaml owner_node: saturn -> piha (biega na PIHA always-on) - ✅ ZROBIONE (2026-07-02, commit
886bc85) — mosquittoservice.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.yamlnie istnieje — 5 kontenerow bez deklaracjihosts/vps/services.yamlniekompletne (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=solariaale NIE biega. Wdrozyc czy wyrzucic z repo?
Grupa C — sprzatanie
- control-plane-ui healthcheck: uzywa
curlktorego 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 whosts/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_nodejej 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
lustronie 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-gatewayna 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/anaconda316G — najwiekszy pojedynczy zjadacz dysku (env-y Pythona). Decyzja Oskara kiedy/czy czyscic.- Dysk 91% -> 83% ugaszone (docker prune + journal + syslog), ale
/homezaszyfrowany 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
aerbotna 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 <user>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-<ts>-..., 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-<node>-<unixts>-<type>) 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ą <hash>_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:
command: celery ...nie odpalał celery. Obraz paperless-ngx (/sbin/docker-entrypoint.sh) routuje każdy argument NIE zaczynający się od/domanage.py— więccelerylą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 paperlessz przodu bo ta gałąź nie dostaje automatycznego gosu, inaczej proces poszedłby jako root i zepsuł właściciela plików na NFS).- Brak współdzielonego
SCRATCH_DIR. Paperless@PIHA staguje wgrywany plik w/tmp/paperless(domyślnySCRATCH_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 volumepaperless_scratch(ten sam wzorzec codata/media/consume) na/opt/homelab/data/paperless/scratch(już istniał na PIHA,chown 1000:1000), mount na/tmp/paperlesspo 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.yamliinventory/topology.yamlnie mają wpisupaperless-worker(SOLARIA ma tam tylkonode-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.