docs: project instructions and approved design
AGENTS.md/CLAUDE.md for agents, docs/DESIGN.md as the agreed architecture. UI language is English (code, comments, commits and UI); documents stay Polish. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1d6bfb0bb5
commit
8bcd0bc485
5 files changed
+142
No files matched your search
+10
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
coverage/
|
||||||
|
*.log
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite-wal
|
||||||
|
*.sqlite-shm
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
.claude/settings.local.json
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# homelab-upgrade — instrukcje
|
||||||
|
|
||||||
|
Kolejka kroków (SQLite) + driver + appka web do nocnych aktualizacji aplikacji Docker Compose na VM103.
|
||||||
|
Pełny, zatwierdzony projekt: `docs/DESIGN.md` — przeczytaj przed pracą nad architekturą lub nowym krokiem.
|
||||||
|
Stary system (do portu, tylko odczyt): `/root/homelab-modernization`. Nie czytaj tam `backups/` (kopie z `.env`).
|
||||||
|
|
||||||
|
## Środowisko
|
||||||
|
|
||||||
|
- Ten host to Proxmox (Debian 11) — tylko do rozwoju i testów. Docelowo kod działa na VM103 w Dockerze.
|
||||||
|
- Node 24 przez nvm (`.nvmrc`). W powłoce nieinteraktywnej przed `node`/`npm`: `. ~/.nvm/nvm.sh`.
|
||||||
|
- Nic nie instalujesz globalnie. Nie ruszasz Proxmoxa, VM103 ani działających kontenerów.
|
||||||
|
- Skryptów ze starego repo, które mutują (`apply.sh`, `rollback.sh`, `backup.sh`), nie uruchamiasz bez zlecenia właściciela. Testy idą na fejkach i zapisanych fixture'ach.
|
||||||
|
- Sekrety (`.env`, klucze, tokeny) nigdy w repo, logach ani wyjściu.
|
||||||
|
- Remote: `ssh://git@git.mariuszpacek.pl:2244/mariusz.pacek/homelab-upgrade.git`, gałąź `master`. Commity małe i opisowe; commit/push tylko na prośbę właściciela.
|
||||||
|
|
||||||
|
## Kod
|
||||||
|
|
||||||
|
- TypeScript `strict`, ESM, Node 24. Kod, identyfikatory, komentarze, commity i UI appki po angielsku; dokumenty (`docs/`, instrukcje) po polsku.
|
||||||
|
- Warstwy: `domain` (czyste typy i reguły, bez I/O) → `app` (handlery kroków, driver) → `infra` (adaptery) → `http`/`web`. Zależności tylko do środka; reguła lintu to pilnuje.
|
||||||
|
- Abstrakcja tylko tam, gdzie jest szew testowy albo wymienność: `StepStore`, `CommandRunner`, `ModelExecutor`, `Notifier`, `RegistryClient`, `Clock`. Reszta to zwykłe funkcje.
|
||||||
|
- Małe funkcje i moduły, wstrzykiwanie zależności przez argumenty/fabryki, discriminated unions zamiast flag, brak `any`, brak globalnego stanu.
|
||||||
|
- Walidacja na granicach (zod): wyjście skryptów, odpowiedź modelu, HTTP. Ten sam schemat daje typ.
|
||||||
|
- Testy Vitest obok kodu; `domain` i `app` pisz test-first. Przed commitem `npm run check` (typecheck + lint + test) musi być zielone.
|
||||||
|
- Nie dodawaj zależności bez powodu; każda nowa to jedna linia uzasadnienia w PR/commicie.
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
# Projekt: kolejka kroków do nocnych aktualizacji homelabu
|
||||||
|
|
||||||
|
Stan na 2026-09-18: projekt uzgodniony z właścicielem, kodu jeszcze nie ma.
|
||||||
|
Diagramy: artifact „Kolejka decyzji STOP” (https://claude.ai/artifact/5WdgWwxHGFa9qM5N5c8cpR, czytaj przez Artifact `read`). Ten plik jest streszczeniem bez diagramów.
|
||||||
|
|
||||||
|
## Cel
|
||||||
|
|
||||||
|
Nocna aktualizacja aplikacji Docker Compose na VM103 (192.168.0.139, manifesty `/opt/homelab/compose/<projekt>/compose.yaml`) bez nadzoru.
|
||||||
|
STOP tylko wtedy, gdy właściciel naprawdę musi zdecydować. Decyzję podejmuje w appce (LAN), po pushu z Home Assistant.
|
||||||
|
Zastępuje: `nightly-update.sh`, agentów `homelab-orchestrator`/`homelab-updater`, `docs/log.md`, `docs/costs.tsv` ze starego repo.
|
||||||
|
|
||||||
|
## Mechanizm
|
||||||
|
|
||||||
|
- Jedna generyczna pętla: driver bierze najstarszy krok `queued`, wykonuje go (skrypt/kod albo model), zapisuje wynik i dokłada następny krok. STOP i error to zwykły status kroku, nie osobna ścieżka.
|
||||||
|
- Wznowienie po STOP = nowy krok `queued` wstawiony z appki. Ten sam driver podejmie go przy następnym sprawdzeniu (pętla co ~60 s).
|
||||||
|
- Jedna instancja drivera (blokada), jeden krok naraz, jedna usługa naraz. Piszę do `steps` tylko driver.
|
||||||
|
- Krok, który utknął: limit czasu per typ kroku; po restarcie `running` starszy niż limit → `error`. `apply` i `rollback` nigdy nie są powtarzane automatycznie.
|
||||||
|
- Kroki z odroczeniem (`not_before`): ponowienie po `rate_limited`, przypomnienie po 24 h.
|
||||||
|
- Wersje krok po kroku: po `verify = ok` driver dokłada świeży `image_tags` dla tej aplikacji. Nowsza wersja w progu `Auto` → kolejny łańcuch; powyżej progu → STOP; nic nowszego → koniec.
|
||||||
|
- Dwa procesy z jednego kodu: `driver` (uprawnienia wykonawcze) i `server` (appka w LAN, bez uprawnień wykonawczych: tylko baza i compose do odczytu). Wspólna baza SQLite w trybie WAL na jednym hoście. Decyzja w appce to wstawienie wiersza `queued`.
|
||||||
|
|
||||||
|
## Dane (SQLite)
|
||||||
|
|
||||||
|
- `runs`: round_id PK, type (nightly|manual), started_at, finished_at, cost_usd (suma kroków).
|
||||||
|
- `steps`: id PK, round_id FK, seq, app (null = krok roundu), step, status (queued|running|ok|stop|error), executor (script|sonnet|opus), reason_code, detail, resolution (≤10 słów), resolved_at, input, result, cost_usd, not_before, started_at, finished_at.
|
||||||
|
- `app_config`: app PK, enabled, position, service, image_repo, tag_pattern, auto (none|patch|minor|major), http_url, expect_status, release_notes_executor, migrate_executor (null = autonomiczne migracje wyłączone), prompt_hint, notes (≤10 słów).
|
||||||
|
- Status aplikacji = jej ostatni wiersz w `steps`. Kolejka decyzji: `status IN ('stop','error') AND resolved_at IS NULL`.
|
||||||
|
- Retencja 12 miesięcy (czyszczenie skryptem miesięcznie). Baza jest objęta nocnym vzdump VM103.
|
||||||
|
- `docs/log.md` i `docs/costs.tsv` znikają; historia i koszt są w bazie.
|
||||||
|
|
||||||
|
## Powód STOP
|
||||||
|
|
||||||
|
`reason_code` (znane przypadki) + `detail` (konkretny tekst: polecenie, kod wyjścia, pierwsza linia stderr, komunikat wyjątku albo powód modelu ≤10 słów; po maskowaniu sekretów). Nieznany przypadek = `unexpected` z prawdziwym tekstem, nigdy pusty „error”.
|
||||||
|
`stop` = potrzebna decyzja właściciela (znany powód); `error` = coś się zepsuło.
|
||||||
|
Wstępna lista: no_backup, no_space, version_above_auto, version_unknown, migration_required, schema_change, forbidden_change, regression, verify_failed, rolled_back, no_skill, no_tool, model_unparseable, rate_limited, auth_failed, timeout, unexpected.
|
||||||
|
|
||||||
|
## Kroki (mapowanie starej pętli z `/root/homelab-modernization/AGENTS.md`)
|
||||||
|
|
||||||
|
| Krok | Wykonawca | Stary odpowiednik |
|
||||||
|
|---|---|---|
|
||||||
|
| `plan` | kod | wszystkie `enabled` z `app_config` wg `position`, każdej wstawia `status` |
|
||||||
|
| `status` | kod | `status.sh <p>` → `manifest_sha256`, obraz |
|
||||||
|
| `image_tags` | kod | `image_tags.sh <repo> --pattern <tag_pattern> --current <tag>` → `candidate`, `version_change`; bramka: wyżej niż `auto` albo `unknown` → STOP |
|
||||||
|
| `release_notes` | model (bez narzędzi) | czytanie release notes; wynik proceed / migrate / stop |
|
||||||
|
| `migrate` (`repair`) | model z narzędziami | nowe |
|
||||||
|
| `image_resolve` | kod | `image_resolve.sh <repo>:<candidate>` → `pinned` (tag@digest) |
|
||||||
|
| `backup_check` | kod | `pvesm list … 103` młodsza niż 48 h → host zapisuje plik z datą ostatniej udanej kopii, boks czyta go tylko do odczytu |
|
||||||
|
| `preflight` | kod | `preflight.sh` → `storage.sufficient` (≥ 5368709120 B wolnego w `/var/lib/docker`) |
|
||||||
|
| `backup` | kod | `backup.sh <p>` → `backup_dir` (kopia manifestu i `.env`; katalog z sekretami, chmod 700) |
|
||||||
|
| `apply_dry_run`, `apply` | kod | `apply.sh … --dry-run`, potem to samo bez; dla update `diff.changed_paths` musi być tylko `services.<usł>.image` |
|
||||||
|
| `verify` | kod | `verify.sh` (running, image, http, retry 6×5 s, odbiór ze skilla) |
|
||||||
|
| `rollback` | kod | `rollback.sh <p> --from <dir> --service <usł>` |
|
||||||
|
| `skill_gen`, `skill_publish` | model bez narzędzi / kod | rejestracja aplikacji w appce: szkic → zatwierdzenie → zapis |
|
||||||
|
|
||||||
|
Kody wyjścia `apply.sh`: 2 błędne argumenty; 3 `floating_tag`/`missing_tag` → najpierw repin, `missing_digest` → dodaj digest, diff szerszy niż obraz → STOP; 4 manifest zmieniony → `status`, nowy hash, od `preflight`; 5 pull/up nie przeszedł → `verify`, jeśli usługa nie działa → `rollback` i STOP.
|
||||||
|
Repin (digest bez zmian, nazwanie wersji): `status` → `image_version.sh` → `proposed_image` (`no_version_label` → STOP) → backup → apply dry-run → apply → verify.
|
||||||
|
|
||||||
|
## Granice (niezależne od modelu)
|
||||||
|
|
||||||
|
Absolutny STOP, także dla Opusa: sekrety, sieć, porty, UID/GID, mounty, ruszanie traefik/mqtt/unifi/homeassistant/Proxmox/TrueNAS, kasowanie danych/wolumenów/VM, `latest`, „update all”, `prune`, `down -v`, `--remove-orphans`.
|
||||||
|
Egzekwowane w kodzie, nie w prompcie: po `migrate` diff compose jest sprawdzany deny-listą (`privileged`, `cap_add`, `devices`, `network_mode: host`, mounty poza katalogami aplikacji, porty, sieci, sekrety). Trafienie = STOP.
|
||||||
|
`release_notes` ma trzy wyniki: `proceed`, `migrate` (w zakresie tego, co może zrobić wskazany model), `stop`. `migrate_executor = null` wyłącza `migrate` dla aplikacji.
|
||||||
|
|
||||||
|
## Wykonawcy modeli: CLI z abonamentu
|
||||||
|
|
||||||
|
- API bezpośrednio odpada: abonament go nie obejmuje, a Anthropic zabrania routowania żądań przez dane logowania planu Pro/Max i przechowywania tokenów sesji. Dozwolone jest uruchamianie niezmodyfikowanego `claude` zalogowanego własnym kontem właściciela.
|
||||||
|
- Port `ModelExecutor`, adaptery CLI: `claude -p` (start), Codex CLI później (logowanie kontem ChatGPT w automatyzacji jest odradzane; sprawdzić w praktyce). Adaptery API możliwe w przyszłości bez zmian w reszcie.
|
||||||
|
- `--bare` odpada (wymaga klucza API). Uruchamiaj w pustym katalogu roboczym i z osobnym katalogiem konfiguracji (do potwierdzenia: `CLAUDE_CONFIG_DIR`), żeby żaden CLAUDE.md/AGENTS.md ani ustawienia użytkownika nie wchodziły do kroków.
|
||||||
|
- Wynik: `--output-format json --json-schema …` → pole `structured_output` (walidacja zod), `total_cost_usd` to szacunek po stronie klienta. Prompt przez stdin (limit 10 MB).
|
||||||
|
- Profil A (`release_notes`, `skill_gen`): bez narzędzi. Driver sam pobiera release notes (tylko linki ze skilla, limit rozmiaru) — to niezaufany tekst.
|
||||||
|
- Profil B (`migrate`/`repair`): CLI z narzędziami (Read, Edit, ograniczony Bash) dla jednej aplikacji, po świeżej kopii. `--allowedTools` z wzorcami, `dontAsk`, hook `PreToolUse` odrzucający polecenia z listy zakazów. Po naprawie `verify` (kod) decyduje: ok albo `rollback`.
|
||||||
|
- Limity planu: przy limicie krok kończy się `rate_limited` i wraca z `not_before`. Poświadczenia w boksie wygasają → `auth_failed` + push.
|
||||||
|
- Wersję CLI przypinamy w obrazie i aktualizujemy świadomie.
|
||||||
|
|
||||||
|
## Boks (do decyzji przed uruchomieniem modeli na prawdziwych danych)
|
||||||
|
|
||||||
|
Właściciel chce: nic nie wycieka poza boks i brak kontroli nad VM. Uwaga: `docker.sock` to praktycznie root na VM.
|
||||||
|
Kierunek: kontener bez roota, system plików tylko do odczytu poza wolumenami, montowanie tylko katalogu compose i wolumenu SQLite; Docker przez proxy z allowlistą albo cienka nakładka na `docker` z kontrolą argumentów (wszystko zawężone do jednej aplikacji); ruch wychodzący tylko do API Anthropic i logowania Anthropic, rejestrów obrazów, GitHuba, HA; bez dostępu do reszty LAN (TrueNAS, Proxmox, MQTT, UniFi). Szczegóły są otwarte.
|
||||||
|
|
||||||
|
## Powiadomienia i appka
|
||||||
|
|
||||||
|
- Push HA per aplikacja, tylko przy stop/error, z linkiem do kroku (`scripts/notify.sh` ze starego repo: POST do `notify.<usługa>`, konfiguracja `HA_URL`, `HA_TOKEN`, `HA_NOTIFY`; HA stoi na VM103). Przypomnienie raz po 24 h.
|
||||||
|
- Watchdog na hoście: brak `runs.finished_at` po nocnym przebiegu → jeden push „przebieg nie ruszył”.
|
||||||
|
- Appka: tylko LAN, za traefik; logowanie do przemyślenia przed wystawieniem poza dom. Widoki: przebiegi, historia aplikacji, koszty, kolejka STOP z akcjami (proceed / skip / rollback / zmiana executora / notatka), edycja `app_config`, rejestracja aplikacji z listy `/opt/homelab/compose` (montowanej tylko do odczytu) ze `skill_gen` i zatwierdzeniem szkicu (domyślnie `auto = patch`). Odświeżanie na żywo przez SSE.
|
||||||
|
- Skille: parametry mechaniczne w `app_config`; `SKILL.md` tylko fakty dla modelu. Import 15 istniejących skilli jednorazowo (na razie sprawdzony format tylko Bazarr).
|
||||||
|
|
||||||
|
## Stary kod do portu (`/root/homelab-modernization`, tylko odczyt)
|
||||||
|
|
||||||
|
`skills/homelab-compose-ops/scripts/` (≈1230 linii bash + Python): apply, apply_support.py, backup, compose_ps_json.py, guest_lib.sh, image_resolve, image_tags, image_version, plan, preflight, preflight_support.py, pve_agent_exec.py, rollback, status, verify. Skille aplikacji: `skills/*-compose-upgrade/SKILL.md` (15).
|
||||||
|
Stan docelowy: czysty TypeScript, bash jako wzorzec. Port po jednym skrypcie z testem kontraktowym (wynik TS zgodny z bashem na zapisanym JSON-ie). `guest_lib.sh` znika, bo driver działa na VM103 i `CommandRunner` wykonuje polecenia lokalnie. Kolejność od najmniej ryzykownych: status, image_tags, image_resolve, preflight, verify; potem backup, apply, rollback (mutacje, najwięcej testów).
|
||||||
|
|
||||||
|
## Plan etapów
|
||||||
|
|
||||||
|
0. Decyzje: repo, Node, klucz — zrobione. Do zrobienia przez właściciela: domena appki w traefik, logowanie `claude` w boksie, kształt boksu.
|
||||||
|
1. **Fundamenty (pierwsza sesja):** szkielet TS, domain, SQLite (schemat + migracje), driver z pętlą zdarzeń i fałszywymi handlerami, serwer HTTP + SSE, mały frontend (przebiegi, kroki, kolejka STOP, akcja rozstrzygnięcia), testy, `npm run check`.
|
||||||
|
2. Port skryptów na TS z `CommandRunner` (lokalnie na VM103).
|
||||||
|
3. Handlery kroków skryptowych + import skilli + przebieg suchy.
|
||||||
|
4. Boks.
|
||||||
|
5. Kroki z modelem (`ClaudeCliExecutor`, szablony, parser, deny-lista).
|
||||||
|
6. Appka: pełna (rejestracja aplikacji, `skill_gen`).
|
||||||
|
7. Powiadomienia i dwa małe skrypty po stronie hosta (data kopii, watchdog).
|
||||||
|
8. Przełączenie: kilka nocy równolegle ze starym przebiegiem (tryb suchy), potem wyłączenie starego, odchudzenie AGENTS.md starego repo, usunięcie agentów i `log.md`/`costs.tsv`.
|
||||||
|
|
||||||
|
## Otwarte
|
||||||
|
|
||||||
|
Kształt boksu; nakładka na `docker`; hosty logowania Anthropic do allowlisty; Codex przez konto ChatGPT; logowanie do appki poza LAN; wybór sterownika SQLite (`better-sqlite3` albo wbudowany `node:sqlite` — sprawdź stabilność w Node 24); domyślny język kodu (angielski) i stack (Fastify, Vite + Preact, zod, Vitest) do potwierdzenia przy szkielecie.
|
||||||
Reference in new issue
Block a user