# Monitoring coverage — co biega vs co jest monitorowane (recon 2026-07-14)

**Pytanie:** czy wszystkie serwisy floty są monitorowane?
**Odpowiedź krótka:** NIE. Biega **76 kontenerów** na 5 osiągalnych węzłach, alertowanych jest **15** (~20%).
**61 kontenerów** działa bez alertowania — w tym krytyczne: vaultwarden (hasła), forgejo (git = źródło
prawdy GitOps), immich (zdjęcia), homeassistant5 (dom), obie instancje NPM (ingress), agent-system-telegram-bot
(sam kanał alertów).

---

## Metadane zbierania danych

| | |
|---|---|
| **Data zebrania** | 2026-07-14, 17:16–17:17 UTC |
| **Metoda** | `docker ps --format '{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'` |
| **PIHA** | ssh `piha` (100.108.208.3, user oskar) — 17:16:37Z |
| **VPS** | ssh `vps` (100.95.58.48, user oskar) — 17:16:39Z |
| **SOLARIA** | lokalnie (sesja biegła na solarii) — 17:16:41Z |
| **LUSTRO** | ssh `pi@100.99.85.73` (uwaga: user **pi**, nie oskar) — 17:17:11Z |
| **SATURN** | ssh `oskar@100.121.168.72` — 17:17:21Z |
| **CHELSTY-INFRA / CHELSTY-HA** | OFFLINE — `tailscale status`: last seen 42 dni temu. **Do weryfikacji po powrocie online.** |
| **Kod** | branch `task/monitoring-coverage-recon` @ parent `d5139c9` (master) |

Sekcje A–C to **STAN NA DZIEŃ ZBIERANIA** (zmienny — przy odświeżaniu przewalidować w całości).
Sekcja E (klasyfikacja) i F (plan) to **DECYZJE** (trwałe — przy odświeżaniu tylko dopisać nowe kontenery).

---

## Jak działa monitoring w tym systemie (zweryfikowane w kodzie, stan na d5139c9)

Warstwy, od detekcji do alertu:

1. **node-agent** (`services/node-agent/src/node_agent.py`, `check_containers()` ~linia 305):
   czyta docker socket i emituje eventy dla **WSZYSTKICH kontenerów z restart policy**
   (`unless-stopped`/`always`/`on-failure`) — NIE filtruje po services.yaml.
   Emituje: `containers_not_running` (exited/dead), `healthcheck_failed` (running+unhealthy),
   `service_healthy` (running). Kontenery w stanie `created` pomija. **Uwaga (gap):** kontener
   w stanie `restarting` (crash-loop) nie pasuje ani do `exited/dead` ani do `running` —
   **crash-loop nie generuje żadnego eventu** (zaobserwowane na lustro/watchtower, patrz niżej).
   Nazwa kanoniczna = label `com.docker.compose.service` (fallback: nazwa kontenera).
   Zdeployowany na: piha, vps, solaria, lustro (na saturnie NIE).
2. **stability-agent** (`services/stability-agent/src/stability_agent.py`): również raportuje
   wszystkie kontenery (bez filtra) — eventy `containers_not_running`, `mqtt_unreachable`,
   `disk_usage_high` + publikacja do Redis. Biega na piha, vps, solaria (poza services.yaml!).
3. **Observer** (`scripts/observer/observer.py`, uruchamiany w control-plane@VPS): syntetyzuje
   eventy do `/opt/homelab/world/{services,nodes,incidents}.json`. Rejestruje **wszystko**
   (na dziś 116 wpisów w services.json, w tym wszystkie shadow kontenery). Widoczność ≠ alert.
4. **Supervisor** (`services/control-plane/src/supervisor.py`, `reconcile()` linia 288):
   generuje akcje (`redeploy`/`container_restart` → pending → Telegram → operator) **WYŁĄCZNIE
   dla serwisów zadeklarowanych w `hosts/<node>/services.yaml`** (`_load_desired_state()`,
   linie 161–195). Flaga `monitor: false` (linie 182–186) wyklucza serwis z desired-state —
   serwis jest udokumentowany, ale supervisor go pomija. **To jest jedyny filtr: detekcja jest
   flotowa, alertowanie tylko dla zadeklarowanych.** Dodatkowo supervisor routuje niezależnie
   od desired-state: eventy `ha_*` (z ha-diag-agent), `node_offline/stale/online` (liveness
   węzłów z heartbeatów node-agenta) i `disk_pressure`.
5. **fleet-prometheus** (`services/fleet-prometheus/`, VPS): scrape node_exporterów =
   **liveness HOSTA, nie serwisów**. Scrape'uje: vps, piha, solaria, lustro. Świadomie NIE:
   saturn (workstation, często off), chelsty (LTE, exporter DOWN — do zbadania osobno).
   Alert `NodeDown` (rules/liveness.yml) tylko dla `vps|piha` (solaria/lustro bywają planowo
   wyłączane). Bez Alertmanagera — alerty odbiera brain-watchdog.
6. **brain-watchdog** (`services/brain-watchdog/`, PIHA): pilnuje świeżości control-plane
   (/summary) + polluje `PROMETHEUS_URL/api/v1/alerts` i forwarduje do Telegrama.
