Commit Graph

653 Commits

Author SHA1 Message Date
admin 242b835a19 v0.81.0: retire drive-activation banner; add standalone "Kiszolgáló újraindítása" button
In the intermediary-mount model an enrolled drive binds live into the running
guest (no reboot), so the "… meghajtó aktiválásra vár / Újraindítás most" banner
was an obsolete relic — also dead since v0.78 (pendingActivationDrives keyed on
the raw MountPath vs the now-stable sp.Path). Removed the banner block +
activatePendingDrives JS (settings.html), the PendingDrives feed (handlers.go),
and the dead pendingActivationDrives helper + its unused internal/system import.

Renamed handleStorageActivate -> HandleServerReboot (split out a testable
serverReboot core), removed the /api/storage/activate case, and mounted the
handler at the new non-storage route /api/server/reboot (RequireAuth+CsrfProtect).
The agent GuestReboot primitive is reused unchanged.

Added the standalone "Kiszolgáló újraindítása" settings card (sibling to the
controller-only "Vezérlő újraindítása"), reusing the pollRestart() loop.

Test: TestHandleServerReboot_CallsGuestReboot (fake diskAgent asserts GuestReboot
invoked once + 202). diskAgent/mockAgent gained GuestReboot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 18:46:31 +02:00
admin 7cce19797e REPORT: v0.80.0 deployed + verified — disk card shows/acts on the stable path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:57:25 +02:00
admin fc94d91d15 v0.80.0: disk card shows + acts on the stable path, not the raw host mount
The storage card displayed each external drive's raw host PVE mount (/mnt/<name>,
which doesn't exist in the guest) instead of the stable in-guest path
(/mnt/felhom-drives/<name> = guest_path) the registry/HDD_PATH/FileBrowser use.
The eject/wipe buttons also posted the raw path, so they would unmount the drive
but leave the stable registry entry orphaned, and the impact warning found no apps.

