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:
parent
9591c8b688
commit
2c5894d70b
|
|
@ -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
|
||||||
|
|
|
||||||
5
services/kb-site/README.md
Normal file
5
services/kb-site/README.md
Normal 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)
|
||||||
28
services/kb-site/docker-compose.yml
Normal file
28
services/kb-site/docker-compose.yml
Normal 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:
|
||||||
10
services/kb-site/env.example
Normal file
10
services/kb-site/env.example
Normal 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
21
services/kb-site/healthcheck.sh
Executable 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
|
||||||
33
services/kb-site/service.yaml
Normal file
33
services/kb-site/service.yaml
Normal 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
|
||||||
Loading…
Reference in a new issue