7. **ha-diag-agent** (piha → HA "ken" localhost:8123; chelsty-infra → chelsty-ha): diagnostyka
   HA (websocket, integracje, encje) → supervisor routuje na alert/restart **niezależnie od
   services.yaml** (dziś wisi `container-restart-piha-homeassistant` mimo braku wpisu HA
   w hosts/piha/services.yaml).

**Definicja "monitorowany" w tym raporcie** = supervisor wygeneruje akcję/alert gdy serwis
padnie = wpis w `hosts/<node>/services.yaml` bez `monitor: false`, z nazwą zgodną z nazwą
compose-service kontenera.

---

## A. Stan faktyczny — pełne listy kontenerów (STAN NA 2026-07-14)

### PIHA — 42 kontenery

| Kontener | Obraz | Status | Porty |
|---|---|---|---|
| node-agent | node-agent-node-agent | Up 22h (healthy) | — |
| paperless | ghcr.io/paperless-ngx/paperless-ngx:2.14 | Up 47h (healthy) | 192.168.31.5:8210→8000 |
| vikunja | vikunja/vikunja:latest | Up 4d | 0.0.0.0:3456→3456 |
| vikunja-db | postgres:16-alpine | Up 4d (healthy) | 5432 (internal) |
| brain-watchdog | brain-watchdog-brain-watchdog | Up 4d (healthy) | — |
| paperless-db | postgres:16-alpine | Up 4d (healthy) | 192.168.31.5:5434→5432 |
| paperless-broker | redis:7-alpine | Up 4d (healthy) | 192.168.31.5:6380→6379 |
| llm-gateway | llm-gateway-llm-gateway | Up 11d (healthy) | 100.108.208.3:8080→8080 |
| kb-postgres | pgvector/pgvector:pg16 | Up 2w (healthy) | 0.0.0.0:5433→5432 |
| agent-system-webui | agent-system-webui | Up 2w | 0.0.0.0:18180→8080 |
| agent-system-runtime-materializer | agent-system-runtime-materializer | Up 2w | — |
| agent-system-telegram-bot | agent-system-telegram-bot | Up 2w | — |
| ha-diag-agent | ha-diag-agent-ha-diag-agent | Up 2w (healthy) | — |
| stability-agent | stability-agent-stability-agent | Up 2w (healthy) | — |
| agent-system-redis | redis:7 | Up 2w | 0.0.0.0:6379→6379 |
| nginxproxymanager-app-1 | jc21/nginx-proxy-manager:latest | Up 2w | 0.0.0.0:80-81→80-81, 443→443 |
| homeassistant5 | ghcr.io/home-assistant/home-assistant:stable | Up 2w | (host network, :8123) |
| immich_server | ghcr.io/immich-app/immich-server:release | Up 2w (healthy) | 0.0.0.0:2283→2283, 8081-8082 |
| immich_postgres | tensorchord/pgvecto-rs:pg14-v0.2.0 | Up 2w (healthy) | 5432 (internal) |
| immich_machine_learning | ghcr.io/immich-app/immich-machine-learning:release | Up 2w (healthy) | — |
| immich_redis | redis:6.2-alpine | Up 2w (healthy) | 6379 (internal) |
| forgejo_dind | docker:dind | Up 2w | 2375-2376 (internal) |
| forgejo | codeberg.org/forgejo/forgejo:7 | Up 12d | 0.0.0.0:3000→3000, 222→22 |
| forgejo-db-1 | postgres:14 | Up 2w | 5432 (internal) |
| zigbee2mqtt | koenkk/zigbee2mqtt | Up 3d | 0.0.0.0:8087→8080 |
| audiobookshelf | ghcr.io/advplyr/audiobookshelf:latest | Up 2w | 0.0.0.0:13378→80 |
| grafana | grafana/grafana-enterprise:latest | Up 2w | 0.0.0.0:9003→3000 |
| wikijs-db-1 | postgres:15-alpine | Up 2w | 5432 (internal) |
| code-server | lscr.io/linuxserver/code-server:latest | Up 2w | 0.0.0.0:8443→8443 |
| portainer | portainer/portainer-ce:latest | Up 2w | 0.0.0.0:8008→8000, 9009→9000 |
| prom | prom/prometheus:latest | Up 2w | 0.0.0.0:9090→9090 |
| homepage | ghcr.io/gethomepage/homepage:latest | Up 2w (healthy) | 0.0.0.0:3033→3000 |
| actual-server | actualbudget/actual-server:latest-alpine | Up 2w | 0.0.0.0:5006→5006 |
| mqtt-exporter-mqtt-exporter-1 | kpetrem/mqtt-exporter:latest | Up 2w | 0.0.0.0:9000→9000 |
| node-exporter | quay.io/prometheus/node-exporter:latest | Up 2w | — |
| wikijs-wiki-1 | ghcr.io/requarks/wiki:2 | Up 2w | 0.0.0.0:3300→3000 |
| owntracks-recorder | owntracks/recorder:latest | Up 2w | 0.0.0.0:8083→8083 |
| vaultwarden | vaultwarden/server:latest | Up 2w (healthy) | 0.0.0.0:3012→80 |
| pihole-exporter | ekofr/pihole-exporter:latest | Up 2w | 0.0.0.0:9617→9617 |
| fail2ban-prometheus-exporter-exporter-1 | registry.gitlab.com/hctrdev/fail2ban-prometheus-exporter:latest | Up 2w **(unhealthy)** | 0.0.0.0:9191→9191 |
| owntracks-prometheus-exporter-prometheus-owntracks-exporter-1 | linusgroh/prometheus-owntracks-exporter | Up 2w | 0.0.0.0:8780→80 |
| own-tracks-frontend-owntracks-frontend-1 | owntracks/frontend | Up 2w | 0.0.0.0:8084→80 |

