From 003f83d453ed9c76e566b7c3bddcc545e24495a5 Mon Sep 17 00:00:00 2001 From: oskar Date: Wed, 26 Aug 2026 16:00:34 +0200 Subject: [PATCH] feat(kb): skrypt publish.sh dla kb-site + skill przypominajacy o publikacji Automatyzuje kroki 2-3 z kb/runbooks/kb-site-deploy.md (generacja, kontrola wyciekow, upload przez helper alpine, smoke test) w jednym fail-closed skrypcie zamiast recznego klikania. Runbook zaktualizowany, zeby referencjonowal skrypt, z zachowaniem wyjasnien co/dlaczego dla kazdej bramki. Dodaje skill kb-publish, ktory przypomina operatorowi o publikacji po zmianach w kb/**/*.md, ale nigdy sam nie uruchamia skryptu (dotyka prod przez SSH). Co-Authored-By: Claude Sonnet 5 --- .claude/skills/kb-publish/SKILL.md | 26 +++++++++++ kb/runbooks/kb-site-deploy.md | 74 +++++++++++------------------- scripts/kb/publish.sh | 73 +++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 47 deletions(-) create mode 100644 .claude/skills/kb-publish/SKILL.md create mode 100755 scripts/kb/publish.sh diff --git a/.claude/skills/kb-publish/SKILL.md b/.claude/skills/kb-publish/SKILL.md new file mode 100644 index 0000000..ce38f66 --- /dev/null +++ b/.claude/skills/kb-publish/SKILL.md @@ -0,0 +1,26 @@ +--- +name: kb-publish +description: Reminds the operator to publish the KB site after changes to kb/**/*.md. Trigger whenever this session's own edits, or a merge/diff visible in git, touch files under kb/. +--- + +## What this skill does + +At the end of your response, if this session touched `kb/**/*.md` — either +through your own edits or through a merge/diff you observed in git — append +exactly one reminder sentence: + +> Zmiany w kb/ — pamiętaj odpalić `bash scripts/kb/publish.sh` żeby opublikować. + +One sentence, no more. Don't repeat it more than once per response, and don't +add it if nothing under `kb/` changed. + +## What this skill does NOT do + +**Never run `scripts/kb/publish.sh` yourself**, under any circumstances, even +if the operator's task prompt says to deploy, publish, or calibrate. The +script SSHes into PIHA and overwrites the live `kb-site` content volume on +production — that is out of scope for a worktree agent and requires an +explicit, direct instruction from the operator in the current turn. + +If the operator explicitly asks you to run it, that instruction stands on its +own — this skill only governs the passive reminder, not that request. diff --git a/kb/runbooks/kb-site-deploy.md b/kb/runbooks/kb-site-deploy.md index 8bed1d1..2b32158 100644 --- a/kb/runbooks/kb-site-deploy.md +++ b/kb/runbooks/kb-site-deploy.md @@ -61,19 +61,27 @@ docker volume ls | grep kb-site # kb-site_kb-site_content --- -## 2. Generacja treści + kontrola wycieków (SATURN / SOLARIA) +## 2-3. Generacja, kontrola wycieków, wgranie treści (SATURN / SOLARIA) -Generujemy w checkoucie repo, na węźle z którego pracujesz — nie na PIHA. +Kroki 2-4 (generacja → `--check` → upload przez helper alpine → smoke test) +robi jeden skrypt: ```bash -set -a; . /opt/homelab/config/kb-site/.env; set +a # ACCESS_TOKEN -test -n "$ACCESS_TOKEN" || echo "BRAK TOKENU — nie publikuj tego builda" - -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 +bash scripts/kb/publish.sh ``` +Zobacz kod (`scripts/kb/publish.sh`) dla dokładnej kolejności komend. Poniżej +zostaje wyjaśnienie **co** skrypt robi i **dlaczego** — nie trzeba już klikać +kroków ręcznie, ale bramki i uzasadnienia poniżej wciąż obowiązują. + +Uruchamiamy w checkoucie repo, na węźle z którego pracujesz — nie na PIHA. +Docelowy host PIHA jest parametrem na górze skryptu (zmienna `PIHA_HOST`, +domyślnie `piha.local`). + +Skrypt jest fail-closed na każdym etapie: brak/pusty `ACCESS_TOKEN`, `--check` +z exit 1, albo 0 wystąpień `key=` w wygenerowanym `index.html` — każde z tych +zatrzymuje publikację przed dotknięciem PIHA. + ### Token w linkach Generator czyta `ACCESS_TOKEN` ze środowiska i dopisuje `?key=` do @@ -127,39 +135,21 @@ 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) +### 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. - -```bash -# 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' -``` +kontenerem pomocniczym, który montuje wolumen zapisywalnie. `publish.sh` robi +dokładnie to: pakuje `build/kb-site` do tarballa, `scp` na PIHA, i podmienia +wolumen przez ten sam wzorzec helpera co niżej. `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: +Skrypt kończy smoke testem (`ssh $PIHA_HOST curl -s localhost:8250`) — jeśli +chcesz dodatkowej weryfikacji z samej PIHA: ```bash services/kb-site/healthcheck.sh @@ -267,23 +257,13 @@ pilnuje pola `visibility`, ale to człowiek decyduje, co dostaje `public`. Po każdej zmianie w `kb/**/*.md`, która dotyczy dokumentów `public`: ```bash -set -a; . /opt/homelab/config/kb-site/.env; set +a # ACCESS_TOKEN - -test -n "$ACCESS_TOKEN" \ - && python3 scripts/kb/gen_pages.py --base-url https://kb-e2a24af3.okit.pl \ - && python3 scripts/kb/gen_pages.py --check \ - && grep -q 'key=' build/kb-site/index.html \ - && 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 +bash scripts/kb/publish.sh ``` -Łańcuch na `&&` jest celowy — każde ogniwo zatrzymuje publikację: pusty -`ACCESS_TOKEN` (build byłby nieklikalny), `--check` z exit 1 (wyciek), brak -`key=` w spisie (token był pusty albo nie dojechał do generatora). +To ten sam skrypt co w krokach 2-3 — rutynowa aktualizacja to po prostu +ponowne uruchomienie całej publikacji. Bramki (`ACCESS_TOKEN` pusty, `--check` +exit 1, brak `key=` w spisie) zatrzymują ją przed dotknięciem PIHA przy każdym +uruchomieniu, nie tylko przy pierwszym. Kontener nie wymaga restartu — nginx czyta wolumen na bieżąco. diff --git a/scripts/kb/publish.sh b/scripts/kb/publish.sh new file mode 100755 index 0000000..a56e740 --- /dev/null +++ b/scripts/kb/publish.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# scripts/kb/publish.sh — publikacja kb-site (kroki 2-4 z kb/runbooks/kb-site-deploy.md) +# +# Uruchamiane z węzła generującego (SATURN/SOLARIA), nie z PIHA. +# Kolejność bramek: token -> generacja -> --check -> sanity linków -> upload -> smoke test. +# Każda bramka zatrzymuje publikację przy porażce — patrz runbook, sekcja 2-3. +# +# Usage: scripts/kb/publish.sh + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +PIHA_HOST="${PIHA_HOST:-piha.local}" +BASE_URL="https://kb-e2a24af3.okit.pl" +TOKEN_FILE="/opt/homelab/config/kb-site/.env" +BUILD_DIR="$REPO_ROOT/build/kb-site" +TARBALL="/tmp/kb-site.tgz" + +log() { + echo "[kb-publish] $*" +} + +fail() { + echo "[kb-publish] BLAD: $*" >&2 + exit 1 +} + +log "krok 1/6: wczytanie ACCESS_TOKEN z $TOKEN_FILE" +[[ -f "$TOKEN_FILE" ]] || fail "brak pliku $TOKEN_FILE — patrz kb/runbooks/kb-site-deploy.md sekcja 0" + +set -a +# shellcheck disable=SC1090 +. "$TOKEN_FILE" +set +a + +[[ -n "${ACCESS_TOKEN:-}" ]] || fail "ACCESS_TOKEN pusty w $TOKEN_FILE — nie publikuje bez tokenu" + +log "krok 2/6: generacja treści (gen_pages.py --base-url $BASE_URL)" +cd "$REPO_ROOT" +python3 scripts/kb/gen_pages.py --base-url "$BASE_URL" + +log "krok 3/6: kontrola wyciekow (gen_pages.py --check)" +if ! python3 scripts/kb/gen_pages.py --check; then + fail "--check wykryl wyciek — publikacja WSTRZYMANA, popraw zrodlo i uruchom ponownie" +fi + +log "krok 4/6: sanity check — token w linkach index.html" +KEY_COUNT=$(grep -c 'key=' "$BUILD_DIR/index.html" || true) +[[ "$KEY_COUNT" -gt 0 ]] || fail "0 wystapien 'key=' w $BUILD_DIR/index.html — token nie trafil do linkow" +log " -> $KEY_COUNT linkow z tokenem" + +log "krok 5/6: pakowanie i wgranie na $PIHA_HOST" +tar -czf "$TARBALL" -C "$BUILD_DIR" . +log " -> scp $TARBALL do $PIHA_HOST:/tmp/kb-site.tgz" +scp "$TARBALL" "$PIHA_HOST:/tmp/kb-site.tgz" + +log " -> helper alpine: podmiana wolumenu kb-site_kb-site_content" +ssh "$PIHA_HOST" '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"' + +log " -> sprzatanie /tmp (lokalnie i na $PIHA_HOST)" +rm -f "$TARBALL" +ssh "$PIHA_HOST" 'rm -f /tmp/kb-site.tgz' + +log "krok 6/6: smoke test na $PIHA_HOST:8250" +SMOKE_OUTPUT=$(ssh "$PIHA_HOST" 'curl -s localhost:8250' | head -c 300) +log " -> odpowiedz (pierwsze 300 znakow):" +echo "$SMOKE_OUTPUT" + +log "publikacja zakonczona."