Fix: card sub-line + eject/wipe buttons use the stable path (regKey); type-to-confirm
name uses the basename; register keeps the raw path. handleStorageWipe maps to raw
via agentWhere() for the agent eject (matching handleStorageEject). Agent ops
unchanged (same raw paths); display + registry bookkeeping corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:54:21 +02:00
admin 9596c8e563 REPORT: v0.79.0 deployed + verified — disk view keys registered on stable path (badge/button now correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:45:05 +02:00
admin a9f0de4c63 v0.79.0: disk view keys the "registered" check on the stable path
Follow-up to v0.78.0. The disk-view JS (settings.html regBadge/actions) checked
registration by the raw mount_path, but the registry stores the stable path since
v0.78.0 — so enrolled drives showed a spurious "Nem regisztrált" badge + Register
button. Fix: regKey(d) = d.guest_path || d.mount_path (agent reports the stable
guest_path per disk); registerDrive still posts the raw path. Display-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:42:37 +02:00
admin 081c1cd77f REPORT: v0.78.0 deployed to 9201 + registry remediated — drives now register at the stable path, 2 connected, badge+banner gone
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:37:12 +02:00
admin 68e7e07838 v0.78.0: storage register uses the STABLE intermediary path, not the raw path
handleStorageRegister (the "Regisztrálás" action for an already-mounted,
unregistered drive) registered the raw /mnt/<name> path verbatim, unlike
runStorageInit/runStorageAttach which map to the stable /mnt/felhom-drives/<name>
path the agent actually binds the drive at. The controller then watched an empty
placeholder dir on the guest rootfs → "Rendszermeghajtón" + stuck "activation
pending" banner after a re-provision.

Fix: register stablePathForName(path.Base(req.Where)); attachIntoGuest still uses
the raw path (the agent operates on raw). Test + red-proof added.

Diagnosis: felhom.eu/documentation/audits/DIAGNOSE-drive-bind-after-reprovision-2026-06-23.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
2026-06-23 17:28:07 +02:00
admin 66d84627b8 docs: v0.77.0 open_path — REPORT + README 2026-06-23 12:05:29 +02:00
admin 904e787dda v0.77.0: per-app open_path for the Megnyitás link
Optional .felhom.yml open_path appended to the open-app URL in dashboard/deploy/app-info
(via .Meta.OpenPath). Empty=bare root. Fixes apps whose UI isn't at / (gokapi /admin, ghost /ghost/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:01:12 +02:00
admin 0b7163e5af docs: v0.76.0 REPORT/CONTEXT — campaign-#3 hardening fixes + live re-test evidence
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:39:20 +02:00
admin b0dd13154b v0.76.0: campaign-#3 hardening (settings .bak recovery, restore stack_name validation, quiesce marker quarantine)
S1: corrupt settings.json recovers from .bak / safe-defaults+preserve, no crash-loop.
F2: validStackName gates restore + export handlers (reject /,\,..,NUL traversal).
S3: corrupt quiesce marker logged + quarantined, not silently dropped.
Tests T-S1/F2/S3 + red-proofs. Agent/hub untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:34:33 +02:00
admin 141d51bf19 test campaign #3: Phase 7 brutal recovery (B1-B3 PASS, B4 skip) + final findings/cleanup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:04:55 +02:00
admin eb5cd8375e test campaign #3: Phase 6 security fuzzing (F1-F4; no tree escape; restore stack_name validation gap finding)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:51:46 +02:00
admin f40fedfbfc test campaign #3: Phase 5 network partitions (N1 tunnel block, N2 PBS block mid-backup — fail-safe + unquiesce, both PASS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:40:11 +02:00
admin 7187ef6b62 test campaign #3: Phase 4 time chaos SKIP (unprivileged guest shares host clock; host jump risks certs/DooPlex)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:31:33 +02:00
admin 8845496c0e test campaign #3: Phase 3 concurrency storms (C1 mutex, C2 flap+gate, C3 agent-kill unquiesce — all PASS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:30:08 +02:00
admin 850f0d4568 test campaign #3: Phase 2 state corruption (S1 settings crash-loop FINDING; S2/S3/S4 pass; symlink no-exfil)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:23:05 +02:00
admin 192bb30a4b test campaign #3: Phase 1 resource starvation (R1-R4 fail-safe held)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:16:45 +02:00
admin ee014b3005 test campaign #3: Phase 0 floor (baseline + verified PBS backup, gate open)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:05:26 +02:00
admin 8b4b9011dd docs: v0.75.0 REPORT/CONTEXT — userdata mountpoint gate + boot-ordering design note + live C6 proof
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:55:08 +02:00
admin 0b2605c5a8 v0.75.0: gate userdata MkdirAll on a live mountpoint (no writes into an absent drive)
Belt (ensureUserdataMounts) + FileBrowser sync skip ensure/mount when an external
drive root is not a live mountpoint -> no 'mkdir userdata: permission denied' + no
rootfs-shadow during a drive-absent window. System/local path never gated. Reuses
system.IsMountPoint; matches planDriveGates external-only rule. T1-T4 + red-proofs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:47:08 +02:00
admin 9d47232d7f test campaign #2: Phase 5 skip (host reboot) + final matrix/findings/cleanup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:09:57 +02:00
admin 608cd02dd4 test campaign #2: Phase 4 (concurrent-restore mutex + stopped-stack backup PASS; snapshots-404 + additive-restore gaps re-confirmed)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:07:51 +02:00
admin 61b2dc02a4 test campaign #2: Phase 3 C7-C8 (quiesce crash-safety + reboot-during-op PASS); phase complete
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:04:08 +02:00
admin dce2362245 test campaign #2: Phase 3 C4-C6 (C5 app.yaml graceful, C6 drive-eject recover + recreate-noise finding, C4 skip)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:50:38 +02:00
admin 3ee26c575f test campaign #2: Phase 3 C1-C3 (kill app/controller/agent — chaos; docker restart-policy not honored finding)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:44:04 +02:00
admin d07848a786 test campaign #2: Phase 2 (storage lifecycle unblocked — disconnect/reconnect + decommission/re-enroll PASS; loopback blocked by agent whitelist)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:34:15 +02:00
admin d3f1b9d323 test campaign #2: Phase 1 (app sweep + edge cases — navidrome HDD-bound, redeploy race PASS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:22:42 +02:00
admin 1190e56a2c test campaign #2: Phase 0 (baseline + safety net + leak-fix gate PASS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:18:49 +02:00
admin c208ddf308 docs: v0.74.0 REPORT/CONTEXT/README — agent connection-leak fix + live proof
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:52:12 +02:00
admin 2a5b88fbf9 v0.74.0: fix controller->agent connection leak (reuse one agentapi client)
agentClient() built a new agentapi.Client (new bare http.Transport, IdleConnTimeout:0)
per call and discarded it without closing idle conns -> one leaked idle ESTABLISHED
socket per call to the agent :8443, exhausting the ephemeral port range after ~5 days
(EADDRNOTAVAIL). Memoize one shared client via sync.Once; harden Transport
(MaxIdleConns/PerHost + IdleConnTimeout 90s). Agent/firewall untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:19:24 +02:00
admin 0bfb481c2a test campaign: Phase 7 (guest reboot PASS) + final feature matrix, findings, cleanup, after-state
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 16:15:37 +02:00
admin 258d13ede4 test campaign: Phase 6 results (monitoring/alerts/notification email/settings/hub DR-recipe-pbs-coord verified)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 16:05:22 +02:00
admin b9b233e537 test campaign: Phase 5 results (storage observe PASS; UI mgmt blocked by controller->agent 8443 defect)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:55:37 +02:00
admin d1d5ff4964 test campaign: Phase 4 results (DR rebuild verified via restore-test + deep 9300 restore, collision-safe)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:49:31 +02:00
admin 7b906cfe3f test campaign: Phase 3 results (backup DB-dump + Tier-2 + restore verified; 2 findings)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:36:08 +02:00
admin 3b0bacab9b test campaign: Phase 2 results (deploy sweep; gitea HC fix + glance defect logged)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:25:51 +02:00
admin e6577c53da test campaign: Phase 0 + Phase 1 results (baseline, PBS backup, komga HC fix verified)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:12:45 +02:00
admin f3146ac7bf controller v0.73.0: DR recipe — emit secret-free customer+apps half in hub report
DR recipe slice (controller half), grounded in SPIKE-dr-recipe-2026-06-16. The
controller emitter is the BOUNDARY enforcement point: v1 ships an explicit
allowlist {catalog_ref, enabled, storage_bindings} and reads NOTHING from
AppConfig.Env, so no ENC:/token/password can leak. storage_bindings parsed from
the compose (${HDD_PATH}/${USERDATA_PATH} volume binds -> {container_path,
drive, subpath}).

Load-bearing tests: TestBuildAppRecipe_NoSecrets (synthetic-secret app -> none
leak) + TestBuildAppRecipe_AllowlistIsLoadBearing (red-proof companion) +
TestAppStorageBindings + TestBuildDRRecipeAppHalf. Red-proofed live: forcing the
emitter to dump Env makes the boundary test fail. recipe_version=1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:38:59 +02:00
admin 0d159d7e34 docs: REPORT/CHANGELOG/README — v0.72.0 FileBrowser boot-recreate convergence + live x2 reboot acceptance
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:09:58 +02:00
admin 6ea25388d7 controller v0.72.0: FileBrowser converges on boot-recreate
processGuestBootChange recreated the drive-backed app stacks but never re-synced
FileBrowser (base-infra, no HDD_PATH), so its drive mounts went stale after a
reboot. Now, AFTER pollLiveBinds confirms the live binds and the apps are
recreated, trigger go s.SyncFileBrowserMounts() so FileBrowser converges against
the now-live drives. Refactored into pure recreateDriveBackedApps(stacks, present,
recreate, syncFB). Tests: FB sync runs once after recreate (red-proofed companion);
runs even when nothing recreated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:37:12 +02:00
admin 14f200c9a1 docs(REPORT): FileBrowser was not recovered after host reboot — two gaps
Found FileBrowser down after the host-reboot drills (my 'all recovered' claim only
checked the 8 felhom-flash apps). Two distinct gaps:
(A) agent-side: felhom-usb did not re-mount after the host-reboot device-letter
    swap (mount unit inactive; agent reports present drive as durable-id absent) —
    a felhom-agent bug, out of controller scope.
(B) controller-side: FileBrowser is base-infra (no HDD_PATH) so processGuestBootChange
    skips it; its SyncFileBrowserMounts runs once at startup, racing the bind, not
    retried. Recommended fix: call SyncFileBrowserMounts after the live-bind poll.

Recovered FileBrowser live (started felhom-usb mount unit -> agent bound it ->
restarted controller -> FB Up healthy). felhom-usb userdata was intact, just hidden
behind the unmounted placeholder. Corrected the host-reboot trust caveat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:56:01 +02:00
admin 6b9603ed26 docs: host-reboot drill + regression analysis for v0.71.0
Re-drilled the HOST reboot (felhom-pve) x2 after the follow-up challenge: both
recover all 8 drive-backed apps automatically; host-btime prefix advances so the
host path triggers processGuestBootChange; the boot-race manifests on host reboots
too (not just guest). Corrected the REPORT's 'host path unaffected' claim.

Regression analysis: the v0.68 sweep genuinely exercised the recovery (it surfaced
a real state-filter bug on the host reboot), so the agent path worked then and
regressed after (controller.yaml reset to golden no-local_api baseline on each
recreate; old MaybeIngest never re-merged). v0.70.0 config round-trip exonerated
(GET /api/config is verbatim). ensureLocalAPI closes the regression on both paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:46:20 +02:00
admin edc09bbdcb docs: REPORT.md for controller v0.71.0 guest-reboot recovery fix
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:21:22 +02:00
admin e2de234325 controller v0.71.0: fix guest-reboot recovery (boot-race + agent-path blocker)
Live diagnosis of drive-backed apps stuck Exited after a pct reboot pinned THREE
sub-causes, fixed together (hardening the existing processGuestBootChange, not a
parallel mechanism):

1. Agent-path blocker (live root cause): agentClient() returned "agent not
   configured" (cfg.LocalAPI.Endpoint empty), so processGuestBootChange AND the
   whole drive gate bailed at the first guard. bootstrap.json had a complete
   local_api block, but MaybeIngest returned immediately on "already configured"
   so a controller.yaml seeded before local_api existed never got the agent path.
   Fix: MaybeIngest now calls ensureLocalAPI on the already-configured path,
   merging local_api from bootstrap.json into the existing controller.yaml when
   missing (no hub re-pull, config preserved; idempotent + fail-safe).

2. Boot-race readiness gate: processGuestBootChange sampled BoundUnderParent once
   during fast startup, racing the ~18s rebind, recreated nothing, burned its
   boot-id one-shot. Fix: gate on the REAL live in-guest bind -- driveBindLive
   checks /mnt/felhom-drives/<drive> is a mountpoint in the controller's own /mnt
   rslave /proc/self/mountinfo; pollLiveBinds waits for it (bounded ~120s) before
   recreating via the normal pipeline. shouldRecreateOnBoot stays state-independent
   so stuck-Exited create-time-failure apps are included.

3. Single-shot fragility: processGuestBootChange ran only once at startup; a
   briefly-unreachable agent right after a guest reboot stranded recovery. Fix:
   driveGateLoop runs it every periodic tick too (idempotent, boot-id gated).

Tests (non-hollow, pre-fix companions, red-proofed): pollLiveBinds waits then
reports live / never-live stays absent / single early sample misses; ensureLocalAPI
merges local_api into a configured controller.yaml that lacks it / no-ops when
present. Live-accepted with repeated pct reboot 9201.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:12:49 +02:00
admin 25e5cb5850 controller v0.71.0: fix guest-reboot boot-race stranding drive-backed apps
Sub-cause: on guest pct reboot, in-guest dockerd auto-starts unless-stopped
drive-backed apps ~18s BEFORE the agent re-binds the drive; the create-time
volume bind fails (mkdir /mnt/felhom-drives/<drive>/userdata: permission denied)
and RestartCount=0 means it's never retried -> stuck Exited. The existing
recovery (processGuestBootChange) RAN but raced the rebind: it sampled the
agent's BoundUnderParent once during fast startup (not live yet), recreated
nothing, and persisted the new boot-id -> burned its one-shot. The periodic gate
never recovered them either (first observation after the rebind -> no transition).

Fix (harden the existing mechanism, no parallel one): processGuestBootChange now
gates on the REAL live in-guest bind. driveBindLive checks whether
/mnt/felhom-drives/<drive> is an actual mountpoint in the controller's own /mnt
(rslave) /proc/self/mountinfo -- true only once the agent's bind propagated,
exactly when docker can recreate the app. pollLiveBinds waits for that (bounded
~120s, poll 2s; rebind lands ~18s) and only then recreates via the normal
pipeline, including stuck-Exited create-time-failure apps (shouldRecreateOnBoot
is state-independent). Single-flight; absent-after-window drives left to the gate;
host-reboot path unaffected; guest-only reboot path now covered.

Tests: pollLiveBinds waits through the rebind then reports live (recreate fires);
never-live drive stays absent; pre-fix companion (single early sample misses the
not-yet-live bind). Red-proofed against a no-wait single-sample.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:46:51 +02:00
admin 38c82db522 docs: REPORT.md for controller v0.70.0 (config-apply self-restart + geo UX fixes + live verification)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:19:05 +02:00
admin 9b7a26585c fix(settings): render restart button unconditionally
The Vezérlő újraindítása card was accidentally placed inside the
{{if .RetrievalPassword}} block, so it was hidden whenever no retrieval
password is set (e.g. the demo). Moved it outside so the self-serve restart
is always available. Found during live verification of v0.70.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:13:18 +02:00
admin c514c0042b docs: CHANGELOG + README for v0.70.0 (config-apply self-restart, restart button, geo report push, always-report geo, autocomplete fix)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:03:08 +02:00
admin 09d75c1e2e controller: fix country autocomplete — reveal dropdown with display:block (C)
Root cause (diagnosed live, not the hypothesised JS throw): filterCountries runs
fine and correctly populates the list, but reveals it with `list.style.display=''`.
The .geo-country-list CSS default is `display:none` (style.css), so clearing the
inline style falls back to none and the populated list never shows — no console
error, just an invisible dropdown. Latent since the geo feature's first commit.
Fix: reveal with 'block'. Verified live (typing "Német" now lists Németország).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:01:11 +02:00