Zmiany vs audyt 2026-06-30 (`docs/infra/inventory-2026-06-30.md`): **przybyły** paperless,
paperless-db, paperless-broker (Deploy 1, 2026-07-10); **zniknęły** diskover i elasticsearch
(w audycie 06-30 były w 33 shadow; dziś nie biegają). 06-30: 40 kontenerów → dziś: 42.

### VPS — 24 kontenery

| Kontener | Obraz | Status | Porty |
|---|---|---|---|
| control-plane-executor | control-plane-executor | Up 46m (healthy) | — |
| control-plane-observer | control-plane-observer | Up 46m (healthy) | — |
| control-plane-supervisor | control-plane-supervisor | Up 46m (healthy) | — |
| control-plane-ui | control-plane-operator-ui | Up 46m (healthy) | 0.0.0.0:18180→8080 |
| fleet-prometheus | prom/prometheus:v3.5.0 | Up 2w (healthy) | 100.95.58.48:9090→9090 |
| node-agent | node-agent-node-agent | Up 2w (healthy) | — |
| humanai-mailer | humanai-mailer | Up 2w | — |
| humanai-landing | humanai-landing | Up 2w | 80 (internal) |
| umami | ghcr.io/umami-software/umami:postgresql-latest | Up 2w (healthy) | 3000 (internal) |
| umami-db | postgres:16-alpine | Up 2w (healthy) | 5432 (internal) |
| node_exporter | quay.io/prometheus/node-exporter:latest | Up 5w | (host network :9100) |
| stability-agent | stability-agent-stability-agent | Up 5w (healthy) | — |
| outline-outline-1 | outlinewiki/outline:1.6.1 | Up 5w (healthy) | 0.0.0.0:3000→3000 |
| outline-postgres-1 | 4e6e670bb069 (anonimowy image ID!) | Up 5w (healthy) | 5432 (internal) |
| outline-redis-1 | redis:7-alpine | Up 5w (healthy) | 6379 (internal) |
| ai-cluster-service-ops-worker-1 | ai-cluster-service-ops-worker | Up 5w | — |
| ai-cluster-codex-worker-1 | ai-cluster-codex-worker | Up 5w | — |
| ai-cluster-openclaw-1 | ai-cluster-openclaw | Up 5w (healthy) | 0.0.0.0:8000→8000 |
| ai-cluster-planner-worker-1 | ai-cluster-planner-worker | Up 5w | — |
| ai-cluster-redis-1 | redis:7-alpine | Up 5w | 6379 (internal) |
| mosquitto | eclipse-mosquitto:2 | Up 5w | 100.95.58.48:1883→1883 |
| joplin-server | joplin/server:latest | Up 5w | 127.0.0.1:22300→22300 |
| npm | jc21/nginx-proxy-manager:latest | Up 5w | 0.0.0.0:80-81→80-81, 443→443 |
| joplin-db | postgres:18 (pre-release tag!) | Up 5w (healthy) | 5432 (internal) |

**gokapi NIE biega** mimo deklaracji w hosts/vps/services.yaml — supervisor poprawnie to wykrył:
w `/opt/homelab/actions/pending/` wisi `redeploy-vps-gokapi.json`. To dowód, że monitoring
desired-state DZIAŁA dla zadeklarowanych serwisów.

### SOLARIA — 5 kontenerów

| Kontener | Obraz | Status | Porty |
|---|---|---|---|
| paperless-worker | ghcr.io/paperless-ngx/paperless-ngx:2.14 | Up 5h (healthy) | 8000 (internal) |
| planner-agent | planner-agent-planner-agent | Up 5h (healthy) | — |
| node-agent | node-agent-node-agent | Up 5h (healthy) | — |
| stability-agent | stability-agent-stability-agent | Up 5h (healthy) | — |
| node_exporter | quay.io/prometheus/node-exporter:latest | Up 5h | (host network :9100) |

### LUSTRO — 4 kontenery

| Kontener | Obraz | Status | Porty |
|---|---|---|---|
| node-agent | node-agent-node-agent | Up 20h (healthy) | — |
| node-exporter | quay.io/prometheus/node-exporter:latest | Up 20h | (host network :9100) |
| piper-tts | piper-tts-rpi5 | Up 20h | 0.0.0.0:5000→5000, 10200→10200 |
| pi-watchtower-1 | containrrr/watchtower | **Restarting (1) — crash-loop!** | — |

