homelab-codex-ws/kb/runbooks/kb-site-deploy.md
oskar 67e49a0952 docs(kb-site): przepisz nieaktualne kb.okit.pl na kb-e2a24af3.okit.pl
Dociagniecie do zmiany DEFAULT_BASE_URL z 24afb49 — po niej repo w szesciu
miejscach dalej podawalo stary adres.

- kb/runbooks/kb-site-deploy.md: wszystkie wystapienia + rekord Cloudflare
  (Name: kb -> kb-e2a24af3). Runbook jest visibility: private, wiec slug
  moze stac wprost.
- services/kb-site/{README.md,service.yaml,env.example,docker-compose.yml}
- hosts/piha/services.yaml: komentarz przy exposure

kb/services/kb-site.md swiadomie nietkniety — dokument publiczny, slug tam
nie wchodzi (opisuje adres jako "non-obvious subdomain").

check_okf.py exit 0, gen_pages.py --check exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 13:08:13 +02:00

8.8 KiB

okf type visibility status updated links
0.1 runbook private active 2026-08-05
../services/kb-site.md
./npm-api.md
../phases/okit-cloudflare.md

kb-site — deploy, publikacja treści, ingress (kb-e2a24af3.okit.pl)

Serwis: services/kb-site/ (nginx:alpine na PIHA, host port 8250). Generator: scripts/kb/gen_pages.py. Publiczny adres: https://kb-e2a24af3.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-ws i 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 dla vikunja.okit.pl.
  • scripts/npm/.env wypełniony poświadczeniami (patrz kb/runbooks/npm-api.md).
  • Strefa okit.pl w Cloudflare (patrz kb/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-e2a24af3.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-e2a24af3
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-e2a24af3.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-e2a24af3.okit.pl @1.1.1.1

5. NPM — proxy host + certyfikat

Wszystkie komendy npm_api.pydry-run domyślnie; realna zmiana dopiero z --apply. Puść najpierw bez --apply i przeczytaj payload.

# 5a. host: kb-e2a24af3.okit.pl -> PIHA:8250
python3 scripts/npm/npm_api.py --npm piha create-host \
    --domain kb-e2a24af3.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-e2a24af3.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-e2a24af3.okit.pl/ | head -1                     # 200
curl -s https://kb-e2a24af3.okit.pl/ | grep -c 'class="cards"'      # spis jest
curl -s https://kb-e2a24af3.okit.pl/subsystems/observer.html | tail -5   # stopka: data + commit

W przeglądarce: https://kb-e2a24af3.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-e2a24af3.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.