recreateBootStaleApps recreates a present drive-backed app when boot-stale OR
exited/restarting/unhealthy (the recency-only gate missed already-exited apps).
Still skips healthy long-running + cleanly user-stopped apps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes guest-reboot convergence. driveGateLoop runs recreateBootStaleApps once
at startup: deployed drive-backed apps whose drive is present (BoundUnderParent)
and whose containers started recently (fresh guest boot, not a controller-only
restart) are recreated (down+up) onto the re-propagated drive. Paired with agent
v0.35.0's drive re-propagation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The drive-absent gate treats a stable path usable only when bound under the parent
(BoundUnderParent), not merely host-mounted. Makes a host reboot converge: apps
stay gated until the agent binds the drive under the parent, then are restarted
(recreated) on the populated path. Test updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix caught live: planDriveGates falsely marked the internal SSD path
/mnt/sys_drive/felhom-data disconnected (agent never reports it), which would
block starting SSD-resident apps. Gate now skips non-/mnt/felhom-drives/ paths.
Regression case added. No apps were stopped (none depended on the SSD path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drives are visible in-guest only at the STABLE /mnt/felhom-drives/<name>; the
registered path + HDD_PATH + FileBrowser source repoint there while agent calls
map back to raw /mnt/<name> (agentWhere). Enroll binds-under-parent before
register. Drive-absent GATE (planDriveGates + 30s driveGateLoop) stops/blocks
apps when a drive vanishes and auto-restarts on return; start-gate refuses start
when the drive is absent. H1 endpoints (disconnect/reconnect/restart-apps) routed
onto host-side ops. Non-hollow tests + companions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
agentapi.Decommission + handleStorageDecommission (migrate-all-or-none, Change 2):
migrate-then-decommission via the migration done-hook, or decommission-anyway (stop
apps, keep HDD_PATH). 'Hiányzó tárhely' badge on dashboard/stacks/app card when an
app's drive is decommissioned/disconnected/absent. Change 4: registerStoragePath
clears the decommissioned marker on re-enroll (ClearDecommissioned had no callers).
Non-hollow tests incl. mutation-proven Change-4 companion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ServeStorageAPI gains POST /api/storage/migrate (whole-namespace), POST
/api/storage/migrate-app (single app), GET /api/storage/migrate/status (poll).
settings.html: the greyed migrate-all span becomes a real target-select + button +
shared progress panel; app_info.html gains a per-app 'Áthelyezés másik tárhelyre'
control. Both poll the shared status endpoint and render Hungarian phase progress.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Traefik only publishes a route to a healthy container, so an unhealthy deployed app
returns 404 at its URL though the container runs — previously shown only as 'Nem
egészséges' with no hint the URL is dead. New routeUnpublished() funcmap helper +
a distinct indicator on the dashboard and stacks cards (gated on .Deployed). Tests:
routeUnpublished across all states, real templateFS parses with the funcmap, and the
card guard renders the indicator only for deployed+unhealthy.
NewServer launches the SyncFileBrowserMounts goroutine (reads integrationMgr)
from the constructor, BEFORE main.go's SetIntegrationManager write — so the
init-only happens-before that covers the other Set* fields does NOT hold here,
making it a genuine data race (handlers.go:358/360/1433 reads vs server.go:162
write). Converted the field to atomic.Pointer[integrations.Manager]; setter
Stores, all 3 readers Load(). Regression test reproduces the concurrent access
(clean under -race; flags on the pre-fix plain-pointer field).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reserved-buffer headroom guard on the Docker-data volume (system/dockervol.go,
max(5GB,10%)); deploy-time hard gate refuses (HTTP 507) when below the buffer
(api/router.go); deploy page warns + disables the button (deploy.html); runtime
disk monitor confirmed to watch the Docker volume above the buffer. Log rotation
baked into the golden (agent side). Phase 1 = felhom-agent v0.29.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part A of the UI-fixes/storage-spike spec.
A1: enrichHostStorageTargets sorts /api/host-metrics storage_targets
server-side and attaches friendly Hungarian labels + purpose, fixing the
#host-storage-bars reorder-on-poll bug. Display labels only — PVE storage
ids are never renamed.
A2: new GET/POST /stacks/{name}/backup Tier-2 config panel; the "2. mentés"
Beállítás button is repointed there from the dead-end deploy page. Customer
can pin a target drive or disable Tier 2; preference is preserved across the
runner's status writes. Always visible (single-SSD + non-HDD apps included).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4A: scope FileBrowser bind to <drive>/appdata (recovery units + Tier 2 copies under
backups/ are no longer mounted into FileBrowser — customer can't browse/delete the
thing that restores them). 4B: deploy storage-selection step states the chosen drive
holds files while the DB runs on the fast internal SSD + is backed up with the app.
4C: buildStorageBars stable sort + purpose description on the monitoring storage list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier 2 rsync-mirrors each HDD app's recovery unit + appdata to a DIFFERENT physical
disk (the only off-drive protection bind-mounted userdata can get; PBS can't reach it).
Auto-enabled, auto-target: prefer another registered drive (different physical disk via
system.SamePhysicalDevice), else the internal SSD for SMALL units only — with a
size-aware headroom guard that REFUSES rather than fill the ~8G guest rootfs, recording
an honest "needs 2nd HDD" status. Status persisted via the surviving CrossDriveBackup;
"2. mentés" UI card now populated. Daily tier2-backup job + POST /api/backup/tier2.
- backup/tier2.go (engine+selection+headroom), tier2_test.go (headroom arithmetic)
- system.SamePhysicalDevice (linux Stat_t.Dev + stub)
- handlers.go Tier2 UI population + tier2DestLabel; backups.html honest no-target reason
- fixed stale TestBackupCopiesOnPath (old felhom-data layout -> in-guest layout)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore recreates an app from its on-drive unit + the guest's own secrets,
regenerating nothing. reconcileRestoreSecrets (pure, unit-tested) merges the unit's
non-secret env with secrets recovered from the live app.yaml and FAILS CLOSED if a
data-encrypting key is unrecoverable (refuse — a PBS whole-guest restore is needed —
rather than regenerate and corrupt). Resettable secrets missing → warn + proceed.
- backup: RestoreFromRecoveryUnit (manifest -> recover secrets -> gate -> restore
volumes -> recreate definition + redeploy w/ re-pull); falls back to volume-only.
- seams: RecoverStackSecrets/RecreateStackFromUnit (adapter +encKey),
stacks.RedeployFromEnv. Wired into /backup/restore.
- tests: gate (refuse/proceed/verbatim) + data_key parsing.
Gate + reconcile + data_key parsing unit-tested; capture live-validated (v0.53.1).
Full readable-data e2e vs AdventureLog needs the auth-gated dashboard restore — pending.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Backups page: whole-guest backup shown as real DR — target label "Biztonsági szerver –
külön hardver (PBS)"; app-data "Távoli mentés" card now reflects the PBS offsite tier
(guestBackupView.Offsite) instead of "nincs beállítva".
- Model-A double-nest fix: appbackup path helpers take a felhom-data NAMESPACE ROOT (no
internal felhom-data join); backup.Manager.namespaceRoot/AppNamespaceRoot resolve
HDD-vs-systemDataPath provenance so a drive-resident app's backups land single-nested
(<drive>/backups/... on the guest = <drive>/felhom-data/backups/... on the host) instead
of .../felhom-data/felhom-data/.... Writes, deletion (GetStackBackupData/RemoveStack/
ProtectedHDDPaths), wipe-warning scan, and export updated coherently; legacy double-nest
dirs kept protected. New appbackup test asserts no doubled segment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4A: user-data drives are backup-target-eligible (not role-locked) — surfaced in
the drive purpose note. 4B: handleStorageImpact returns backup_copies (apps whose
cross-drive backups live on the drive, via backupCopiesOnPath); the wipe/eject
modal warns they'd be destroyed (stays customer-confirmable — copies redundant).
Cross-drive backup engine remains out of scope. Test: TestBackupCopiesOnPath.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pendingActivationDrives() flags registered drives the agent shows attached but not
live-mounted in the container; settings banner + "Újraindítás most" button →
/api/storage/activate → agentapi.GuestReboot. Batches all pending into one restart.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
agentapi GuestAttach(where) → POST /disks/guest-attach; runStorageInit/Attach +
handleStorageRegister call attachIntoGuest after register (best-effort, P3 heals).
Closes Branch A: enrolled drives become usable in the guest, banner clears.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part 2 of the USB/backup spec. agentapi: StatusResponse.Backup record, DueResponse
age_seconds, RestoreTestStatus(). New "Rendszermentés (teljes mentés)" section
(read-only: last backup/target PBS-vs-local/next-due/restore-test) + "Mentés most"
manual trigger that goes through the quiesce loop (controller owns quiescing):
quiesce.Loop gains mutex + TriggerNow() (single-flight, async). New
/api/guest-backup/{trigger,status} (distinct from apiRouter's /api/backup/*).
App-data rows relabeled under an "Alkalmazás-mentések" divider. Config → slice 10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
backups.html still referenced .Backup.{RepoStats,LastBackup,ResticSchedule,
NextBackup,PruneSchedule,Retention,SnapshotHistory,LastCheckTime,LastCheckOK} —
fields removed from FullBackupStatus in the 8C de-privileging (disk-tier backup
moved to the agent). Field access on the slimmed struct 500s. Removed the dead
restic/snapshot/repo-stat sections; kept the app-data (DB dumps + per-app) view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
containerOnNetwork misread the absent-key '<nil>' as "already attached", so
wireController skipped docker network connect -> traefik 502'd felhom.<domain>.
Now lists network names and matches exactly. Also removed dashboard.html's dead
CrossDrive* block (slice-8C leftover) that 500'd the dashboard via gt <nil> 0,
exposed once v0.41.1 made the dashboard reachable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New internal/infra package renders traefik/cloudflared/filebrowser from config
(pinned images, single source of truth; web filebrowser path delegates here).
stacks.EnsureBaseStack deploys the traefik-public network + the three stacks,
single-flight + idempotent + non-fatal; wired to first boot and every health
tick. monitor.EffectiveProtected drops cloudflared when no tunnel token.
Section-G fix lives in felhom-agent build-golden.sh (same-path stacks bind).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove five orphaned HTML templates left behind when slice 8C retired the
disk/storage/restore web handlers (storage_handlers.go, handler_restore.go and
the /api/storage/* + /api/restore/* routes): storage_init, storage_attach,
migrate, migrate_drive, restore. Zero .go references, zero cross-template
references, no route, no nav entry; embed is a glob so deletion is safe (14
templates remain, build + tests green). No behaviour change; the deleted pages
were already unreachable.
Also ships the live demo validation (v0.39.0) writeup in REPORT.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add agentapi HostMetrics() + a thin /api/host-metrics proxy to the agent's
new GET /host/metrics, and a 'Szerver allapota (gazdagep)' card on the
monitoring page rendering host CPU%/load/mem/CPU-temp(n/a)/uptime + per-
storage capacity bars (thin-pool fill, disk temp/wear). Polls every 8s.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The backups page template references .HasVolumeData on the status table
rows but the AppBackupRow struct was missing this field, causing a
template error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Docker named volume backup to Tier 1 (dump to tar, include in restic)
and Tier 2 (copy tars to rsync mirror _volumes/ dir)
- Fix volume name resolution: use project-prefixed names (mealie_mealie_data)
- Fix double Tier 1 in restore dropdown: filter snapshots by app's home drive
- Add Tier 2 restore: RestoreAppFromTier2() restores from rsync mirror
- Show Tier 2 entry in restore dropdown when cross-drive backup succeeded
- Add .fab import link in restore section
- Volume-aware restore type banners and backup content labels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Users couldn't find metadata provider fields (IGDB, ScreenScraper, etc.)
on the app info page. Move them to the deploy page where all other
settings (integrations, geo-restriction) already live.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The gtstef/filebrowser image bakes FILEBROWSER_CONFIG=/home/filebrowser/data/config.yaml,
but controller mounts config at /home/filebrowser/config.yaml. Override the env var in both
generateFileBrowserCompose() and docker-setup.sh so FileBrowser reads the controller-managed
config with proper sources and database path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detect and offer to format empty (no filesystem) partitions on the system
disk. Adds IsSystemPartition() for granular per-partition safety checks
instead of blocking the entire system disk. Init wizard shows formatable
partitions with appropriate warnings. Add felhotest demo node to docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Deactivated drives (Schedulable=false) now treated like disconnected for
Tier2 backups. New IsStoragePathSchedulable() checks active+connected+not
decommissioned. UI shows yellow "Cél meghajtó inaktív" badge, scheduler
skips silently with WARN log.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, removing a storage drive from the controller only marked it as
disconnected if the StoragePath entry still existed with Disconnected:true.
Drives removed entirely from storage_paths were invisible to the check,
causing Tier2 backup UI to show green "Sikeres" and scheduler to attempt
backups to a no-longer-managed destination.
New IsStoragePathKnown() method covers both cases. UI shows yellow
"Cél meghajtó leválasztva" and scheduler skips silently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- IsUSBDevice/diskModel: strip findmnt bind-mount suffix [/subdir] before
parsing device path (fixes USB badge not showing for attach-wizard drives)
- crossdrive.go: skip disconnected src/dest drives with WARN log instead of
returning error (prevents noisy error status in settings.json)
- handlers.go: detect Tier2 destination disconnection, set yellow status dot
instead of red, skip ValidateDestination for disconnected paths
- backups.html: new template branch showing "Cél meghajtó leválasztva" badge
with grayed-out info and hidden "Futtatás most" button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add structured operational logging at INFO, WARN, and ERROR levels to
every controller module. Standardize custom prefixes ([GEO], [SCHED],
[SYNC]) to use [INFO/WARN/ERROR] [module] format. Fix misleveled logs
(WARN->ERROR for data loss scenarios, WARN->INFO for routine operations).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add detailed [DEBUG] logging to every controller module when
logging.level is set to "debug". Each module with stateful debug
uses SetDebug(bool) wired from main.go. Covers stacks, backup,
cloudflare, integrations, system, monitor, settings, scheduler,
web handlers, storage, metrics, API, selfupdate, and assets.
Also includes the app export/import (.fab bundles) feature from
v0.32.0 and its debug page integration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Normal storage add/remove no longer nukes the FileBrowser database volume.
A .fb-reset flag file is written during restore and consumed on next startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FileBrowser Quantum caches user source preferences in its SQLite
database. After a restore, the config.yaml gets correct sources but
the database still references the old "srv" source from docker-setup.sh
initial install. Now SyncFileBrowserMounts() detects when sources
changed and runs docker compose down -v to reset the database before
recreating.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace traffic light colors (green/yellow/red) with brand palette:
- Primary actions: blue gradient
- Secondary actions: ghost/outline
- Destructive actions: ghost with red hover (modals keep filled red)
- Running cards: blue glow instead of green border
- Bottom-aligned buttons via flexbox column layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SyncFileBrowserMounts: use --force-recreate so FB always picks up
config.yaml changes (bind mount not detected by docker compose up)
- OnlyOffice compose template: add Traefik middleware to forward
X-Forwarded-Proto=https (fixes mixed content errors in browser)
- Nextcloud handler: add StorageUrl=http://nextcloud for internal
file download callbacks from OO Document Server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SyncFileBrowserMounts regenerates config.yaml from scratch, overwriting
any integration config. The old approach used an async OnStackStart hook
after container restart, which failed due to timing issues (stack state
not yet refreshed).
New approach: ReapplyConfigForTarget() writes integration config
synchronously after config generation but before container restart,
with a no-op RestartStack since the caller handles restart.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Protected stacks like FileBrowser have no app.yaml so Deployed=false,
which caused the URL link condition to fail. Now also shows the URL
when the stack is protected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
User feedback: these settings belong on the Beállítások (settings) page,
not the app description/details page. Moves both sections from app_info.html
to deploy.html and rewires data in deployHandler instead of appDetailHandler.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generic integration system for connecting deployed apps via toggle UI.
First handlers: OnlyOffice→FileBrowser (config.yaml patch) and
OnlyOffice→Nextcloud (occ CLI). Lifecycle hooks auto-suspend on
stop and re-apply on start.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>