**pi-watchtower-1 jest w crash-loopie** i przez lukę w node-agencie (stan `restarting` nie
generuje eventu, patrz sekcja o mechanizmie) — **nikt tego nie widzi**; world-state pokazuje
`lustro/watchtower healthy` (stary event `service_healthy`).

### SATURN — 1 kontener

| Kontener | Obraz | Status | Porty |
|---|---|---|---|
| agent-system-webui | agent-system-webui | Up 9h | 0.0.0.0:8080→8080 |

Stack control-plane z audytu 06-30 (executor/observer/supervisor/ui) już NIE biega na saturnie
— posprzątane. Saturn nie ma node-agenta, nie ma services.yaml, nie jest scrape'owany przez
fleet-prometheus → **węzeł całkowicie poza monitoringiem** (świadomie: workstation, często off).

### CHELSTY-INFRA / CHELSTY-HA — OFFLINE (42 dni)

Niedostępne przez Tailscale (last seen ~2026-06-02). Deklaracje: chelsty-infra = ha-diag-agent,
node-agent, mosquitto, zigbee2mqtt, frigate; chelsty-ha = homeassistant (`monitor: false`).
`world/services.json` nadal pokazuje serwisy chelsty-infra jako "healthy" — to STĘCHŁY stan
(status serwisów nie wygasa; liveness wygasa tylko na poziomie węzła). **Do weryfikacji po
powrocie online**, w tym: czy alerty `node_offline` dla chelsty odpaliły 42 dni temu.

---

## B. Stan deklarowany — hosts/<node>/services.yaml (STAN NA 2026-07-14)

| Węzeł | Zadeklarowane serwisy | monitor: false? |
|---|---|---|
| piha | ha-diag-agent, node-agent, brain-watchdog, vikunja, llm-gateway, kb-postgres | — |
| vps | node-agent, control-plane, node_exporter, fleet-prometheus, gokapi | — |
| solaria | node-agent | — |
| lustro | node-agent | — |
| saturn | **BRAK PLIKU services.yaml** | — |
| chelsty-infra | ha-diag-agent, node-agent, mosquitto, zigbee2mqtt, frigate | — |
| chelsty-ha | homeassistant | **TAK** (node-agent tam nie zdeployowany; HA kryty pośrednio przez MQTT) |

