feat(kb-site): serwis nginx na PIHA + manifesty (wzorzec narty27)

services/kb-site/ — nginx:alpine serwujacy wolumen kb-site_content
(:ro, /usr/share/nginx/html), pelny layout z CLAUDE.md: docker-compose.yml,
service.yaml, README-wskaznik, env.example (swiadomie pusty — brak sekretow),
healthcheck.sh. Wpis w hosts/piha/services.yaml.

Port hosta 8250, NIE 8240 z opisu zadania: 8240 jest juz zajete przez narty27
(services/narty27/docker-compose.yml). Blok statyczny PIHA to 8210 paperless,
8220 nextcloud, 8230 kb-query, 8240 narty27 -> 8250 to nastepny wolny.
Uzgodnione z operatorem.

exposure: public — w odroznieniu od narty27 ta wystawka ma byc dostepna z
internetu przez vhost npm@PIHA kb.okit.pl; bind :8250 jest upstreamem proxy,
nie punktem wejscia.

Tresc jest czystym artefaktem repo (wyjscie scripts/kb/gen_pages.py), zyje
wylacznie w wolumenie kb-site_kb-site_content — bez binda pod /opt/homelab/data
i bez zadania backupu: odtworzeniem jest regeneracja z kb/.

Sprawdzone lokalnie: docker compose config -q, bash -n healthcheck.sh,
yaml.safe_load na obu manifestach.
This commit is contained in:
oskar 2026-08-04 17:59:05 +02:00
parent 9591c8b688
commit 2c5894d70b
6 changed files with 117 additions and 0 deletions

View file

@ -165,6 +165,26 @@ services:
# No backup job, no /opt/homelab/data bind. # No backup job, no /opt/homelab/data bind.
config_path: services/narty27 config_path: services/narty27
kb-site:
role: static-html-host # public KB slice generated by scripts/kb/gen_pages.py
deployment_model: docker-compose
exposure: public # via npm@PIHA vhost kb.okit.pl; container binds LAN :8250
offline_required: false
depends_on:
local: []
external: []
ports:
- name: http
container_port: 80
host_port: 8250
protocol: tcp
runtime:
# No config and no secrets. Content is a pure artifact of the repo: it
# lives only in the Docker named volume kb-site_kb-site_content and is
# refreshed by regenerating from kb/ (kb/runbooks/kb-site-deploy.md).
# No backup job, no /opt/homelab/data bind.
config_path: services/kb-site
# --- Known unmanaged containers on piha (recon B5/B6, 2026-07-27) ---------- # --- Known unmanaged containers on piha (recon B5/B6, 2026-07-27) ----------
# ~28 running containers have no entry above and are deliberately NOT being # ~28 running containers have no entry above and are deliberately NOT being
# added piecemeal — bringing them under desired state is a later stage # added piecemeal — bringing them under desired state is a later stage

View file

@ -0,0 +1,5 @@
# kb-site
Public slice of the knowledge base (`kb.okit.pl`) — static HTML generated from `kb/**/*.md` by `scripts/kb/gen_pages.py`, served by nginx on PIHA.
Dokumentacja: [kb/services/kb-site.md](../../kb/services/kb-site.md)

View file

@ -0,0 +1,28 @@
services:
kb-site:
image: nginx:alpine
container_name: kb-site
restart: unless-stopped
ports:
# PIHA 82x0 static-HTTP block: 8210 paperless, 8220 nextcloud,
# 8230 kb-query, 8240 narty27 -> 8250 is the next free slot.
# Publicly the site is reached only through the npm@PIHA vhost
# kb.okit.pl; this bind is the proxy's upstream.
- "8250:80"
volumes:
# Generated output of scripts/kb/gen_pages.py — never committed, never
# bind-mounted from the repo. Read-only: nginx only serves it; writes go
# through the helper-container procedure in kb/runbooks/kb-site-deploy.md
# (docker cp cannot write into a :ro mount).
- kb-site_content:/usr/share/nginx/html:ro
# busybox wget — nginx:alpine ships no curl. index.html is generated on
# every run, so it is the one file that must always be there.
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/index.html"]
interval: 30s
timeout: 10s
retries: 5
start_period: 5s
volumes:
kb-site_content:

View file

@ -0,0 +1,10 @@
# kb-site has NO configuration and NO secrets.
#
# The port bind (8250:80) is static and the content lives in the
# kb-site_kb-site_content Docker volume, generated from kb/ by
# scripts/kb/gen_pages.py. This file exists only to keep the
# services/<service>/ layout from CLAUDE.md complete — there is nothing to
# copy to .env.
#
# The public address is a generator argument, not an env var:
# python3 scripts/kb/gen_pages.py --base-url https://kb.okit.pl

21
services/kb-site/healthcheck.sh Executable file
View file

@ -0,0 +1,21 @@
#!/bin/bash
# Healthcheck for kb-site (nginx:alpine serving the generated public KB)
# Container must be running
if ! docker ps --filter "name=kb-site" --filter "status=running" | grep -qw "kb-site"; then
echo "[FAIL] kb-site container is not running"
exit 1
fi
# The index is generated on every run, so both the bare root and /index.html
# must answer. An empty volume means the content was never loaded — see
# kb/runbooks/kb-site-deploy.md.
for path in "" index.html; do
if ! curl -sf -o /dev/null "http://127.0.0.1:8250/${path}"; then
echo "[FAIL] kb-site is not serving /${path} on 127.0.0.1:8250 (content loaded?)"
exit 1
fi
done
echo "[OK] kb-site is healthy"
exit 0

View file

@ -0,0 +1,33 @@
service:
name: kb-site
owner_node: piha
role: static-html-host # public slice of the KB, rendered by scripts/kb/gen_pages.py
exposure: public # public via npm@PIHA (kb.okit.pl). The container itself binds
# 8250 on the LAN; npm is the sole public entry point.
dependencies: [] # nginx serving a local volume — nothing else required at runtime
ports:
- container: 80
host: 8250
protocol: tcp
healthcheck:
type: http
endpoint: http://127.0.0.1:8250/index.html # content must be loaded first (see runbook)
interval: 30s
timeout: 10s
retries: 5
restart_policy: unless-stopped
persistence:
# Docker named volume kb-site_kb-site_content (compose project prefix), NOT a
# bind under /opt/homelab/data. The content is a pure artifact: regenerate it
# from the repo with scripts/kb/gen_pages.py, no backup job needed.
paths:
- kb-site_kb-site_content
runtime:
config_files: [] # no .env — the port bind is static, no secrets
env_vars: []
content:
# Only kb/ documents with `visibility: public` are published; the generator
# is fail-closed (no frontmatter / no visibility field = private).
generator: scripts/kb/gen_pages.py
leak_check: scripts/kb/gen_pages.py --check # must pass before publishing
source: kb/**/*.md