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 <noreply@anthropic.com>
This commit is contained in:
parent
04884ed67d
commit
003f83d453
26
.claude/skills/kb-publish/SKILL.md
Normal file
26
.claude/skills/kb-publish/SKILL.md
Normal file
|
|
@ -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.
|
||||||
|
|
@ -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
|
```bash
|
||||||
set -a; . /opt/homelab/config/kb-site/.env; set +a # ACCESS_TOKEN
|
bash scripts/kb/publish.sh
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
### Token w linkach
|
||||||
|
|
||||||
Generator czyta `ACCESS_TOKEN` ze środowiska i dopisuje `?key=<token>` do
|
Generator czyta `ACCESS_TOKEN` ze środowiska i dopisuje `?key=<token>` 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
|
źródła, wpis do whitelisty dopiero jako świadoma decyzja, zawsze z komentarzem
|
||||||
i datą.
|
i datą.
|
||||||
|
|
||||||
---
|
### Wgranie treści do wolumenu (helper alpine)
|
||||||
|
|
||||||
## 3. Wgranie treści do wolumenu (helper alpine)
|
|
||||||
|
|
||||||
Wzorzec z `services/narty27/README.md`, rozszerzony z jednego pliku na drzewo:
|
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
|
`docker cp` nie zapisze do montowania `:ro`, więc treść wjeżdża jednorazowym
|
||||||
kontenerem pomocniczym, który montuje wolumen zapisywalnie.
|
kontenerem pomocniczym, który montuje wolumen zapisywalnie. `publish.sh` robi
|
||||||
|
dokładnie to: pakuje `build/kb-site` do tarballa, `scp` na PIHA, i podmienia
|
||||||
```bash
|
wolumen przez ten sam wzorzec helpera co niżej.
|
||||||
# 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
|
`rm -rf /content/*` przed rozpakowaniem jest **obowiązkowe**: bez tego strona
|
||||||
dokumentu przełączonego z `public` na `private` zostałaby w wolumenie i dalej
|
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,
|
serwowała treść, której już nie publikujemy. Publikacja jest podmianą całości,
|
||||||
nie dogrywaniem plików.
|
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
|
```bash
|
||||||
services/kb-site/healthcheck.sh
|
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`:
|
Po każdej zmianie w `kb/**/*.md`, która dotyczy dokumentów `public`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
set -a; . /opt/homelab/config/kb-site/.env; set +a # ACCESS_TOKEN
|
bash scripts/kb/publish.sh
|
||||||
|
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Łańcuch na `&&` jest celowy — każde ogniwo zatrzymuje publikację: pusty
|
To ten sam skrypt co w krokach 2-3 — rutynowa aktualizacja to po prostu
|
||||||
`ACCESS_TOKEN` (build byłby nieklikalny), `--check` z exit 1 (wyciek), brak
|
ponowne uruchomienie całej publikacji. Bramki (`ACCESS_TOKEN` pusty, `--check`
|
||||||
`key=` w spisie (token był pusty albo nie dojechał do generatora).
|
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.
|
Kontener nie wymaga restartu — nginx czyta wolumen na bieżąco.
|
||||||
|
|
||||||
|
|
|
||||||
73
scripts/kb/publish.sh
Executable file
73
scripts/kb/publish.sh
Executable file
|
|
@ -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."
|
||||||
Loading…
Reference in a new issue