Razem: **18 deklaracji alertowalnych** (w tym 5 na offline'owym chelsty-infra) + 1 z monitor:false.

Uwaga: `stability-agent` ma katalogi w `hosts/{piha,solaria,vps}/runtime/`, ale NIE ma wpisów
w żadnym services.yaml — sam strażnik nie jest pilnowany.

---

## C. Luka — biega, ale poza alertowaniem (STAN NA 2026-07-14)

Pokrycie per węzeł (kontenery alertowane / biegające):

| Węzeł | Biega | Alertowane | Luka |
|---|---|---|---|
| PIHA | 42 | 6 (node-agent, ha-diag-agent, brain-watchdog, vikunja, llm-gateway, kb-postgres) | **36** |
| VPS | 24 | 7 (node-agent, control-plane ×4¹, node_exporter, fleet-prometheus) | **17** |
| SOLARIA | 5 | 1 (node-agent) | **4** |
| LUSTRO | 4 | 1 (node-agent) | **3** |
| SATURN | 1 | 0 | **1** |
| **RAZEM** | **76** | **15** | **61 (~80%)** |

¹ control-plane to 1 deklaracja pokrywająca 4 kontenery — node-agent na VPS probe'uje endpoint
HTTP :18180/summary i emituje `service_healthy`/`service_unhealthy` dla logicznego serwisu
"control-plane" (`_check_control_plane_health()`).

Częściowe pokrycie (nie liczone jako "alertowane" powyżej):
- **homeassistant5@piha** — ha-diag-agent pilnuje websocketu HA i supervisor generuje
  `container_restart`/`alert_only` niezależnie od services.yaml. Padnięcie HA zostanie
  zauważone. Brak jednak wpisu desired-state (drift "kontener zniknął" nie będzie wykryty).
- **node-exporter@piha** — jego padnięcie = `up==0` w fleet-prometheus = alert NodeDown
  (piha) przez brain-watchdog → Telegram. Pośrednio alertowany.
- **node_exporter@solaria, node-exporter@lustro** — scrape'owane, ale świadomie wyłączone
  z reguły NodeDown (węzły planowo wyłączane) → brak alertu.
- **gokapi@vps** — zadeklarowany, NIE biega; drift poprawnie wykryty (pending
  `redeploy-vps-gokapi.json`). Luka deploymentu, nie monitoringu.

---

## D. Nowe serwisy KB — weryfikacja wpisu z backlogu (f135365)

Backlog ("Nowe serwisy KB nie sa w monitoringu", 2026-07-12) **potwierdzony w połowie** —
paperless i paperless-worker faktycznie poza monitoringiem; gokapi/llm-gateway/kb-postgres są OK:

| Serwis | services/<name>/ w repo? | hosts/*/services.yaml? | Biega? | Alertowany? |
|---|---|---|---|---|
| paperless (+db, +broker) | ✓ (+ hosts/piha/runtime/paperless) | ✗ | ✓ piha (healthy) | **✗ LUKA** |
| paperless-worker | ✓ (owner_node: solaria) | ✗ | ✓ solaria (healthy) | **✗ LUKA** |
| nextcloud | ✓ (owner_node: piha, commit 7e5577c) | ✗ | ✗ nigdzie (nie zdeployowany) | ✗ (nie dotyczy — dodać wpis RAZEM z deployem) |
| gokapi | ✓ (owner_node: vps) | ✓ hosts/vps | **✗ nie biega** | ✓ — drift wykryty, wisi `redeploy-vps-gokapi` |
| llm-gateway | ✓ (owner_node: piha) | ✓ hosts/piha | ✓ piha (healthy) | ✓ |
| kb-postgres | ✓ (owner_node: piha) | ✓ hosts/piha | ✓ piha (healthy) | ✓ |

---

## E. Klasyfikacja niemonitorowanych kontenerów (DECYZJA — trwała)

Kategorie:
- **A (pełny GitOps):** krytyczny — docelowo compose w `services/`, service.yaml, deploy przez
  deploy.sh. Każde A dostaje NAJPIERW wpis monitoringowy (krok B) — tanio, od razu.
- **B (tylko monitoring):** deployment zostaje jak jest; dodać do `hosts/<node>/services.yaml`
  → supervisor alarmuje gdy padnie.
- **C (świadomie poza):** padnięcie nie boli / narzędzie jednorazowe. Uzasadnienie obowiązkowe.

Kontenery pomocnicze stacka (db/redis/broker) dziedziczą kategorię stacka — w services.yaml
deklaruje się serwis główny (padnięcie db zwykle wywala healthcheck głównego kontenera).

### PIHA (36 luk)

| Kontener | Co robi | Kategoria | Uzasadnienie |
|---|---|---|---|
| vaultwarden | menedżer haseł | **A** (najpierw B) | Hasła całej rodziny — padnięcie/utrata danych boli maksymalnie; musi być w GitOps z backupem i alertem |
| forgejo, forgejo-db-1, forgejo_dind | git hosting + CI | **A** (najpierw B) | Źródło prawdy GitOps + OIDC dla vikunji; bez forgejo nie ma deployów; już zidentyfikowane w audycie 06-30 (owner_node błędnie=saturn) |
| homeassistant5 | Home Assistant "ken" (dom) | **A** (najpierw B) | Automatyka domu; częściowo kryty przez ha-diag-agent (websocket), ale bez wpisu desired-state; wpis `homeassistant` w services.yaml domknie lukę |
| immich_server, immich_postgres, immich_machine_learning, immich_redis | zdjęcia rodzinne | **A** (najpierw B) | Dane niereprodukowalne (zdjęcia); padnięcie = brak backupu telefonów w tle, zauważalne późno |
| nginxproxymanager-app-1 | ingress LAN (*.kapala.org: HA, immich, paperless, cloud…) | **A** (najpierw B) | Pojedynczy punkt wejścia do wszystkich usług domowych; padnięcie = "wszystko nie działa" |
| paperless, paperless-db, paperless-broker | KB: dokumenty (OCR pipeline) | **B** | Już w GitOps (services/paperless + runtime override) — brakuje TYLKO wpisu w services.yaml; przypadek z backlogu f135365 |
| stability-agent | watchdog węzła | **B** | Repo-managed (runtime override jest); strażnik musi być pilnowany; dotyczy też vps i solarii |
| zigbee2mqtt | bridge Zigbee→MQTT (ken) | **B** | services/zigbee2mqtt istnieje (owner=piha); sensory domowe przestają raportować gdy padnie |
| agent-system-telegram-bot | kanał alertów Telegram | **B** | Jak padnie, ŻADEN alert nie dojdzie — a nikt się nie dowie; pilnować w pierwszej kolejności |
| agent-system-webui | operator UI (approve akcji) | **B** | Bez niego nie da się zatwierdzać akcji z przeglądarki; część pętli human-in-the-loop |
| agent-system-redis | redis dla agent-system | **B** | Zależność telegram-bota i webui; tani wpis |
| agent-system-runtime-materializer | materializacja runtime agentów | **B** | Część stacka agent-system; padnięcie cichaczem psuje odświeżanie stanu |
| wikijs-wiki-1, wikijs-db-1 | wiki | **B** | Notatki/dokumentacja domowa — padnięcie boli umiarkowanie, wpis jest tani |
| actual-server | budżet domowy | **B** | Dane finansowe; używany regularnie, padnięcie zauważalne przy wpisywaniu wydatków |
| prom | domowy Prometheus (LAN) | **B** | Źródło metryk domowych (HA, piha OS); padnięcie = dziura w historii; UWAGA: ma plaintext token HAOS (anti-pattern, patrz services/fleet-prometheus/prometheus.yml komentarz) |
| owntracks-recorder | lokalizacja rodziny (backend) | **C** | Hobby/eksperyment; padnięcie = luka w śladzie lokalizacji, nie boli operacyjnie |
| own-tracks-frontend-… | frontend owntracks | **C** | Czysta wizualizacja |
| owntracks-prometheus-exporter-… | exporter owntracks | **C** | Pomocniczy; objaw padnięcia = brak metryk, widoczny w grafanie |
| mqtt-exporter-mqtt-exporter-1 | exporter MQTT→prom | **C** | Jak wyżej |
| pihole-exporter | exporter pihole | **C** | Jak wyżej |
| fail2ban-prometheus-exporter-… | exporter fail2ban | **C** | Jak wyżej; DZIŚ unhealthy — naprawić lub usunąć (follow-up) |
| node-exporter | metryki hosta dla fleet-prometheus | **C** | Pośrednio alertowany: padnięcie = up==0 = NodeDown(piha) przez brain-watchdog |
| grafana | dashboardy | **C** | Wizualizacja; padnięcie zauważalne przy wejściu, zero utraty danych (provisioning/db na dysku) |
| homepage | strona startowa | **C** | Kosmetyka |
| audiobookshelf | audiobooki | **C** | Media; restart ręczny wystarczy |
| code-server | IDE w przeglądarce | **C** | Narzędzie dev, używane ad-hoc |
| portainer | GUI dockera | **C** | Narzędzie administracyjne ad-hoc; wręcz kandydat do usunięcia (GitOps ma być źródłem prawdy) |

### VPS (17 luk)

| Kontener | Co robi | Kategoria | Uzasadnienie |
|---|---|---|---|
| npm | publiczny ingress okit.pl | **A** (najpierw B) | Cała publiczna powierzchnia (share.okit.pl, vikunja.okit.pl…); manifesty A już istnieją na NIEZMERGOWANEJ gałęzi `feat/vps-service-migration` (commit 862c04a, cutover niewykonany) |
| mosquitto | broker MQTT ai-cluster | **B** | services/mosquitto istnieje ale owner_node=piha (błąd — biega na VPS od tygodni, audyt 06-30 poz. 3); szyna komunikacji ai-cluster |
| stability-agent | watchdog węzła | **B** | Jak na piha |
| outline-outline-1, outline-postgres-1, outline-redis-1 | wiki zespołowa | **B** | Notatki; manifesty na gałęzi feat/vps-service-migration; przy okazji naprawić anonimowy image ID postgresa (audyt 06-30 poz. 19) |
| joplin-server, joplin-db | sync notatek | **B** | Notatki osobiste; przy okazji zejść z postgres:18 pre-release (audyt 06-30 poz. 20) |
| ai-cluster-codex-worker-1, ai-cluster-planner-worker-1, ai-cluster-service-ops-worker-1, ai-cluster-openclaw-1, ai-cluster-redis-1 | workery agentowe | **B** | Padnięcie = agenci przestają mielić kolejki (objawia się późno); docelowo migracja compute na SOLARIA (CLAUDE.md), monitoring dodać już teraz |
| humanai-landing, humanai-mailer | strona publiczna + mailer | **B** | Publiczna wizytówka — padnięcie boli reputacyjnie i cicho (nikt nie patrzy); brak w repo (audyt 06-30 poz. 21) |
| umami, umami-db | analityka www | **C** | Statystyki odwiedzin; padnięcie = luka w danych analitycznych, nie boli operacyjnie |

### SOLARIA (4 luki)

| Kontener | Co robi | Kategoria | Uzasadnienie |
|---|---|---|---|
| paperless-worker | OCR/konsument kolejki KB | **B** | Przypadek wprost z backlogu f135365: "jesli worker padnie (…) dowiesz sie po tym, ze kolejka nie jest przetwarzana"; repo+runtime są, brakuje wpisu |
| planner-agent | agent planowania | **B** | services/planner-agent istnieje (owner=solaria ✓); audyt 06-30 poz. 14 |
| stability-agent | watchdog węzła | **B** | Jak na piha/vps |
| node_exporter | metryki hosta | **C** | Scrape'owany, ale solaria świadomie poza NodeDown (planowe wyłączenia); alerting wróci z anomaly-detection (backlog) |

### LUSTRO (3 luki)

| Kontener | Co robi | Kategoria | Uzasadnienie |
|---|---|---|---|
| piper-tts | TTS dla magic mirror | **C** | Padnięcie = lustro nie mówi; kosmetyka. Jeśli tani wpis — można podnieść do B przy okazji |
| node-exporter | metryki hosta | **C** | Jak solaria — świadomie poza NodeDown |
| pi-watchtower-1 | auto-update obrazów | **C** + **follow-up PILNY** | DZIŚ w crash-loopie, niewykrywalnym przez node-agent (luka na stan `restarting`). Decyzja operatora: naprawić albo USUNĄĆ (watchtower na edge = niekontrolowane pulle, sprzeczne z GitOps) |

### SATURN (1 luka)

| Kontener | Co robi | Kategoria | Uzasadnienie |
|---|---|---|---|
| agent-system-webui | operator UI (kopia dev?) | **C** | Saturn = workstation, często wyłączony; świadomie poza monitoringiem (jak w fleet-prometheus). Wyjaśnić czemu biega duplikat UI z pihy (follow-up) |

---

## F. Plan domknięcia — paczki na jedną sesję każda (DECYZJA — trwała)

**Zasada procesowa (z backlogu, do utrwalenia):** wpis w `hosts/<node>/services.yaml` +
`inventory/topology.yaml` to CZĘŚĆ deployu każdego nowego serwisu, nie osobny krok "kiedyś".

**⚠ Gotcha techniczna dla WSZYSTKICH paczek (sprawdzić przed każdym wpisem):** supervisor
matchuje klucz `node/nazwa-serwisu` z nazwą kanoniczną kontenera = label
`com.docker.compose.service` (node_agent.py `_canonical_container_name()`). Dla stacków
compose'owych to NIE jest nazwa kontenera: np. `nginxproxymanager-app-1` ma compose-service
**"app"**, `wikijs-wiki-1` → "wiki", stąd w world/services.json wiszą kolizyjne klucze typu
`piha/app`, `piha/db`, `piha/redis`, `piha/database`. Przed dodaniem wpisu sprawdzić na hoście:
`docker inspect <kontener> --format '{{ index .Config.Labels "com.docker.compose.service" }}'`
— i jeśli label koliduje/jest ogólnikowy, najpierw nadać `container_name` + porządny compose
project name albo poprawić observer (patrz follow-upy). Bez tego wpis będzie wiecznym
`missing_service` i zaspamuje kolejkę akcji.

### Paczka 1 — KB + już-zGitOps'owane serwisy (najtańsza-najważniejsza; czysta kategoria B)

Serwisy, które JUŻ są w repo (services/ + runtime overrides) — brakuje tylko rejestracji:

1. `hosts/piha/services.yaml`: + paperless, + stability-agent, + zigbee2mqtt
2. `hosts/solaria/services.yaml`: + paperless-worker, + planner-agent, + stability-agent
3. `hosts/vps/services.yaml`: + stability-agent, + npm, + mosquitto (i naprawić
   `services/mosquitto/service.yaml` owner_node: piha→vps oraz `services/npm` — potwierdzić
   że nazwa compose-service = "npm")
4. `inventory/topology.yaml`: dopisać ww. do sekcji węzłów
5. Weryfikacja: po 1–2 cyklach supervisora `world/services.json` ma klucze o zgodnych nazwach,
   kolejka pending NIE zawiera fałszywych `missing_service`; test driftu: `docker stop
   paperless-worker` na chwilę → pojawia się akcja → `docker start` → akcja auto-cancelled
   (test wymaga zgody operatora; alternatywnie tylko obserwacja biernie)
6. Domknięcie wiszącego driftu: zdeployować gokapi na VPS (akcja `redeploy-vps-gokapi` już
   czeka na approve) — UWAGA: brak `hosts/vps/runtime/gokapi/docker-compose.override.yml`
   z mem_limit (reguła VPS 4GB) — dodać przed deployem.

### Paczka 2 — krytyczne shadow na PIHA (kategoria B dla przyszłych A)

Wpisy monitoringowe (bez ruszania deploymentu!) dla: vaultwarden, forgejo, homeassistant
(kontener homeassistant5), immich, nginxproxymanager (compose-service prawdopodobnie "app" —
patrz gotcha!), agent-system (telegram-bot, webui, redis, runtime-materializer), wikijs,
actual-server, prom. Każdy wpis poprzedzony sprawdzeniem compose-label na hoście.
Jeśli labels kolidują (db/app/redis) — najpierw follow-up "canonical naming" (niżej) albo
wpis odroczony z adnotacją.

### Paczka 3 — VPS shadow (kategoria B + przygotowanie cutover)

1. Wpisy: outline, joplin, ai-cluster, humanai-landing, humanai-mailer (+ umami jeśli tanio)
2. Przejrzeć gałąź `feat/vps-service-migration` (commit 862c04a: manifesty npm/outline/joplin/
   ai-cluster; cutover NIE wykonany) — zdecydować: merge + cutover per checklista z CLAUDE.md,
   czy przepisać od nowa. UWAGA: CLAUDE.md już dziś twierdzi "All VPS services are now
   GitOps-managed" — to NIEPRAWDA na masterze; poprawić doc albo zmergować gałąź.

### Paczka 4 — pełny GitOps dla krytycznych (kategoria A; JEDEN serwis = JEDNA sesja)

Kolejność wg bólu: 1) vaultwarden, 2) forgejo, 3) npm@piha + npm@vps (cutover), 4) immich,
5) homeassistant5. Dla każdego: compose do `services/<name>/`, service.yaml, env.example,
healthcheck.sh, runtime override w `hosts/piha/runtime/`, deploy przez deploy.sh.
**Reguła migracji danych: ścieżki danych zostają NA MIEJSCU** (CLAUDE.md) — zero przenoszenia
wolumenów bez osobnego planu.

### Paczka 5 — porządki i decyzje C (follow-upy)

- **lustro/pi-watchtower-1**: naprawić albo usunąć (crash-loop TERAZ; watchtower vs GitOps)
- **piha/fail2ban-exporter**: unhealthy od ≥2 tyg. — naprawić albo usunąć
- **saturn/agent-system-webui**: wyjaśnić czemu biega; usunąć jeśli pozostałość dev
- **nextcloud**: przy deployu (osobna sesja per plan KB) wpis do services.yaml OD RAZU
- **chelsty**: po powrocie online powtórzyć inwentaryzację (sekcja "Jak odświeżyć")
- **saturn**: decyzja czy dostaje node-agenta + services.yaml (dziś świadomie poza)

### Follow-upy techniczne w kodzie monitoringu (poza zakresem tego reconu, do osobnych tasków)

1. **node-agent nie widzi stanu `restarting`** (crash-loop = cisza) — dodać gałąź dla
   `status == "restarting"` w `check_containers()` → event `containers_not_running` lub nowy
   typ `container_crash_loop`. Dowód: lustro/pi-watchtower-1.
2. **Kolizje nazw kanonicznych** w observerze/node-agencie: label `com.docker.compose.service`
   bez project-name daje klucze `piha/db`, `piha/app`, `piha/redis` (dziś w world/services.json
   są duplikaty typu `piha/immich_server` I `piha/immich-server`). Propozycja: klucz
   `<project>_<service>` albo preferować container_name gdy label jest ogólnikowy.
3. **Stęchły world-state dla offline węzłów**: serwisy chelsty-infra "healthy" 42 dni po
   zniknięciu węzła — status serwisu powinien degradować się razem z liveness węzła.
4. **Alerting solaria/lustro**: świadomie wyłączone z NodeDown; docelowo anomaly detection
   (istniejący backlog "Plan: Monitoring floty — Prometheus jako źródło prawdy").

---

## TL;DR

- **Biega:** 76 kontenerów (42 piha, 24 vps, 5 solaria, 4 lustro, 1 saturn); chelsty-infra/ha
  offline od ~42 dni — niezweryfikowane.
- **Alertowane:** 15 kontenerów przez 13 deklaracji desired-state (+ HA częściowo przez
  ha-diag-agent, + node-exporter@piha pośrednio przez NodeDown, + gokapi: zadeklarowany,
  niezdeployowany, drift POPRAWNIE wykryty i wisi w pending).
- **Luka:** 61 kontenerów (~80%) bez alertowania. Detekcja (node-agent/observer) widzi
  wszystko; filtr jest w supervisorze — alertuje TYLKO to, co wpisane w
  `hosts/<node>/services.yaml` (bez `monitor: false`).
- **Najboleśniejsze luki:** vaultwarden (hasła), forgejo (git), immich (zdjęcia),
  homeassistant5 (dom, częściowo kryty), npm ×2 (cały ingress), agent-system-telegram-bot
  (kanał alertów pilnujący wszystkiego innego, sam niepilnowany).
- **Znaleziska przy okazji:** lustro/watchtower w crash-loopie niewykrywalnym przez node-agent
  (luka na stan `restarting`); fail2ban-exporter@piha unhealthy; gokapi czeka na deploy;
  CLAUDE.md błędnie twierdzi że VPS jest w pełni GitOps (manifesty na niezmergowanej gałęzi).
- **Co najpierw:** Paczka 1 (wpisy dla paperless, paperless-worker, stability-agent ×3,
  zigbee2mqtt, npm, mosquitto, planner-agent — wszystko już w repo, tylko rejestracja)
  → Paczka 2 (krytyczne shadow PIHA) → Paczka 3 (VPS) → Paczka 4 (pełny GitOps po kolei).

---

## Jak odświeżyć ten dokument

1. Stan faktyczny (sekcja A) — jedna pętla (uwaga na userów ssh: lustro=pi, reszta=oskar;
   solaria/saturn wg tego skąd odpalasz):

   ```bash
   for target in oskar@100.108.208.3 oskar@100.95.58.48 oskar@100.100.231.104 \
                 pi@100.99.85.73 oskar@100.121.168.72 oskar@100.98.91.98; do
     echo "===== $target ====="
     ssh -o ConnectTimeout=10 -o BatchMode=yes "$target" \
       "date -u +%Y-%m-%dT%H:%M:%SZ; docker ps --format '{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'"
   done
   ```
   (kolejno: piha, vps, solaria, lustro, saturn, chelsty-infra; chelsty-infra pomiń jeśli offline)

2. Stan deklarowany (sekcja B): `grep -A2 "^services:" hosts/*/services.yaml` + ręcznie
   sprawdzić flagi `monitor: false`.
3. Luka (sekcja C): porównać 1 z 2 per węzeł; pamiętać że 1 deklaracja może pokrywać stack
   wielokontenerowy (control-plane, vikunja) i że match idzie po labelu
   `com.docker.compose.service`, nie po nazwie kontenera.
4. Wiszące akcje/drifty: `ssh vps ls /opt/homelab/actions/pending/`.
5. Sekcje E–F: NIE odtwarzać — tylko dopisać nowe kontenery i odhaczyć wykonane paczki.
   Zmienić klasyfikację wolno tylko świadomą decyzją (to sekcje DECYZYJNE).
