Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nhk3eBHT8Mg5L8c2aj57aU
12 KiB
CLAUDE.md — Project Instructions for Claude Code (felhom-controller)
Read automatically at session start. Stable orientation only — current state lives in
CONTEXT.mdand the top ofCHANGELOG.md, never here. Cross-repo orientation: workspace-root/mnt/5_hdd/felhom.eu/git/CLAUDE.md.
!!! IMPORTANT !!!
- Always update CHANGELOG.md whenever you modified the code, and pushed to git!!
- IF controller feature changed (new/modify/remove) always update the relevant part of controller/README.md with the architectural change!!
Project overview
Felhom is a managed home-server business for Hungarian customers. This repo contains the felhom-controller — the Go application that manages Docker Compose stacks inside each customer LXC guest via a Hungarian-language web dashboard.
Read in this order:
REUSE.md— before writing new code (canonical helpers, patterns, traps, seams).CONTEXT.md— current project state, decisions, roadmap (update after each session).controller/README.md— full feature/architecture reference (update when features change).TASK.md— the current task to implement (if it exists).
System context — the three-component model
The project runs on Proxmox, with a locked three-component model:
- Hub (
felhom.eu/hub/) — operator backend on k3s. - Host agent (
felhom-agent/) — one per Proxmox host; operator-tier; owns ALL Proxmox interaction. - In-guest controller (THIS repo) — one per customer LXC; Docker-only; holds NO Proxmox
credentials. De-privileged: disk/host/Proxmox concerns are delegated to the host agent via the
pinned local-API client (
internal/agentapi); the controller keeps the app domain — stack/deploy management, the Hungarian web UI, app-data backup, metrics/telemetry, integrations, git-sync, notifications. Whole-guest backup (PBS vzdump) is the agent's.
Authoritative maps:
felhom.eu/documentation/architecture/01/02/03-*.md(topology/trust, controller module map, host agent) + the code-verified feature docs infelhom.eu/documentation/controller/. Match the current code, not summaries, if they drift.
Don't confuse the two ex-"controllers": felhom-agent (host, operator-tier, was
proxmox-controller) vs this felhom-controller (in-guest, was deploy-felhom-compose).
Layout (verified against the tree)
controller/cmd/controller/ entry point + startup wiring (scheduler block, init-only setters)
controller/internal/
agentapi/ pinned-TLS client to the host agent's per-guest local API (THE disk seam)
api/ REST /api/* router (writeJSON envelope, limitBody, config writes)
appbackup/ felhom-data paths/namespaces, DB dumps, userdata skeleton (shared primitives)
appexport/ .fab export/import bundles (password crypto, strict segment validation)
assets/ app logo/screenshot sync from the hub
backup/ app-data backup manager, recovery units, tier-2 copies, offbox restic
bootstrap/ bootstrap.json ingest → controller.yaml (Day-0 + refresh)
channelhealth/ agent-channel health checker (debounce + born-down alerting)
cloudflare/ geo-enforcement remnant (agent-delegated)
config/ controller.yaml load/validate (LoadPermissive = setup-mode only)
crypto/ AES-256-GCM app.yaml secret encryption (ENC: prefix)
infra/ traefik/cloudflared/filebrowser base-stack templates
integrations/ app-to-app integrations (e.g. OnlyOffice)
mailrelay/ app-email SMTP shim → hub relay
metrics/ telemetry collection
monitor/ health checks, protected containers
notify/ hub event push (typed Notify* wrappers)
quiesce/ quiesce loop for whole-guest backup (marker + recover)
recovery/ recovery-unit restore
report/ hub report builder/pusher + pull-based config refresh
scheduler/ background jobs (Every/Daily, Budapest DST-safe)
selftest/ startup self-checks
selfupdate/ controller image self-update via the agent swap
settings/ settings.json persistence (registry, flags, corruption recovery)
setup/ first-boot setup wizard (own CSRF)
stacks/ compose ops: deploy/delete/migrate/state (THE app domain core)
sync/ git-sync of the app catalog
system/ mounts/probes (linux + permissive _other stubs)
util/ small shared helpers
web/ dashboard UI: server, auth/CSRF, handlers, funcmap, templates (Hungarian)
Per-package helpers/seams/traps: REUSE.md (maintained same-commit as helper changes).
Conventions & cardinal rules
- Trunk-based — no branches. All shippable work commits directly to
main;mainequals what is deployed. Report-only artifacts →felhom.eu/documentation/(audits/,backlog/). Risky fixes are implemented during the supervised session itself, onmain; if a fix can't be verified/shipped, revert + report — never park on a branch. - Code quality: double-check for bugs/edge cases; add debug logging; ask rather than guess.
- All UI text is Hungarian (Budapest timezone). Design tokens/gates: use the
felhom-ui-designskill; templates must passcontroller/scripts/template_id_gate.py+emoji_gate.py. - Testing doctrine (non-hollow tests, red-proofs, seams): use the
felhom-testingskill. - Logging: new leveled lines use
internal/logx(DEBUG always reaches the debug ring; stdout respectslogging.level); English, keys-never-values, durations on outcomes — full rules infelhom.eu/documentation/runbooks/logging-conventions.md. - Update
REUSE.mdif you added/changed/deprecated a shared helper or pattern (same commit). - Coupled features (controller behavior that depends on a specific agent version): add a
featureProbestable row ininternal/agentapi/features.go+ aSupportsgate call at the feature's entry point; declareMinAgent: X.Y.Zin the CHANGELOG entry header. Rules:felhom.eu/documentation/runbooks/publish-train-rules.md.
In every repository where you make a change, update both files in that repo:
CHANGELOG.md— cumulative log, newest on top.REPORT.md— overwrite with the most recent implementation/validation summary only.Never write secrets into any committed file — reference them as "stored out-of-band".
Live validation
Exercise the SERVER-SIDE PIPELINE a real user triggers, end-to-end (connect → enroll → deploy). The
forbidden shortcut is BYPASSING that pipeline (the F9 episode: raw agent guest-attach + hand-set
state). claude-in-chrome is NOT available in the DooPlex environment — the standard method is
endpoint-level: invoke the exact endpoint the UI invokes (no server logic is skipped, only
rendering) and say which method was used. Strict end-to-end UI coverage is a manual click-through.
Environment & access
Claude Code runs on DooPlex (192.168.0.180, Debian 13, user kisfenyo); repos in
/mnt/5_hdd/felhom.eu/git/, build dirs in /mnt/5_hdd/felhom.eu/build/. All repos hosted at
gitea.dooplex.hu/admin/. Builds are local commands; felhom-pve is one SSH hop.
| Host | Access | Role |
|---|---|---|
| DooPlex (this host) | local — /mnt/5_hdd/felhom.eu/{git,build}/ |
build + push images, sudo kubectl |
Demo Proxmox host demo-felhom |
ssh felhom-pve (root@192.168.0.162) |
pct into guests; live validation |
| Demo guest 9201 | ssh felhom-pve "pct exec 9201 -- ..." |
the live demo controller (golden/bootstrap-managed) |
| felhotest (legacy) | ssh -p 33022 kisfenyo@router.abonet.hu |
OLD /opt/docker compose mechanism |
Legacy: Windows workstation. Until 2026-07-19 CC ran on Windows 11 with repos in
E:\git\, and every remote command neededSSH=/c/Windows/System32/OpenSSH/ssh.exe(Git Bash's ssh lacks the Windows agent and fails silently — seedocs/vscode-ssh-fix.md), plusMSYS_NO_PATHCONV=1forpct exec. Retained in case that environment is revived.
TEMPORARY — felhom-pve is at a remote site (until ~2026-08-02). The home-LAN literal
192.168.0.162is NOT reachable from DooPlex for the duration. Access via Tailscale: felhom-pve = 100.70.170.35; theHost felhom-pveentry in~/.ssh/configon DooPlex already points there (the direct-LAN path stays available asHost felhom-pve-lan). Delete this block on return. All documentedssh felhom-pve/pct execworkflows are unchanged. Path is direct (not DERP), ~37 ms rtt per hop. At the remote site the host is on DHCP and currently holds192.168.0.147(the guest holds.104); no Pi-hole there — the guest reachesgitea.dooplex.huand*.demo-felhom.euvia public paths. The host agent is DOWN for the duration: itslocalapibinds the literal192.168.0.162, which no longer exists →bind: cannot assign requested address, so every agent-backed feature (storage, PBS backup, quiesce, restore-test, DR) is unavailable until fixed. Details + findings:felhom.eu/documentation/audits/AUDIT-vacation-remote-ops-2026-07-20.md
External access via Cloudflare Tunnel → Traefik; Pi-hole forwards *.demo-felhom.eu → .162 locally.
Build & deploy — MANDATORY after code changes
Full runbook: use the felhom-build-deploy skill. Summary (guest 9201 is bootstrap-managed —
no compose file; felhom-controller-bootstrap.service runs the tag in /etc/felhom-controller-image):
Clean-tree gate before any build:
git status --porcelainmust be empty andgit rev-parse HEADmust equalgit rev-parse origin/mainin the repo being built. An unpushed change does not exist — never build a dirty or unpushed tree. Thegit pullin the build step stays (it is a no-op when you work in this tree, and load-bearing if anything was pushed from elsewhere).
| Step | Command |
|---|---|
| 1. Commit + push | git add <explicit paths> && git commit -m "..." && git push |
| 2. Build + push image | cd /mnt/5_hdd/felhom.eu/build/felhom-controller && git -C /mnt/5_hdd/felhom.eu/git/felhom-controller pull && ./build.sh <VER> --push (build.sh does NOT pull — the explicit pull is load-bearing) |
| 3. Deploy (9201) | ssh felhom-pve "pct exec 9201 -- bash -c 'docker pull gitea.dooplex.hu/admin/felhom-controller:<VER> && echo gitea.dooplex.hu/admin/felhom-controller:<VER> > /etc/felhom-controller-image && systemctl restart felhom-controller-bootstrap.service'" |
| 4. Verify | ssh felhom-pve "pct exec 9201 -- docker ps --filter name=felhom-controller --format '{{.Image}} {{.Status}}'" + container logs |
Hub build/deploy lives in felhom.eu (GitOps) — see that repo's CLAUDE.md / the skill. Catalog
changes (app-catalog-felhom.eu): commit+push; controller sync picks them up ≤15 min or via the
"Sablonok frissítése" button.
Session-critical invariants (the rest live in REUSE.md)
docker compose restartdoes NOT pick up new images/env — alwaysup -d(RedeployFromEnv).- Docker's
.Statesays "running" even for unhealthy containers —.Statusparse is the truth. - In-memory
Deployedflag is set BEFOREcompose up -d(slow-pull race); reverted on failure. compose up -dexits 0 on crash-loops — post-start status check is the detection.- Env var KEYS are logged, never values. Protected stacks (traefik, cloudflared, felhom-controller) can't be stopped from the UI.
- Verify a container image HAS the healthcheck tool before using it (BusyBox wget / python3 / curl — catalog REUSE.md maps the families).
Working with CHANGELOG.md
DO NOT read the full file — it is large and will waste context.
- Session start: use
CONTEXT.md+controller/README.mdfor current state. - Adding an entry: Read only the top ~30 lines for format, then Edit-insert after line 1.
- History: Grep for topics instead of reading.
End-of-session checklist
- Commit and push all code changes
- Build, push, and deploy the new controller image (if controller code changed)
- Update CHANGELOG.md with what was done
- Update CONTEXT.md with decisions made, state and what's next
- Update controller/README.md if architecture or features changed
- Verify the deployment is working (check
docker psand logs) - Update REUSE.md if you added/changed/deprecated a shared helper or pattern (same commit)