The owner's decision and what the Arcane docs confirm: pull-synced projects, local .env, a trigger webhook, an API key REST API, its own updater off. homelab-upgrade changes compose only by commit, rolls back with git revert and still decides success with its own verify. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
27 KiB
Projekt: kolejka kroków do nocnych aktualizacji homelabu
Od 2026-09-23 łańcuch kroków jest wycofany (decyzja właściciela): aktualizacje, bramki, harmonogramy (cron per workflow) i pushe należą do silnika workflow (sekcja „Rozszerzalny silnik workflow”). Sekcje „Mechanizm”, „Dane”, „Powód STOP” i „Kroki” opisują historyczny łańcuch; jego tabele (runs, steps, reminders) zostają w bazie nieużywane. Zadania Agent i tryb demo usunięto.
Stan na 2026-09-20: fundamenty, tryb „na sucho”, łańcuch prawdziwej aktualizacji przez executor oraz audytowalne zadania Agent. Projekt uzgodniony z właścicielem 2026-09-18, decyzje o zapisie 2026-09-19 (sekcja „Executor”).
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
queuedwstawiony 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
stepstylko driver. - Krok, który utknął: limit czasu per typ kroku; po restarcie
runningstarszy niż limit →error.applyirollbacknigdy nie są powtarzane automatycznie. - Kroki z odroczeniem (
not_before): ponowienie porate_limited, przypomnienie po 24 h. - Wersje krok po kroku: po
verify = okdriver dokłada świeżyimage_tagsdla tej aplikacji. Nowsza wersja w proguAuto→ kolejny łańcuch; powyżej progu → STOP; nic nowszego → koniec. - Dwa procesy z jednego kodu:
driver(uprawnienia wykonawcze) iserver(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 wierszaqueued.
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,summary(nullable dla starych rekordów, nowe zakończone kroki: zamaskowane 1–500 znaków), resolution (≤10 słów), resolved_at, input, result,progress(schemat v7: tylko dla krokurunning, czyszczone przyfinish;{at, actions, last}), 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),post_upgrade_executor(schemat v8: null = po aktualizacji tylko skrypty; źródła notatek wydań to jedna globalna lista, patrzrelease_notes), prompt_hint, notes (≤10 słów), apply (0/1: prawdziwe aktualizacje przez executor; domyślnie 0 = tryb na sucho), project (projekt compose; wiersz = składnik, patrz „Grupy”).- 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.mdidocs/costs.tsvznikają; 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, confirm_apply (celowy STOP przed apply; używany do 2026-09-19, teraz aktualizacja w progu auto idzie sama), 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 (tylko WebSearch i WebFetch) | tylko dla skoku powyżej progu auto: image_tags kolejkuje go zamiast gołego STOP. Model sam znajduje notatki wydań między działającą wersją a kandydatem, preferując globalną listę zaufanych źródeł właściciela (Models → Release notes sources, tabela settings; strona http(s) albo github:owner/repo), a gdy żadne nie pasuje — oficjalne strony producenta. Notatki + skill + wersje reszty projektu → ocena AgentReport (clear/attention/blocked, kroki „Before update:”/„After update:”) w decyzji STOP version_above_auto; właściciel dalej decyduje. Błąd modelu = zwykły STOP z powodem, nigdy zablokowany łańcuch |
migrate (repair) |
model z narzędziami | nowe |
image_resolve |
kod | image_resolve.sh <repo>:<candidate> → pinned (tag@digest) |
backup_check |
kod | kopia vzdump VM103 młodsza niż 48 h: timer na Proxmoksie co godzinę zapisuje najnowszą (pvesh … storage/vm-storage/content) do /srv/appdata/homelab-upgrade/host/vzdump.json, driver czyta ją tylko do odczytu |
preflight |
kod | preflight.sh → storage.sufficient (≥ 5368709120 B wolnego w /var/lib/docker) |
backup |
kod (executor) | backup.sh <p> → backup_id (kopia manifestu i .env na VM w /srv/appdata/homelab-upgrade/backups/<p>/<id>/: katalogi 0700, pliki 0600; wynik to same hashe) |
apply_dry_run, apply |
kod (executor) | apply.sh … --dry-run, potem to samo bez; dla update diff.changed_paths musi być tylko services.<usł>.image. W progu auto po apply_dry_run od razu apply (diff zapisany w wyniku); powyżej progu STOP już w image_tags |
post_upgrade |
model z narzędziami (sesja repair) |
po udanym verify, tylko dla aplikacji z post_upgrade_executor: model czyta ocenę notatek („After update:”), skill i stan usługi, wykonuje to, na co pozwala hl exec (dokładna lista config/agent-exec.json), resztę wypisuje właścicielowi (STOP migration_required, proceed idzie do następnego skoku). Np. Nextcloud: brakujące indeksy bazy przez occ. Domyślnie wyłączony |
verify |
kod | verify.sh (running, obraz = pinned, healthy jeśli jest healthcheck, http, retry 6×5 s; odbiór ze skilla później). Kontener przez proxy (GET), HTTP z drivera. Czeka do 10 min, póki healthcheck mówi starting (migracje przy starcie), potem 24×5 s. Nie przeszedł → STOP verify_failed, bez automatycznego rollbacku: powrót manifestu nie cofa danych (2026-09-19 Nextcloud po migracji bazy nie wstał na starszym obrazie). Właściciel: retry (ponowny verify), rollback albo skip |
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 |
agent_task |
kod + model read-only | ręczny audyt całego homelabu albo jednej aplikacji; wspólna kolejka FIFO |
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. W porcie image_version tylko nazywa wersję (etykieta obrazu) i przekazuje ją do image_tags; osobnego apply dla samego repinu nie ma (docs/PORT.md).
Granice (niezależne od modelu)
Absolutny STOP, także dla Opusa: sekrety, sieć, porty, UID/GID, mounty, ruszanie Proxmox/TrueNAS, kasowanie danych/wolumenów/VM, latest, „update all”, prune, down -v, --remove-orphans.
Od 2026-09-23 (decyzja właściciela) traefik, mqtt, unifi i homeassistant nie są już pod absolutnym STOP: o tym, co się aktualizuje, decydują flagi enabled i apply aplikacji. Executor nadal nigdy nie zmienia własnego projektu (homelab-upgrade).
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 daje ocenę, nie wyrok: decyzję o majorze podejmuje właściciel. migrate_executor = null wyłącza autonomiczną naprawę modelem dla aplikacji, ale model wybrany przy proceed (albo retry kroku verify) naprawia nieudany verify całego skoku niezależnie od tego ustawienia (repair_by niesione w wejściu kroków łańcucha). Prompt naprawy dostaje ocenę notatek tej aktualizacji.
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
claudezalogowanego 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. --bareodpada (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 …→ polestructured_output(walidacja zod),total_cost_usdto szacunek po stronie klienta. Prompt przez stdin (limit 10 MB). - Profil A (
release_notes,skill_gen): bez narzędzi hosta.release_notesma tylko WebSearch i WebFetch; strony to niezaufany tekst (dane, nie polecenia), ocena jest doradcza. - Profil B (
migrate/repair): CLI z narzędziami (Read, Edit, ograniczony Bash) dla jednej aplikacji, po świeżej kopii.--allowedToolsz wzorcami,dontAsk, hookPreToolUseodrzucający polecenia z listy zakazów. Po naprawieverify(kod) decyduje: ok alborollback. - Limity planu: przy limicie krok kończy się
rate_limitedi wraca znot_before. Poświadczenia w boksie wygasają →auth_failed+ push. - Wersję CLI przypinamy w obrazie i aktualizujemy świadomie.
Executor (decyzja właściciela, 2026-09-19)
Pierwsza cegła boksu. Zapisuje wyłącznie wąski sidecar executor (ten sam obraz, src/main/executor.ts); driver zostaje uid 1000 bez docker.sock.
- Kontener: root (manifesty są
root:root 600),cap_drop: ALL,no-new-privileges,read_only, bez portu. Mounty:docker.sock,/opt/homelab/compose(ta sama ścieżka co na hoście),backups(0700),/var/lib/docker:rotylko do sprawdzenia wolnego miejsca. Siećexecutor(internal) tylko z driverem. - Zamknięte API, zawsze jedna usługa jednego projektu:
inspect,preflight,backup,apply(zdry_run),rollback. Wejście i wyjście walidowane zod (domain/executor-api.ts), odpowiedzi to same hashe, nigdy treść.envani rozwiniętego compose. - Które aplikacje są aktualizowane naprawdę, decyduje jedno miejsce: przełącznik „Real updates” (
app_config.apply) na stronie Apps. Executor sam z siebie nigdy nie rusza aplikacji z absolutnego STOP ani własnego projektuhomelab-upgrade;EXECUTOR_PROJECTSwcompose.yamldomyślnie*(wszystkie), jawna lista zawęża. Pierwotnie (rano 2026-09-19) lista była obowiązkowa jako drugi zamek; właściciel uznał ją wieczorem za nieintuicyjną („wszystko tak, jak się konfiguruje w Apps”). - Kopia przed zmianą: manifest i
.env(krokbackup) oraz vzdump VM103 młodszy niż 48 h (backup_check). - Początkowo (rano 2026-09-19) tylko ręcznie, ze STOP
confirm_applyprzed każdymapply. Wieczorem właściciel: „skrypt robi upgrade, dopóki wszystko przechodzi; kiedy STOP albo kontener nie wstał, wchodzi model, patrzy i naprawia”. Teraz aktualizacja w proguautoidzie sama, a nieudanyverifyprzejmuje krokrepair(model).
Grupy (decyzja właściciela, 2026-09-19)
Projekt compose może mieć kilka usług z własnymi obrazami (grafana + influxdb, nextcloud + mariadb + redis). Wiersz app_config to składnik: klucz app (dla głównej usługi nazwa projektu, dla pozostałych <projekt>-<usługa>), project, service. Składniki jednego projektu tworzą grupę:
plankolejkuje pierwszy włączony składnik każdego projektu; następny rusza dopiero, gdy poprzedni skończy swój łańcuch w przebiegu (także po „Proceed”). STOP wstrzymuje resztę grupy, „skip” ją przepuszcza. Kolejność: aplikacja przed swoją bazą (pozycja wapp_config).- Usługi na identycznym obrazie (nextcloud
appicron) to jeden składnik:applyzmienia je razem, diff dopuszcza dokładnie ich linieimage:. - Bazy danych: wzorzec tagów zawężony do obecnej wersji głównej (skok postgresa 14→15 to dump/restore, nie zmiana obrazu). Ruchomy tag (
postgres:14,redis:7-alpine) nie mówi, jaka wersja działa: porównanie uznałoby „14” za 14.0 i zaproponowało cofnięcie, więc taki składnik jest wyłączony, dopóki manifest nie przypnie konkretnej wersji.
Model naprawia (decyzja właściciela, 2026-09-19 wieczór)
„Skrypt robi upgrade, dopóki wszystko przechodzi; kiedy STOP albo kontener nie wstał, wchodzi model, patrzy i naprawia.”
- Krok
repair: gdyverifynie przeszedł, a aplikacja ma „Migration executor” (sonnet/opus),verifysam kolejkujerepair(jedna próba; drugi nieudanyverify= STOP dla właściciela). Na dowolnym STOP właściciel może wybrać „Repair (model)”. - Kontener
model: niezmodyfikowanyclaudeCLI (przypięta wersja w obrazie) zalogowany kontem właściciela (CLAUDE_CONFIG_DIR=/claude). Bezdocker.sock, bez plików projektów i bazy. - Model działa tylko przez narzędzie
hlw sesji executora otwartej na jeden projekt:ps,logs,compose(sekrety jako<secret:KLUCZ>),write,pull,up,restart,exec-rules(lista komend, któreexecw ogóle przyjmie: model patrzy, zamiast zgadywać),execw kontenerach tego projektu. Egzekwowane w kodzie (domain/agent-guard.ts): zapis compose sprawdzany listą zakazów z „Granic” względem manifestu z otwarcia sesji; wyjścia maskowane wartościami sekretów projektu; hookPreToolUseprzepuszcza w Bash tylkohl, a Read/Edit/Write tylko w katalogu sesji; trasy drivera w executorze i/runmodelu odpowiadają tylko adresowi drivera. Każde wywołanie jest w logu sesji i w wyniku kroku. - Wyjątek za zgodą właściciela (2026-09-23): na bramce po nieudanej naprawie przycisk „Repair with full access” uruchamia jedną ponowną naprawę w sesji
full. W niej, tylko w tym projekcie,execprzyjmuje dowolną komendę i użytkownika,runuruchamia jednorazowy kontener usługi z jej wolumenami (np.chownkatalogu danych przy crash-loopie), awritesprawdza tylko poprawność compose, bez listy zakazów. Maskowanie sekretów, log sesji, kopia i rollback działają dalej. To jedna próba na run: po niej zostaje rollback albo skip. - Krok modelu trwa minuty, więc daje znak życia: driver (
watchSession) co 5 s czyta log otwartej sesji (/session/logexecutora, tylko dla drivera, bez zamykania sesji) i zapisuje w kroku liczbę akcjihloraz ostatnią z nich; appka pokazuje to w wyniku kroku „running” (SSE odświeża widok). Gdy executor jest nieosiągalny, nic nie jest raportowane, żeby nie udawać żywego modelu. Odpowiedź/runsidecara jest jednym JSON-em po końcu pracy, więc wysyła nagłówki od razu i spację co 30 s (limit 5 minfetchw Node), a rozłączenie drivera przerywaclaude. Koszt nieudanego przebiegu (total_cost_usdz CLI) jest zapisywany, nie zerowany. - Zwykłe operacje executora i cała sesja modelu używają jednej współdzielonej blokady. Przed każdą mutującą sesją
repairdriver ponownie sprawdza świeżość vzdump i upewnia się, że ma kopię manifestu z.env(robi ją, jeśli krok nie niesie jużbackup_id).hl execjest domyślnie całkowicie wyłączony; dopuszczone są tylko dokładne czwórkiproject,service,user, pełneargvzconfig/agent-exec.json, bez wildcardów i bez powłoki. - Kontekst modelu: tylko ta aplikacja (co nie przeszło, docelowy obraz, pozostałe składniki grupy z wersjami) i skill jej projektu. Zgodność wersji w grupie ocenia model (np. czy Nextcloud obsługuje daną MariaDB).
- O wyniku decyduje kod: po
repairjeszcze razverify.
Skille
Jeden na projekt compose (tabela skills), fakty dla modelu, które nie zmieniają się z wersją: wymagania (np. wersje bazy), co jest STOP, jak sprawdzić i jak wrócić. Zaimportowane raz ze starego repo (config/skills/<projekt>.md), dopisywane do bazy, gdy ich brak, edytowane w appce (Apps → aplikacja → Skill). Do modelu trafia tylko skill naprawianego projektu.
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 run (
app/run-report.ts,infra/ha-notifier.ts: POST donotify.<usługa>,HA_URL,HA_TOKEN,HA_NOTIFYz.env; HA stoi na VM103): gdy run czeka na decyzję właściciela, gdy się nie powiedzie i gdy run „Real” się skończy. Wysyła driver po każdym kroku; link prowadzi do strony runu. - Harmonogram: cron per workflow (tabela
workflow_schedules, sprawdza serwer), bez osobnego przebiegu nocnego i watchdoga. - Appka: tylko LAN, za traefik; logowanie do przemyślenia przed wystawieniem poza dom. Widoki: Overview (co czeka na decyzję, co trwa, co się skończyło, co następne), Apps (konfiguracja, skill, runy i harmonogram aplikacji), Runs (lista i strona runu z grafem, bramką i historią), Workflows, Functions, Models. Odświeżanie na żywo przez SSE.
- Skille: parametry mechaniczne w
app_config;SKILL.mdtylko 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).
Rozszerzalny silnik workflow (decyzja właściciela, 2026-09-20)
Obecna kolejka kroków pozostaje działającą bazą podczas rozbudowy. Docelowy silnik ma pozwalać układać i wersjonować przepływy niezależnie od konkretnego dostawcy modelu oraz dodawać kroki systemowe bez rozbudowywania drivera o kolejne wyjątki.
- Definicja workflow przechodzi cykl
draft → validate → publish. Opublikowane wersje są niemutowalne, a każda instancja wskazuje konkretną wersję. - Każda instancja zapisuje pełny snapshot konfiguracji potrzebnej do odtworzenia przebiegu: dokumenty workflow, konfiguracje aplikacji, skille, profile modeli, wersje funkcji i politykę akcji uprzywilejowanych.
- Przejścia stanu są czystą, deterministyczną maszyną XState 5. Do SQLite zapisujemy własną wersjonowaną projekcję domenową i append-only eventy, a nie wewnętrzny snapshot aktora XState.
- Workflow może zawierać zagnieżdżone podworkflow i sekwencyjne
for-each, ale nie rekursję. Globalnie wykonywany jest najwyżej jeden node. Wiele instancji może jednocześnie oczekiwać wqueuedlubwaiting; wyłączność obejmuje tylko statusrunning. workflow_instanceoznacza całe uruchomienie workflow, przyszłynode_attemptpojedynczą próbę node'a, aaction_invocationwywołanie akcji uprzywilejowanej.- Kroki użytkownika to wersjonowane funkcje TypeScript albo POSIX shell zapisane w SQLite. Mają obowiązkowe testy oraz ścisły wynik
{event, summary, data}. Wykonuje je izolowany, efemeryczny runtime z jawnymi limitami i capabilities. - Operacje wymagające wysokich uprawnień są zamkniętymi akcjami w executorze, oddzielonymi od funkcji użytkownika. Przykład docelowej reguły: po spełnieniu warunku wolnego miejsca workflow może wywołać jawnie dozwoloną akcję Docker image prune. Sam
prunenie jest domyślnym zachowaniem i wymaga osobnej polityki oraz implementacji. - Integracja modeli korzysta z portu
ModelExecutori jawnych, wersjonowanych profili. Claude i Codex działają jako osobne sidecary; błędy nie powodują cichego przełączenia dostawcy. Kolejny dostawca, np. Gemini, ma wymagać nowego adaptera i profilu, nie zmian w domenie workflow. - Profile Codex są udostępniane przez MCP. Konfiguracja modelu i narzędzi należy do snapshotu instancji.
- Edytor webowy używa generycznego grafu Cytoscape z możliwością zmiany kolejności, przypinania pozycji oraz rozwijania zagnieżdżeń. Monaco służy do edycji funkcji. Układ wizualny jest osobną, wersjonowalną metadaną i nie zmienia semantyki workflow.
- Historia aplikacji jest append-only. Widok w stylu osi Jira pokazuje eventy, zmianę stanu, aktualny node i powiązaną instancję bez wyprowadzania prawdy z logów tekstowych.
- Obecny proces aktualizacji, jego bramki i zakazy zostaną odwzorowane jako opublikowany workflow oraz porównane w trybie shadow przed przełączeniem. Do tego czasu nowy subsystem działa obok obecnego drivera.
Szczegółowe etapy i obowiązkowy cykl pracy agentów są zapisane w docs/IMPLEMENTATION.md.
Arcane i compose w git (decyzja właściciela, 2026-09-24)
Dockge zastępuje Arcane (BSD-3, ghcr.io/getarcaneapp/manager, wersja przypięta, bo wydania są co kilka dni; Portainer odrzucony jako przekombinowany i idący w płatną wersję). Pliki compose.yaml trafiają do repo na Gitea, a Arcane wdraża je sam po zmianie w git. homelab-upgrade zostaje „mózgiem” (wersje, release notes, bramki, verify, naprawa modelem), Arcane jest warstwą wdrożenia i UI do ręcznej pracy.
Co daje Arcane (dokumentacja getarcane.app, sprawdzone 2026-09-24): projekt z git w trybie Pull (repo jest źródłem prawdy, pliki compose w Arcane tylko do odczytu, osobna ścieżka compose na projekt, więc jedno repo z folderem na projekt); .env zostaje lokalny, a przy synchronizacji lokalne wartości wygrywają z .env.git; webhook POST /api/webhooks/trigger/{token} uruchamia synchronizację lub redeploy i od razu odpowiada 202 (bez wyniku); REST API z nagłówkiem X-Api-Key (specyfikacja w /api/openapi.json instancji); własne auto-aktualizacje obrazów domyślnie wyłączone, a etykieta com.getarcaneapp.arcane.updater=false blokuje je niezależnie od UI; hooki pre-deploy tylko dla Pull.
Zasady:
- Compose tylko z git. Każda zmiana compose z homelab-upgrade (aktualizacja,
docker.migrate, zapis modelu przy naprawie) to commit i push, nie zapis pliku; inaczej Arcane przy synchronizacji ją nadpisze. Rollback togit reverttego commita..envi kopie danych bez zmian (lokalnie). - Każdy projekt zarządzany przez homelab-upgrade ma
com.getarcaneapp.arcane.updater=false. - Po pushu executor sam woła webhook Arcane (bez zależności od webhooka Gitea), czeka, aż plik w katalogu projektu ma skrót commita, a o sukcesie decyduje istniejący
verify(obraz i health), nie status Arcane. - Tryb per aplikacja (
direct|git) do stopniowego przełączania; dry run nie pushuje. Integracja za portem, testy na fejkach.
Do sprawdzenia w pilocie: SSH na porcie 2244, czy Pull robi up -d tylko przy zmianie plików, zachowanie przy zmianie pliku na dysku, ścieżki REST do statusu synchronizacji, PROJECTS_DIRECTORY wskazany na istniejący /opt/homelab/compose (pierwszy Pull nadpisuje pliki projektu wersją z repo, więc repo musi mieć dokładnie obecne pliki), otwarte zgłoszenie #2731 (uprawnienia GitSync).
Historyczny plan budowy obecnego systemu
- Decyzje: repo, Node, klucz — zrobione. Do zrobienia przez właściciela: domena appki w traefik, logowanie
claudew boksie, kształt boksu. - 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. - Port skryptów na TS z
CommandRunner(lokalnie na VM103). - Handlery kroków skryptowych + import skilli + przebieg suchy.
- Boks.
- Kroki z modelem (
ClaudeCliExecutor, szablony, parser, deny-lista). - Appka: pełna (rejestracja aplikacji,
skill_gen). - Powiadomienia i dwa małe skrypty po stronie hosta (data kopii, watchdog).
- 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; uzgodnione przy szkielecie (2026-09-18): język kodu i UI angielski, dokumenty polskie; stack Fastify, Vite + Preact, zod, Vitest; sterownik SQLite to wbudowany node:sqlite (Node 24.21, SQLite 3.53, bez natywnej kompilacji); TypeScript ~6.0.3 do czasu wsparcia 7.x przez typescript-eslint. Uruchamianie i układ kodu: docs/DEV.md.