Decyzja operatora 2026-08-04: /opt/homelab to standardowa sciezka deploy rootu homelaba, ta sama na kazdym wezle, opisana wprost w publicznej czesci modelu (standards, service-model, observer, event-system). Nie ujawnia sekretow ani topologii, wiec zostaje na stronie publicznej. scripts/kb/check_whitelist.txt: wpis `/opt/homelab` z uzasadnieniem i data. Zakres wyjatku jest waski — sprawdzone, ze wycisza wylacznie warianty `/opt/homelab/...`; `/home/oskar/...`, `/opt/other/...`, adresy RFC1918 i porty dalej zapalaja czerwone. gen_pages.py: load_whitelist() obcina komentarz `#` w dowolnym miejscu linii, nie tylko na jej poczatku — wyjatek ma stac obok uzasadnienia, a nie osobno. Zaden ze skanowanych wzorcow nie zawiera `#`, wiec obciecie jest bezpieczne. kb/runbooks/kb-site-deploy.md §2: zapisany aktualny stan bramki (exit 0, 22 trafienia wyciszone) zamiast opisu decyzji do podjecia. Po zmianie: python3 scripts/kb/gen_pages.py --check -> CZYSTO, exit 0.
8.7 KiB
| okf | type | visibility | status | updated | links | |||
|---|---|---|---|---|---|---|---|---|
| 0.1 | runbook | private | active | 2026-08-04 |
|
kb-site — deploy, publikacja treści, ingress (kb.okit.pl)
Serwis: services/kb-site/ (nginx:alpine na PIHA, host port 8250).
Generator: scripts/kb/gen_pages.py. Publiczny adres: https://kb.okit.pl.
Kolejność ma znaczenie: DNS → NPM host → cert. Certyfikat Let's Encrypt leci challenge'em HTTP-01, więc rekord A musi już wskazywać na łącze domowe, a vhost musi już odpowiadać na porcie 80, zanim zamówisz cert.
0. Wymagania wstępne
- PIHA ma checkout repo w
~/homelab-codex-wsi działającego Dockera. - npm@PIHA działa (
http://192.168.31.5:81), port 80/443 z routera jest przekierowany na PIHA — tak samo jak dlavikunja.okit.pl. scripts/npm/.envwypełniony poświadczeniami (patrzkb/runbooks/npm-api.md).- Strefa
okit.plw Cloudflare (patrzkb/phases/okit-cloudflare.md).
1. Deploy serwisu na PIHA
# na PIHA
cd ~/homelab-codex-ws
git pull
docker compose -f services/kb-site/docker-compose.yml up -d
Bez .env i bez override'a — serwis nie ma konfiguracji ani sekretów.
Kontener wstanie, ale healthcheck będzie unhealthy do czasu wgrania treści
(pusty wolumen = brak index.html). To normalne między krokiem 1 a 3.
docker ps --filter name=kb-site
docker volume ls | grep kb-site # kb-site_kb-site_content
2. Generacja treści + kontrola wycieków (SATURN / SOLARIA)
Generujemy w checkoucie repo, na węźle z którego pracujesz — nie na PIHA.
python3 scripts/kb/gen_pages.py --base-url https://kb.okit.pl
python3 scripts/kb/gen_pages.py --check
echo $? # 0 = czysto, 1 = trafienia
--check skanuje wygenerowany HTML, nie źródła: adresy IP (RFC1918,
Tailscale 100.64/10, publiczne v4/v6), porty 1024-65535, ścieżki /home/
i /opt/, długie hexy i ciągi base64 wyglądające na tokeny.
Kontrola jest bramką publikacji — nie kopiuj treści na PIHA przy exit 1.
Stan na 2026-08-04 (11 dokumentów public): exit 0 — czysto, 22 trafienia
wyciszone whitelistą. Wszystkie wyciszone to path-host /opt/homelab/...
w dokumentach opisujących layout runtime (subsystems/observer,
subsystems/standards, subsystems/service-model, subsystems/event-system,
subsystems/agent-operating-procedures, subsystems/action-approval-model,
runbooks/node-onboarding). Zero IP, zero portów, zero tokenów.
Decyzja operatora 2026-08-04: /opt/homelab jest świadomym wyjątkiem —
standardowa ścieżka deploy rootu, ta sama na każdym węźle, nie ujawnia
sekretów ani topologii. Wpis siedzi w scripts/kb/check_whitelist.txt razem
z uzasadnieniem. Wycisza wyłącznie warianty /opt/homelab/...; /home/...,
adresy, porty i tokeny dalej zapalają czerwone.
Każdy kolejny wyjątek podlega tej samej regule: najpierw próba wyczyszczenia źródła, wpis do whitelisty dopiero jako świadoma decyzja, zawsze z komentarzem i datą.
3. Wgranie treści do wolumenu (helper alpine)
Wzorzec z services/narty27/README.md, rozszerzony z jednego pliku na drzewo:
docker cp nie zapisze do montowania :ro, więc treść wjeżdża jednorazowym
kontenerem pomocniczym, który montuje wolumen zapisywalnie.
# 1. spakuj build (na węźle generującym)
tar -czf /tmp/kb-site.tgz -C build/kb-site .
# 2. wyślij na PIHA
scp /tmp/kb-site.tgz piha:/tmp/kb-site.tgz
# 3. podmień zawartość wolumenu przez helper
ssh piha 'docker run --rm \
-v kb-site_kb-site_content:/content \
-v /tmp:/src:ro \
alpine sh -c "rm -rf /content/* /content/.[!.]* 2>/dev/null; \
tar -xzf /src/kb-site.tgz -C /content"'
# 4. posprzątaj staging
rm /tmp/kb-site.tgz
ssh piha 'rm /tmp/kb-site.tgz'
rm -rf /content/* przed rozpakowaniem jest obowiązkowe: bez tego strona
dokumentu przełączonego z public na private zostałaby w wolumenie i dalej
serwowała treść, której już nie publikujemy. Publikacja jest podmianą całości,
nie dogrywaniem plików.
Weryfikacja z PIHA:
services/kb-site/healthcheck.sh
curl -sI http://192.168.31.5:8250/index.html | head -1
4. Cloudflare — rekord A
Type: A
Name: kb
Content: <publiczny IP łącza domowego> # ten sam, co ma vikunja.okit.pl
Proxy: DNS only (szara chmurka)
TTL: Auto
Docelowy IP weź z istniejącego rekordu vikunja w tej samej strefie zamiast
wpisywać z pamięci — łącze domowe potrafi zmienić adres.
DNS only, nie proxied: challenge HTTP-01 w kroku 5 musi trafić na nginx, a nie na krawędź Cloudflare. Włączenie proxy to osobna decyzja, po tym jak cert już działa.
Split-horizon (patrz kb/phases/okit-cloudflare.md): w Pi-hole na PIHA dodaj
Local DNS Record kb.okit.pl → 192.168.31.5, żeby klienci w LAN szli prosto do
npm, a nie przez hairpin NAT na routerze.
Sprawdzenie propagacji (z hosta poza LAN albo przez publiczny resolver):
dig +short kb.okit.pl @1.1.1.1
5. NPM — proxy host + certyfikat
Wszystkie komendy npm_api.py są dry-run domyślnie; realna zmiana dopiero
z --apply. Puść najpierw bez --apply i przeczytaj payload.
# 5a. host: kb.okit.pl -> PIHA:8250
python3 scripts/npm/npm_api.py --npm piha create-host \
--domain kb.okit.pl \
--forward-host 192.168.31.5 --forward-port 8250 \
--block-exploits --http2-support
# ...i to samo z --apply
# 5b. cert Let's Encrypt (HTTP-01 — wymaga działającego kroku 4 i 5a)
python3 scripts/npm/npm_api.py --npm piha create-cert --domain kb.okit.pl --apply
# 5c. podepnij cert pod host i wymuś HTTPS
python3 scripts/npm/npm_api.py --npm piha list-hosts # weź HOST_ID
python3 scripts/npm/npm_api.py --npm piha list-certs # weź CERT_ID
python3 scripts/npm/npm_api.py --npm piha set-cert HOST_ID CERT_ID --ssl-forced --apply
Jeśli w npm@PIHA istnieje już wildcard *.okit.pl (faza 1 z
kb/phases/okit-cloudflare.md), pomiń 5b i w 5c podepnij jego CERT_ID —
jeden cert DNS-01 zamiast kolejnego per-host HTTP-01.
Diagnostyka nieudanego certu: ssh piha 'docker logs npm --tail 100' — NPM
zwraca wyjście certbota w treści błędu, a przy porażce challenge'u kasuje
wiersz certyfikatu (szczegóły w kb/runbooks/npm-api.md).
6. Weryfikacja końcowa
curl -sI https://kb.okit.pl/ | head -1 # 200
curl -s https://kb.okit.pl/ | grep -c 'class="cards"' # spis jest
curl -s https://kb.okit.pl/subsystems/observer.html | tail -5 # stopka: data + commit
W przeglądarce: https://kb.okit.pl — spis pogrupowany per type, kłódka bez
ostrzeżeń, wejście w dowolną kartę, powrót linkiem „All documents".
Kontrola treści (ręczna, jednorazowa po pierwszej publikacji): przejrzyj spis
i potwierdź, że nie ma tam nic, czego nie chcesz mieć w internecie. Generator
pilnuje pola visibility, ale to człowiek decyduje, co dostaje public.
7. Rutynowa aktualizacja treści
Po każdej zmianie w kb/**/*.md, która dotyczy dokumentów public:
python3 scripts/kb/gen_pages.py --base-url https://kb.okit.pl \
&& python3 scripts/kb/gen_pages.py --check \
&& tar -czf /tmp/kb-site.tgz -C build/kb-site . \
&& scp /tmp/kb-site.tgz piha:/tmp/kb-site.tgz \
&& ssh piha 'docker run --rm -v kb-site_kb-site_content:/content -v /tmp:/src:ro \
alpine sh -c "rm -rf /content/* /content/.[!.]* 2>/dev/null; tar -xzf /src/kb-site.tgz -C /content"' \
&& ssh piha 'rm /tmp/kb-site.tgz' \
&& rm /tmp/kb-site.tgz
Łańcuch na && jest celowy: --check z exit 1 zatrzymuje publikację.
Kontener nie wymaga restartu — nginx czyta wolumen na bieżąco.
8. Rollback i typowe problemy
| Objaw | Przyczyna | Reakcja |
|---|---|---|
healthcheck unhealthy, 404 na / |
pusty wolumen — treść nigdy nie wjechała | krok 3 |
| stara strona po publikacji | pominięte rm -rf /content/* |
powtórz krok 3 w całości |
| dokument nie pojawia się na stronie | brak visibility: public albo niepoprawny frontmatter (fail-closed) |
python3 scripts/kb/check_okf.py, potem regeneracja |
link renderuje się jako tekst z [private] |
cel jest prywatny albo nie istnieje | tak ma być — to nie błąd |
--check zapala nowe trafienie |
do dokumentu public wjechał adres/ścieżka/token |
popraw źródło; whitelist tylko świadomie |
| cert nie schodzi | rekord A nie propagował, proxy Cloudflare włączone, albo port 80 niedostępny | krok 4, potem 5b ponownie |
Awaryjne wygaszenie wystawki (bez ruszania NPM i DNS):
ssh piha 'cd ~/homelab-codex-ws && docker compose -f services/kb-site/docker-compose.yml down'
Wolumen zostaje; up -d przywraca stan sprzed. Pełne odtworzenie od zera to
kroki 1-3 — treść jest artefaktem repo, nie danymi.