Fix 1: HDD data backup is now mandatory for all deployed apps. resolveAppBackupPaths() iterates ListDeployedStacks() directly — no longer reads GetAppBackupMap() or checks the Enabled flag. DiscoverAppData() drops backupPrefs parameter; BackupEnabled is set from HasHDDData. Five dead settings methods removed: IsAppBackupEnabled, SetAppBackup, GetAppBackupMap, SetAppBackupBulk, GetAppBackupPrefs. Fix 2: Cross-drive backup now triggers a fresh DB dump (DumpStackDB) before running. DBDumper interface added to crossdrive.go; Manager implements it; SetDBDumper wired in main.go. Non-fatal — proceeds with user data backup even if DB dump fails. Fix 3: Restore dropdown shows ALL deployed apps (not just HDD+enabled). restore.go rewritten: always restores config+DB, adds user data if hasHDD. UI shows restore type banner (full / config+DB / config only) with color-coded styling. Snapshot API clarified for non-HDD apps. Fix 4: "Docker kötetek" → "Konfiguráció" — named volumes are not in the restic backup paths; compose files + app.yaml are what's backed up. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 KiB
Changelog
What was just completed (2026-02-18 session 43)
-
v0.12.7 — Backup Architecture Overhaul (mandatory HDD backup, pre-dump, restore for all apps):
Fix 1: HDD data backup now mandatory (
backup.go,appdata.go,settings.go)resolveAppBackupPaths()rewrote to iterate ALL deployed stacks viaListDeployedStacks()— no longer readsGetAppBackupMap()or checksEnabledflagDiscoverAppData()signature simplified: droppedbackupPrefs map[string]boolparameter;BackupEnabledis now derived fromHasHDDData(if app has HDD data, it's always backed up)RefreshCache()updated to call newDiscoverAppData(m.stackProvider, status.DiscoveredDBs)signature- 5 dead settings methods deleted:
IsAppBackupEnabled,SetAppBackup,GetAppBackupMap,SetAppBackupBulk,GetAppBackupPrefs—AppBackupPrefs.Enabledfield kept in struct for backward-compat JSON loading
Fix 2: Cross-drive backup triggers fresh DB dump first (
crossdrive.go,backup.go,main.go)- New
DBDumperinterface withDumpStackDB(ctx, stackName)incrossdrive.go CrossDriveRunnergetsdbDumperfield +SetDBDumper(d DBDumper)setterManager.DumpStackDB()discovers containers for that stack viaDiscoverDatabases(), runsDumpAll(), persists validation cache — same logic as nightly dump but scoped to one stackRunAppBackup()callsDumpStackDB()beforeValidateDestination()— non-fatal on failure (logs warn, proceeds with user data)main.gowirescrossDriveRunner.SetDBDumper(backupMgr)after both are initialized
Fix 3: Restore dropdown shows ALL deployed apps (
backups.html,restore.go,router.go)restore.gorewritten: noIsAppBackupEnabled()check; resolvesGetStackComposePath+DBDumpDir+ HDD mounts; always restores config+DB, adds user data ifhasHDD; logs restore type (config+DBvsfull (config+DB+userdata))- Restore dropdown template: removed
{{if and .HasHDDData .BackupEnabled}}filter; every app gets an<option>withdata-has-hddanddata-has-dbattributes - New
#restore-type-infodiv added between snapshot selector and warnings onRestoreAppChange()JS updated: readsdata-has-hdd/data-has-dbfrom selected option, shows Hungarian restore type banner (full / config+DB / config only) with color-coded stylingrouter.gobackupSnapshots: added clarifying comment for non-HDD apps (no filter = all snapshots returned)
Fix 4: Honest UI label (
backups.html)- "Docker kötetek" renamed to "Konfiguráció" — Docker named volumes at
/var/lib/docker/volumes/are NOT in the restic backup paths; what's actually backed up is compose files + app.yaml + .felhom.yml
CSS:
.restore-infoand.restore-info-partialclasses added tostyle.cssFiles modified (9):
internal/backup/backup.go,internal/backup/appdata.go,internal/settings/settings.go,internal/backup/crossdrive.go,internal/backup/restore.go,cmd/controller/main.go,internal/web/templates/backups.html,internal/web/templates/style.css,internal/api/router.go
What was just completed (2026-02-18 session 42)
-
v0.12.6 — Cross-Drive Backup Rsync Fixes:
Context: After fixing mount-point validation and system-drive thresholds (v0.12.5), testing revealed two more rsync issues for Immich.
Fix 3: Simplified rsync destination path structure (
internal/backup/crossdrive.gorunRsyncBackup)- Old logic stripped only the first 2 path segments and kept the rest as a subpath, producing redundant nesting:
backups/rsync/immich/storage/immich/<data>instead ofbackups/rsync/immich/<data> - New logic: if app has a single mount, rsync directly into the stack folder (
backups/rsync/immich/); if multiple mounts, use each mount's leaf directory name as subfolder - Duplicate leaf names disambiguated by appending
_Nindex suffix - Loop variable changed from
_, srcMounttoi, srcMountto support the index-based disambiguation - Old nested
storage/immich/folder will remain orphaned after first run (no data loss;--deleteonly affects the target subtree)
Fix 4: Exclude app-internal DB dump files from rsync (
internal/backup/crossdrive.gorunRsyncBackup)- Apps like Immich store their own periodic DB dumps in
<data>/backups/*.sql.gz(~16 MB/day) - The controller already handles DB backups via
pg_dumpseparately — copying these again via rsync is redundant and wastes space - Added
--exclude backups/*.sql.gz,--exclude backups/*.sql,--exclude backups/*.dumpto rsync command - The
backups/directory itself and non-dump files within it are preserved
Files modified (1):
internal/backup/crossdrive.go - Old logic stripped only the first 2 path segments and kept the rest as a subpath, producing redundant nesting:
What was just completed (2026-02-18 session 41)
-
v0.12.5 — Cross-Drive Backup Validation Fix:
Root cause: Immich cross-drive backup failed with
destination /mnt/hdd_placeholder is not a mount pointbecauseValidateDestination()hard-blocked non-mount-point destinations. The/mnt/hdd_placeholderfolder is on the internal SSD (not a separate mount), so the device-ID check returned false.Fix 1: Drive-type-aware space checks in
ValidateDestination(internal/backup/crossdrive.go)onSystemDriveflag replaces the previous boolean-only mount-point check- System-drive destinations: require ≥10 GB free and <90% usage to protect OS stability
- External-drive destinations: require ≥100 MB free (original threshold)
- Updated function comment to reflect the new tiered logic
Fix 2: Aligned
CheckBackupDestinationUI thresholds for system drives (internal/system/mounts_linux.go)- Tier 4 disk checks now branch on
h.SystemDriveflag (set in Tier 3) - System drive: block at <10 GB free OR ≥90% used (matches runner enforcement); Hungarian warning messages
- External drive: warn at ≥90% used, block at ≥95% used (unchanged)
- Removed the
&& h.Severity == "ok"guard that prevented system-drive warnings from being overridden properly
Files modified (2):
internal/backup/crossdrive.go,internal/system/mounts_linux.go
What was just completed (2026-02-18 session 40)
-
v0.12.4 — Correctness & Robustness Bug Fixes (TASK.md — 15 bugs fixed):
CRITICAL fixes (data loss, panics):
- C1:
SetAppBackupBulkdata loss + nil map panic — Fixed: now updates map IN PLACE instead of replacing it, so stacks absent from the input are preserved. Added nil guard fors.AppBackup. (internal/settings/settings.go) - C2:
UpdateStackConfignil Env map panic — Added nil checkif appCfg.Env == nil { appCfg.Env = make(...) }before the field assignment loop. (internal/stacks/deploy.go) - C3:
ValidateDumpmissing scanner.Err() check — Addedif err := scanner.Err()check after the scan loop so I/O errors don't silently mark a partial dump as valid. (internal/backup/dbdump.go)
HIGH fixes (logic errors, resource leaks):
- H1:
nextDailyRunDST bug — Replacednext.Add(24 * time.Hour)withtime.Date(day+1, ...)for correct scheduling across Europe/Budapest DST transitions. (internal/scheduler/scheduler.go) - H2:
nextDailyRunrepeatedLoadLocation— Cached timezone in package-levelsync.Oncevariable;getBudapestLocation()now loaded only once. (internal/scheduler/scheduler.go) - H3:
settings.save().tmp file leak — Addedos.Remove(tmpPath)cleanup onWriteFilefailure path. (internal/settings/settings.go) - H4:
SetNotificationPrefsnil pointer panic — Added nil guard at start of function, returns error instead of panicking. (internal/settings/settings.go) - H5:
appDirSizeignoresSscanfreturn value — Now checksn != 1and returns(0, "?")on parse failure. Same fix applied togetDirSizeBytesinstacks/delete.go. (internal/backup/appdata.go,internal/stacks/delete.go) - H6:
getDirSizeBytesno timeout — Addedexec.CommandContextwith 30s timeout. Added"context"import. (internal/stacks/delete.go) - H7:
dbdump.gotmpFile not usingdefer Close— Replaced explicittmpFile.Close()call withdefer tmpFile.Close()so the file handle is released even on panic. (internal/backup/dbdump.go) - H8:
UpdateCrossDriveStatusmisleading comment — Updated comment to accurately describe the "does nothing if nil" behavior instead of claiming it "creates one if nil". (internal/settings/settings.go)
MEDIUM fixes (code quality, edge cases):
- M1: Custom
contains/containsBytesreplaced — Removed bespokecontainsBytesand simplifiedcontainsto delegate tostrings.Contains. Added"strings"import. (internal/notify/notifier.go) - M2:
scheduler.Every()doesn't validate interval — Added early return with error log ifinterval <= 0to prevent panic intime.NewTicker. (internal/scheduler/scheduler.go) - M3:
executeJobpanic recovery missingLastRun— Panic recovery defer now also setsjob.LastRun = time.Now()so the job status shows a timestamp after a panic. (internal/scheduler/scheduler.go) - M4:
logPostStartStatusgoroutine captures env by reference — Copies the env slice before launching the goroutine (envCopy). (internal/stacks/manager.go) - M5: Multiple
time.LoadLocationcalls in web package — Added package-levelgetTimezone()withsync.Onceinfuncmap.go. Replaced alltime.LoadLocation("Europe/Budapest")calls in the web package withgetTimezone(). (internal/web/funcmap.go,internal/web/handlers.go)
Files modified (8):
internal/settings/settings.go,internal/stacks/deploy.go,internal/backup/dbdump.go,internal/scheduler/scheduler.go,internal/backup/appdata.go,internal/stacks/delete.go,internal/stacks/manager.go,internal/notify/notifier.go,internal/web/funcmap.go,internal/web/handlers.go - C1:
What was just completed (2026-02-17 session 39)
-
v0.12.3 — Security & Correctness Bug Fixes (TASK.md — 33 bugs fixed):
CRITICAL fixes (data races, security vulnerabilities):
- C1: Data race in RefreshCache — Moved
m.lastDBDump.Resultsmutation insidem.mu.Lock(). Was previously mutating shared state without the lock, causing potential torn writes visible toGetFullStatus()goroutines. (internal/backup/backup.go) - C2: SnapshotHistory reversed after unlock — Moved snapshot reversal loop before
m.cachedStatus = status(inside the lock). Previously reversed afterUnlock(), som.cachedStatus.SnapshotHistorywas reversed without protection. (internal/backup/backup.go) - C3: SetStackProvider write without lock —
m.stackProvider = providernow wrapped inm.mu.Lock(). Read byresolveAppBackupPaths()concurrently. (internal/backup/backup.go) - C4: GetFullStatus shallow-copies mutable pointers —
LastDBDumpandLastBackupare now deep-copied (struct + Results slice) so callers cannot mutate shared manager state. (internal/backup/backup.go) - C5: IsSystemDisk 8-bit major mask — Replaced
>> 8 & 0xffwithunix.Major()/unix.Minor()(12-bit extraction). Also compares disk-portion of minor (groups of 16) to correctly distinguish physical disks of the same type. Addsgolang.org/x/sys/uniximport. (internal/storage/safety_linux.go) - C6: No /dev/ prefix validation on DevicePath —
FormatAndMountnow validatesDevicePathstarts with/dev/and does not contain..before any disk operations. (internal/storage/format_linux.go) - C7: Path traversal in extractName —
extractName()now rejects empty string,.,.., and names containing/or\. (internal/api/router.go) - C8: Path traversal in TargetPath — Migration API validates
TargetPathagainst registered storage paths from settings before starting migration job. (internal/web/storage_handlers.go) - C9: Path traversal in DestinationPath — Cross-drive backup config API validates
DestinationPathagainst registered storage paths whenenabled=true. (internal/api/router.go) - C10: Path traversal in ParseComposeHDDMounts —
filepath.Clean()applied before prefix check; uses separator-aware checkcleanHDD + string(filepath.Separator)to prevent${HDD_PATH}/../../etc/passwdescaping. (internal/stacks/delete.go)
HIGH fixes (logic errors, resource leaks):
- H1: ValidateDump reads entire file into memory — Replaced
os.ReadFilewithbufio.Scannerreading line-by-line. 256KB per-line buffer prevents OOM on large (500MB+) SQL dumps during 5-min cache refresh. (internal/backup/dbdump.go) - H2/H3: Double du invocation per mount + no timeout — Replaced
appDirSizeHuman()+appDirSizeBytes()with singleappDirSize()function usingexec.CommandContextwith 30s timeout. Halves subprocess calls per mount point. (internal/backup/appdata.go) - H4: Snapshot validation only checks first 100 — Replaced
ListSnapshots(100)existence check with regex validation (^[0-9a-f]{8,64}$). Allows restoring any snapshot;restic restorereturns a clear error for non-existent IDs. (internal/backup/restore.go) - H5: No pruning for cross-drive restic repos — Added
pruneResticRepo()called after each successful cross-drive restic backup (forget --keep-daily 7 --keep-weekly 4 --prune). Non-fatal — logs warning on failure. (internal/backup/crossdrive.go) - H6: Temp password file management — Reorganized temp file lifecycle: close before deferred remove, remove-on-write-error cleanup. (
internal/backup/crossdrive.go) - H7: dirSizeBytes swallows walk errors —
filepath.Walkcallback now returns errors instead ofnil, propagating permission/IO issues. (internal/backup/crossdrive.go) - H8: Non-atomic fstab write —
AppendFstabEntrynow reads existing fstab, writes to.tmp, then atomically renames. Crash-safe. (internal/storage/safety_linux.go) - H9: IsDeviceMounted naive prefix matching — After prefix check, next character must be digit (
0-9) orp(partition marker). Prevents/dev/sdbmatching/dev/sdba. (internal/storage/safety_linux.go) - H10: eMMC device mapping bug —
partitionToParentDisknow handlesmmcblk0p1 → mmcblk0andnvme0n1p1 → nvme0n1patterns. UsesLastIndex("p")with digit-suffix check before falling back toTrimRight("0-9"). (internal/storage/scan_linux.go) - H11: Data race on bytesCopied in rsync error path — Error return path in
runRsyncnow readsbytesCopiedunder mutex lock. (internal/storage/migrate.go) - H13: Path prefix match without separator — Migration source path check now uses
srcPath == req.CurrentHDDPath || strings.HasPrefix(srcPath, req.CurrentHDDPath+"/"). Prevents/mnt/hddmatching/mnt/hdd_backup/data. (internal/storage/migrate.go) - H14: DeleteStack continues after failed compose down —
docker compose downfailure now returns an error immediately, preventing deletion of files while containers are still running. (internal/stacks/delete.go) - H16: exec.Command("docker") without timeout —
syncFileBrowserMounts()now usesexec.CommandContextwith 60s timeout. (internal/web/handlers.go) - H17: SetNotificationPrefs stores caller's pointer — Deep-copies
NotificationPrefsstruct andEnabledEventsslice before storing. (internal/settings/settings.go) - H18: wipefs error silently discarded — wipefs failure logged as warning via progress channel; continues (wipefs may not be installed). (
internal/storage/format_linux.go) - H19: Orphaned fstab entry on mount failure — New
RemoveFstabEntry()function atomically removes UUID entry. Called as rollback onmountfailure andfindmntverify failure. (internal/storage/safety_linux.go,format_linux.go)
MEDIUM fixes (edge cases, code quality):
- M1: formatBytes duplicate in dbdump.go — Removed
formatBytes()fromdbdump.go; all callers (backup.go, restic.go, dbdump.go) now usehumanizeBytes()from appdata.go. (internal/backup/dbdump.go,backup.go,restic.go) - M2: Dead code .tmp suffix check — Reordered filter in
ListDumpFiles:.tmpcheck now comes before.sqlcheck to correctly skip.sql.tmptemp files (was unreachable before). (internal/backup/dbdump.go) - M3: sizeBytes() returns 0 for string types — Added
case string:tosizeBytes()usingstrconv.ParseUint. (internal/storage/scan_linux.go) - M6: Dead elapsed variable — Removed
_ = elapsed; elapsed time now shown inline in the "done" progress message. (internal/storage/migrate.go) - M7: time.LoadLocation error silently discarded — Two locations in handlers.go now handle
LoadLocationerror, falling back totime.UTC. (internal/web/handlers.go) - M10: filterSnapshotsByPaths imprecise prefix — Added
pathCovers()helper using separator-aware prefix check. Prevents/mnt/hdd_1matching/mnt/hdd_10/data. (internal/api/router.go) - M11: XSS in editStorageLabel innerHTML —
cancelEditLabel()in settings.html now uses DOM manipulation (document.createElement,.textContent) instead ofinnerHTMLfor the label text. (internal/web/templates/settings.html)
Files modified (15):
internal/backup/backup.go,internal/backup/appdata.go,internal/backup/dbdump.go,internal/backup/restore.go,internal/backup/crossdrive.go,internal/backup/restic.go,internal/storage/safety_linux.go,internal/storage/format_linux.go,internal/storage/scan_linux.go,internal/storage/migrate.go,internal/stacks/delete.go,internal/api/router.go,internal/web/handlers.go,internal/web/storage_handlers.go,internal/settings/settings.go,internal/web/templates/settings.html - C1: Data race in RefreshCache — Moved
What was just completed (2026-02-17 session 38)
- v0.12.2 — Restore Section Simplification (Bug 4 from v0.12.1 TASK.md):
- Feature: Snapshot filtering by app —
GET /api/backup/snapshots?stack={name}now filters snapshots to those whosePathsoverlap with the app's HDD mount paths. Uses prefix matching (snapshot path is prefix of required, or vice versa). NewfilterSnapshotsByPaths()helper ininternal/api/router.go. Manager gainsGetStackHDDMounts()method to expose stackProvider's mount resolution. - Feature: Auto-stop/restart on restore —
RestoreApp()now stops the app's containers before runningrestic restoreand restarts them after (even on failure). Avoids data corruption from live writes during restore. Eliminates the "Javasoljuk az alkalmazás leállítását" advisory from the UI. - Interface extension: StackDataProvider — Added
StopStack(name string) errorandStartStack(name string) errorto thebackup.StackDataProviderinterface ininternal/backup/appdata.go.stackAdapterincmd/controller/main.gowires these through tostacks.Manager. - UI simplification: Restore section — Removed confusing "Visszaállítandó útvonalak" path list (technical detail not needed by customer). Snapshot dropdown now populated per-app (filtered) with human-friendly format:
2026-02-17 hétfő 03:00 (a3f2b1). Single calm warning replacing the triple-exclamation block. Empty filtered result shows inline message instead of empty dropdown.data-pathsattribute removed from app dropdown options. - Files modified (6):
internal/backup/appdata.go,internal/backup/backup.go,internal/backup/restore.go,internal/api/router.go,internal/web/templates/backups.html,cmd/controller/main.go
- Feature: Snapshot filtering by app —
What was just completed (2026-02-17 session 37)
- v0.12.0 — Backup Page Overhaul — Unified App Backup Status & Bug Fixes:
- Bug Fix 1: Duplicate unconfigured apps —
GetFullStatus()now returns a deep copy of the cached status.CrossDriveSummary,UnconfiguredApps, andCrossDriveWarningsslices are always nil in the returned copy so the handler builds them fresh on every page load. Previously the handler appended to the cached slices, causing 3× duplication on 3 page loads. - Bug Fix 2: Misleading "drive disconnected" error — Replaced the binary
IsMountPoint || !IsWritablecheck with tieredCheckBackupDestination()validation (new ininternal/system/mounts_linux.goand stub inmounts_other.go). Tiers: path doesn't exist (critical/blocked), not writable (critical/blocked), same block device as/(warning/allowed with note about system drive), disk >95% full (critical/blocked), disk >90% (warning/allowed).isSameBlockDevice()replacesIsMountPoint()for source/dest same-device detection. Used in bothdeployHandler()andbackupsHandler()for display, and incrossdrive.gologic viaCheckBackupDestination(). - Bug Fix 3: Dead BackupEnabled toggle — Removed
settingsAppBackupHandler()from handlers.go and itsPOST /settings/app-backuproute from server.go. The toggle wrote to settings.json but nothing read it to skip apps. UI nightly backup section in deploy.html now shows an informational note instead of the toggle. - Architecture: Unified per-app backup rows — New
AppBackupRowstruct andbuildAppBackupRows()in handlers.go. Replaces old "Alkalmazás adatok" + "Másolatok másik meghajtóra" sections with a single expandable row per app showing all 3 backup layers (DB, Docker volumes, user data). Status dot: green=fully covered, yellow=warning (failed run, system drive, disk full), red=HDD data without cross-drive configured, auto=no user data. Expandable JS toggle with ▶/▼ icon. - Architecture: Sequential backup chaining — Removed independent
cross-drive-daily(03:30) andcross-drive-weekly(04:30) scheduler jobs. Cross-drive backups now run immediately after the restic backup completes (daily jobs every night; weekly jobs on Sunday). This ensures DB dump → restic → cross-drive happen in the same window for file/DB consistency on restore. - Architecture: Deploy page schedule dropdown — Removed "Csak kézi indítás" option (schedule="manual"). Two options remain: "Naponta (az éjszakai mentés után)" and "Hetente, vasárnap (az éjszakai mentés után)". Weekly option shows informational note about DB consistency implications. Existing "manual" configs treated as "weekly" in the dropdown.
- CSS added:
.app-backup-row,.app-backup-row-header,.app-backup-row-name,.app-backup-row-meta,.app-backup-row-detail,.status-dot(green/yellow/red/auto),.backup-layers,.backup-layer-row,.layer-label,.layer-badge,.layer-na,.layer-method,.layer-dest,.layer-schedule,.layer-last,.layer-unconfigured,.layer-actions,.layer-warnings,.backup-layer-warning,.btn-xs,.text-ok,.text-error. - Files modified (9):
internal/backup/backup.go,internal/system/mounts_linux.go,internal/system/mounts_other.go,internal/web/handlers.go,internal/web/server.go,internal/web/templates/backups.html,internal/web/templates/deploy.html,internal/web/templates/style.css,cmd/controller/main.go
- Bug Fix 1: Duplicate unconfigured apps —
What was just completed (2026-02-17 session 36)
- v0.11.9 — UI Polish Fixes for deploy/settings backup section:
- Fix 1: Spacing —
.deploy-cross-drivemargin-bottomincreased from1remto1.5remfor consistent spacing before deploy form. - Fix 2: Tooltip on "Módszer" — Renamed "Verziózott mentés (restic)" to "Titkosított mentés (restic)". Added info
(i)tooltip explaining rsync vs restic tradeoffs. - Fix 3: Nightly backup indicator — Replaced disabled checkbox (with confusing pointer cursor) with a non-interactive green/gray dot indicator.
- Fix 4: Progressive disclosure — Dest/method/schedule selects are disabled until "Engedélyezve" is checked. JS
toggleCrossDriveFields()enables/disables them. Backend handler updated to preserve existing config when disabling (disabled fields not submitted). - Fix 5: Emoji cleanup — Removed all emoji from
deploy.htmlbackup section (h4, warning, status, hint, stale data) andbackups.htmlcross-drive summary (status badges, schedule badge, unconfigured warning). JS callbacks also cleaned up. - CSS added:
.info-tooltip,.info-icon,.info-tooltip-text,.cross-drive-nightly-status,.nightly-status-indicator,.nightly-enabled,.nightly-disabled,.meta-badge-fail. - Files modified (4):
web/templates/deploy.html,web/templates/backups.html,web/templates/style.css,web/handlers.go
- Fix 1: Spacing —
What was just completed (2026-02-17 session 35)
- v0.11.8 — Per-App Cross-Drive Backup (3-2-1 rule, second copy on different media):
- Feature: CrossDriveBackup data model —
AppBackupPrefsextended withCrossDrive *CrossDriveBackupfield insettings.go. New methods:GetCrossDriveConfig,SetCrossDriveConfig,UpdateCrossDriveStatus,GetAllCrossDriveConfigs,GetOrCreateCrossDrivePassword. ExistingSetAppBackup/SetAppBackupBulknow preserve cross-drive config. Auto-generated restic password stored insettings.json. - Feature: CrossDriveRunner — New
internal/backup/crossdrive.go. Supports rsync (simple mirror with--delete) and restic (versioned, deduplicated, shared repo). Safety guards: destination ≠ source, mount point check, writable check, per-app concurrency lock.RunAllScheduled(ctx, schedule)iterates all apps matching the given schedule. Status (last_run, last_status, last_error, last_duration, last_size_human) persisted to settings.json after each run. - Feature: Scheduler jobs — Two new daily jobs:
cross-drive-dailyat 03:30 (for apps withschedule: daily),cross-drive-weeklyat 04:30 Sundays only (forschedule: weekly). - Feature: API endpoints — 4 new routes:
POST /api/stacks/{name}/cross-backup,POST /api/stacks/{name}/cross-backup/run,GET /api/stacks/{name}/cross-backup/status,POST /api/backup/cross-drive/run-all. - Feature: Deploy/Settings page UI — New "Biztonsági mentés" card on the deploy page for apps with HDD data. Shows nightly backup toggle (read-only link), cross-drive dropdowns (destination, method, schedule), last run status, manual trigger button. States: no other storage (info message), configured, destination unreachable (warning). Flash messages on save redirect.
- Feature: Backup page summary — New "Másolatok másik meghajtóra" section showing all configured apps with method, destination, last status, size. Warns about unconfigured apps with HDD data. Destination health warnings. "Összes futtatása most" button.
- CSS:
margin-bottom: 1.5remadded to.deploy-stale-data. New styles:.deploy-cross-drive,.cross-drive-list,.cross-drive-item,.cross-drive-header,.cross-drive-meta,.cross-drive-actions. - Files modified (10):
settings/settings.go,backup/crossdrive.go(new),backup/backup.go,api/router.go,web/handlers.go,web/server.go,web/templates/deploy.html,web/templates/backups.html,web/templates/style.css,cmd/controller/main.go
- Feature: CrossDriveBackup data model —
What was just completed (2026-02-17 session 34)
- v0.11.7 — Stale Data Cleanup + FileBrowser Sync + UI Title Fix:
- Feature: Stale data cleanup — After app data migration, the deploy/settings page now shows leftover data on previous storage paths with size info and a delete button. Two-step confirmation required before deletion. Protected paths (storage root, media, Dokumentumok, appdata) cannot be deleted. Also available immediately after migration on the migration-done page.
- Fix: FileBrowser sync after migration —
syncFileBrowserMounts()now called after successful data migration, ensuring FileBrowser mounts reflect the current storage layout. - Fix: Deploy page title — Already-deployed apps now show "Beállítások" (Settings) instead of "Telepítés" (Deploy) in both the browser page title and the
<h2>heading. - Internal: Exported
ProtectedHDDPaths()from stacks package for reuse in web handlers. - Files modified (7):
internal/stacks/delete.go,internal/web/handlers.go,internal/web/storage_handlers.go,internal/web/templates/deploy.html,internal/web/templates/migrate.html,internal/web/templates/style.css
What was just completed (2026-02-17 session 33)
- v0.11.6 — FileBrowser Auto-Mount Sync + UI Polish (3 fixes):
- Feature: FileBrowser auto-mount sync — Added
syncFileBrowserMounts()andgenerateFileBrowserCompose()tohandlers.go. After a storage path is added (via storage init wizard) or removed, the controller regenerates/opt/docker/stacks/filebrowser/docker-compose.ymlwith volume mounts for all registered paths (/mnt/hdd_1:/srv/hdd_1etc.), then recreates the FileBrowser container. Domain is read from FileBrowser's.env. If FileBrowser isn't deployed, the function silently returns. The generated compose is self-contained (no env vars). - UI Fix 1: Badge color fix —
settings.html: changed "Nincs csatolva!" (redstate-red) badge to "Rendszermeghajtón" (yellowbadge-warn). The path is on the system SSD, which isn't an error — just informational. Added.badge-warn { background: rgba(250, 204, 21, 0.15); color: #facc15; }tostyle.css. - UI Fix 2: Progress bar fix —
storage_init.html: replaced the disk-usage gradient progress bar (green→yellow→red zones, alarming at 30%) with a clean single-colorprogress-bar-taskbar. Added.progress-bar-taskand.progress-bar-task .progress-fillCSS classes tostyle.css. - UI Fix 3: Button text fix —
settings.html: "Alapértelmezett" button (reads as status, confusing) → "Legyen alapértelmezett" (clear action verb). - Files modified (5):
web/handlers.go,web/storage_handlers.go,web/templates/settings.html,web/templates/storage_init.html,web/templates/style.css
- Feature: FileBrowser auto-mount sync — Added
What was just completed (2026-02-17 session 32)
- v0.11.4 — Bugfix: Storage Initialization (FormatAndMount) — 3 bugs + 4 safety improvements:
- Bug 1 (sfdisk): Added
wipefs -abefore sfdisk; changed sfdisk input from,,,L(unsupported GPT type shorthand) to,,(default Linux GUID); added--force --wipe alwaysflags. Previous table confusing sfdisk andLtype not accepted for GPT. - Bug 2 (mount): Replaced
mount mountPath(fstab lookup — uses container's /etc/fstab, not host's) with explicitmount -t ext4 -o defaults,noatime /host-dev/sdb1 /mnt/hdd_1. fstab entry still written to/host-fstabfor host reboot persistence. - Bug 3 (mount propagation): Changed
/mntvolume in compose to long-form bind withpropagation: rshared. Also ranmount --bind /mnt /mnt && mount --make-rshared /mnton demo host. ConfirmedPropagation=rsharedindocker inspect. Mounts created inside container now propagate to host. - Safety 1 (post-mount verification): Added
findmntcheck after mount — fails with clear error if mount isn't actually visible. - Safety 2 (ASCII label): Use
req.MountName(always ASCII) for ext4-Llabel (16-byte limit). Display label (req.Label, may contain UTF-8 Hungarian chars) stays only in settings.json. - Safety 3 (smart partition): In
storageInitAPIHandler, if disk has exactly 1 empty partition (no filesystem), skip wipefs+sfdisk entirely and format existing partition directly. Handles demo sdb case (sdb1 exists, no FS). - Safety 4 (progress messages): Updated
send()calls to include command details (device paths, flags) for remote debugging via UI progress panel. - Files modified (3):
storage/format_linux.go,docker-compose.yml,web/storage_handlers.go
- Bug 1 (sfdisk): Added
What was just completed (2026-02-17 session 31)
- v0.11.3 — Bugfix: Missing sfdisk in container (fdisk package):
sfdiskis in thefdiskpackage on Debian bookworm, notutil-linux. Dockerfile hadutil-linuxbut notfdisk, sosfdiskwas missing and partitioning failed.- Added
fdiskto Dockerfile'sapt-get installlist. Updated comment to clarify which package provides what. - Verified: all six disk tools now present in container (
sfdisk,mkfs.ext4,blkid,mount,lsblk,partprobe). - Files modified (1):
Dockerfile
What was just completed (2026-02-17 session 30)
- v0.11.2 — Bugfix: /dev/sdb not accessible inside container:
- Root cause: Docker always creates a fresh tmpfs at
/devinside containers. Even withprivileged: true, the bind mount- /dev:/devis silently dropped. Block device nodes like/dev/sdbdon't exist inside the container. - Fix: Mount host
/devat/host-devinstead. Withprivileged: true, the kernel allows I/O to the device nodes regardless of path inside the container. - docker-compose.yml: Changed
- /dev:/dev→- /dev:/host-dev:rw. Also applied missingprivileged: true,/etc/fstab:/host-fstab, and/run/udev:/run/udev:roto demo node's live compose (never applied after v0.11.0). - safety.go: Added
HostDevPath = "/host-dev"constant andHostDevicePath(devPath) stringhelper (/dev/sdb→/host-dev/sdb). - format_linux.go: All device operations (os.Stat, sfdisk, partprobe, mkfs.ext4, blkid UUID) use
HostDevicePath(). - safety_linux.go:
IsSystemDisk()stats device viaHostDevicePath(). - scan_linux.go:
enrichWithBlkid()probes each partition individually (blkid -o value -s TYPE/UUID/LABEL /host-dev/sdXN) instead of batchblkid -o export(which fails when/devis Docker's minimal tmpfs). - Verified:
/host-dev/sda,/host-dev/sdb, partitions visible;blkid /host-dev/sdb1returns correct UUID/fstype/label. - Files modified (5):
storage/safety.go,storage/safety_linux.go,storage/format_linux.go,storage/scan_linux.go,docker-compose.yml
- Root cause: Docker always creates a fresh tmpfs at
What was just completed (2026-02-17 session 29)
- v0.11.1 — Bugfix: Storage Scan — System Disk Detection & FSType in Container:
- Bug 1 fix: System disk detection — Replaced mount-point string comparison (
== "/","/boot","/boot/efi") with host fstab parsing. Inside the container,lsblkreports container mount points (e.g./opt/docker/felhom-controller/data), not host mount points. NewgetSystemDiskNames()reads/host-fstab(fallback:/etc/fstab), finds system entries (/,/boot,/boot/efi,swap), resolvesUUID=entries to device paths viablkid -U, and marks parent disks as system.partitionToParentDisk()handles both standard (sda2→sda) and NVMe (nvme0n1p2→nvme0n1) naming. - Bug 2 fix: FSType enrichment —
lsblkreturns null fstype in containers (udev/blkid cache incomplete). NewenrichWithBlkid()runsblkid -o exportafter lsblk scan and fills in missingFSType,UUID,Labelper partition from direct device probing. Runs on bothAvailableDisksandSystemDisks. - Result: sda (system SSD) now correctly appears in SystemDisks; sdb (USB HDD) appears in AvailableDisks; partition fstypes (vfat/ext4/swap) correctly shown; sdb1 genuinely shows "(nincs fájlrendszer)".
- Files modified (1):
storage/scan_linux.go
- Bug 1 fix: System disk detection — Replaced mount-point string comparison (
What was just completed (2026-02-17 session 28)
- v0.11.0 — Phase C: Storage Init, Data Migration & Startup Fixes:
- Step 0: Startup ping + hub report — Controller now fires heartbeat ping, system_health ping, and hub report immediately on startup (5s delay) instead of waiting for first scheduler tick (5-15 min).
hubPusherinstance created once and reused for both startup and periodic reports. Prevents Healthchecks showing stale "Last Ping: X ago" after restarts. - Step 1-3: Storage initialization wizard — New
internal/storage/package (scan.go,format.go,safety.go,format_linux.go,safety_linux.go,scan_linux.go+ non-linux stubs).ScanDisks()vialsblk -J.FormatAndMount()with progress channel (partition via sfdisk → mkfs.ext4 → blkid UUID → fstab backup + UUID-based entry → mount → chown + subdirs). Safety guards: system disk detection via major device numbers, mount path conflict, confirmation "FORMÁZÁS" required. New wizard page at/settings/storage/init. JSON API endpoints at/api/storage/scan,/api/storage/init,/api/storage/init/status. Auto-registers storage path in settings.json after success. - Step 4-5: Data migration — New
MigrateAppData()ininternal/storage/migrate.go. Per-app "Mozgatás" button on deploy page (for deployed apps with HDD data) and settings page storage app list. Migration flow: stop app → rsync with--info=progress2progress parsing → updateapp.yamlHDD_PATH → start app. Rollback on failure (revert config + restart with original path). Old data preserved. New migration page at/stacks/{name}/migrate. JSON API at/api/storage/migrate,/api/storage/migrate/status. - Step 6: Per-app storage display — Deploy page (read-only mode) now shows "Adattárolás" section for deployed apps: current path + label, data size, free space. "Mozgatás" link shown when other storage paths exist.
- Step 7: Container setup — Added
privileged: truetodocker-compose.yml. New volume mounts:/dev:/dev,/etc/fstab:/host-fstab,/run/udev:/run/udev:ro. Docker socket changed from:roto writable.Dockerfileadds:util-linux,e2fsprogs,rsync,parted. - Storage API routing — New
/api/storage/prefix registered inmain.gobefore/api/catch-all (longer prefix takes priority in Go ServeMux).ServeStorageAPImethod on web.Server handles all storage JSON endpoints. - CSS additions —
.disk-step,.disk-step-active,.disk-step-done,.disk-progress-steps,.disk-progress-bar-wrap,.deploy-storage-infostyles. - Files created (13):
storage/scan.go,storage/scan_linux.go,storage/scan_other.go,storage/safety.go,storage/safety_linux.go,storage/safety_other.go,storage/format.go,storage/format_linux.go,storage/format_other.go,storage/migrate.go,web/storage_handlers.go,templates/storage_init.html,templates/migrate.html - Files modified (8):
main.go,web/server.go,web/handlers.go,templates/settings.html,templates/deploy.html,templates/style.css,docker-compose.yml,Dockerfile
- Step 0: Startup ping + hub report — Controller now fires heartbeat ping, system_health ping, and hub report immediately on startup (5s delay) instead of waiting for first scheduler tick (5-15 min).
What was just completed (2026-02-17 session 27)
- v0.10.0 — Phase B: Storage Management UI Polish & Health Severity Fix:
- Step 0: Health severity fix —
checkStoragePaths()mount-point check reclassified from issue (FAIL) to warning (WARN). All storage health messages translated to Hungarian. Added.monitoring-banner-warnCSS class for yellow warning banners. Prevents false FAIL status on demo/test environments where storage is intentionally on SSD. - Step 1: Success flash messages — All 4 storage handlers (add/remove/set-default/toggle-schedulable) now redirect with
?storage_msg=success&storage_detail=...query params. Settings page displays green "alert-info" flash on success. Consistent with backup page flash pattern. - Step 2: Edit storage path labels — New
SetStorageLabel()method insettings.go. NewPOST /settings/storage/labelroute + handler. Inline edit UI with ✏️ button, text input, OK/Cancel. Added.btn-ghostCSS class. - Step 3: App details per storage path — Settings page now shows expandable
<details>list per storage path with app names, sizes, and links to deploy page. NewStorageAppDetailstruct +appDetailsForPath()helper. Added CSS for.storage-app-details,.storage-app-list,.storage-app-row. - Step 4: Storage badge on stacks page — Deployed app cards show "💾 Label" badge indicating which registered storage path the app uses.
StorageLabelsmap built from deployed apps' HDD_PATH → registered storage path label lookup. Added.meta-badge-storageCSS. - Step 5: Deploy dropdown enhancements — Storage path dropdown now shows free space ("234 GB szabad").
DeployStoragePathstruct wrapsStoragePathwithFreeHuman/FreePercentfromGetDiskUsage(). JScheckStorageSpace()shows yellow warning when selected storage has <20% free. - Step 6: Filesystem & disk info — New
FSInfostruct +GetFSInfo()inmounts_linux.gousingfindmntcommand +/sys/block/sysfs reads for disk model. Settings page shows "ext4 · /dev/sdb1 · WD Elements" below disk usage bar. Non-Linux stub returns nil. - Step 7: Backup page storage context — Added
StorageLabelfield toAppBackupInfo. Backup page shows storage label badge per app by matching HDD path prefixes against registered storage paths. Uses existing.meta-badge-storageCSS. - Files modified (12):
healthcheck.go,settings.go,mounts_linux.go,mounts_other.go,appdata.go,handlers.go,server.go,settings.html,stacks.html,deploy.html,backups.html,style.css
- Step 0: Health severity fix —
What was previously completed (2026-02-17 session 26)
- v0.9.0 — Phase A: Storage Paths Foundation & Backup Toggle Fix:
- Root cause: Per-app backup toggles (v0.8.0) didn't appear because
controller.yamlhad nopaths.hdd_pathset →ParseComposeHDDMountsreturned nil. Even with global hdd_path, apps with different HDD_PATH values wouldn't match. - Core fix: Per-app HDD_PATH resolution —
stackAdapter.GetStackHDDMounts()now reads each app's ownHDD_PATHfrom itsapp.yamlenv section (Priority 1), falling back to all registered storage paths (Priority 2). Removed dependency on globalcfg.Paths.HDDPath. - Storage paths registry (
settings.json) — newStoragePathstruct with Path, Label, IsDefault, Schedulable, AddedAt. Thread-safe CRUD methods insettings.go(Get/Add/Remove/SetDefault/SetSchedulable). Multiple external storage paths supported. - Auto-discovery — On startup,
discoverHDDPaths()scans deployed apps'app.yamlforHDD_PATHvalues.AutoDiscoverStoragePaths()registers discovered paths with inferred labels. Legacycfg.Paths.HDDPathused as fallback. - Mount-point validation — New
mounts_linux.go(build-tagged):IsMountPoint()viasyscall.Stat_t.Devcomparison,IsWritable(),PathsOverlap(),GetDiskUsage()viasyscall.Statfs. Non-Linux stubs inmounts_other.go. - Settings page "Adattárolók" section — Lists registered paths with label, path, disk usage bar, app count, badges (default/active/unmounted). Actions: set default, toggle schedulable, remove (with guards). Expandable "Új adattároló hozzáadása" form with 5-step validation (exists, mount point, writable, no overlap, no duplicate).
- Deploy page storage dropdown —
pathfield type renders as<select>dropdown of schedulable storage paths. Falls back to text input with warning if no paths registered. - Health check storage monitoring —
RunHealthCheck()now acceptsstoragePathsparameter. Checks: path accessible (warning), not a mount point (issue — data writes to SSD!), disk usage ≥95% (issue) / ≥90% (warning). - Controller docker-compose.yml — Changed HDD mount from
${HDD_PATH:-/mnt/hdd_placeholder}:...:roto/mnt:/mnt:rwfor multi-storage support + restore capability. - Removed unused
hddPathparam fromDiscoverAppData()signature in backup/appdata.go. - Files created (2):
system/mounts_linux.go,system/mounts_other.go - Files modified (11):
settings.go,main.go,appdata.go,backup.go,handlers.go,server.go,settings.html,deploy.html,style.css,healthcheck.go,docker-compose.yml,report/builder.go
- Root cause: Per-app backup toggles (v0.8.0) didn't appear because
What was previously completed (2026-02-16 session 25)
- v0.8.0 — Phase 7: Storage Overview, Per-App Backup Toggles & Limited Restore:
- Storage overview on backup page — new "Tárhely áttekintés" section as first section on backup page showing SSD/HDD progress bars + backup repo stats (repo size, dump file count, snapshot count). Reuses existing
system.GetInfo()andRepoStats. - Restic password visibility — new "Titkosítási kulcs" section inside the repository card. Masked password field with show/copy buttons (JS toggle). Password synced to hub via periodic report for disaster recovery (
ResticPasswordfield added toBackupReport). - App data discovery — new
internal/backup/appdata.go:StackDataProviderinterface to avoid circular imports between backup and stacks packagesAppBackupInfo,AppDataPath,AppDockerVolumestructsDiscoverAppData()iterates deployed stacks, discovers HDD bind mounts (via adapter callingParseComposeHDDMounts), Docker named volumes (viaparseComposeNamedVolumesusing YAML parser), and DB dump status- Stack adapter in
main.goimplementsStackDataProviderusingstacks.Manager
- Per-app backup toggles — new "Alkalmazás adatok" section on backup page:
- Toggle checkbox per app (only for apps with HDD data)
- Shows HDD paths with sizes, Docker volume info, DB dump notes
POST /settings/app-backuphandler saves preferences tosettings.jsonAppBackupPrefsstruct + bulk getter/setter insettings.goRefreshCache()populatesAppDataInfoviaDiscoverAppData()
- Dynamic backup paths —
RunBackup()now includes enabled app HDD data paths:resolveAppBackupPaths()reads enabled apps from settings, resolves HDD paths via provider- Paths logged at INFO level, included in restic snapshot
BackupPathsdisplay on backup page includes app data paths
- Limited app restore — new restore section on backup page:
RestoreApp()inrestore.go: validates enabled, resolves HDD paths, validates snapshot exists, uses running mutexRestoreAppData()onResticManager: runsrestic restorewith--includeflags for specific pathsPOST /backup/restoreweb handler with confirmation flowGET /api/backup/snapshotsJSON endpoint for restore dropdown- UI: app/snapshot dropdowns, warning box, confirmation checkbox, JS-driven form submission
- Exported
ParseComposeHDDMountsfrom stacks package (was unexportedparseComposeHDDMounts) - Flash messages on backup page via query params (success/error redirects from handlers)
- CSS: New styles for storage overview grid, app backup toggles, encryption key field, restore section, flash messages
- Files created:
appdata.go,restore.go - Files modified:
backup.go,restic.go,handlers.go,server.go,backups.html,style.css,settings.go,delete.go,router.go,types.go,builder.go,main.go
- Storage overview on backup page — new "Tárhely áttekintés" section as first section on backup page showing SSD/HDD progress bars + backup repo stats (repo size, dump file count, snapshot count). Reuses existing
What was previously completed (2026-02-16 session 24)
- v0.7.2 — Fix Notification Preferences Sync (Controller → Hub):
- Two repos changed (deploy-felhom-compose + felhom.eu):
- Hub:
POST /api/v1/preferencesendpoint (hub/internal/api/handler.go):- New route in API handler: same Bearer token auth as /report and /notify
- Accepts JSON payload:
{customer_id, email, enabled_events} - Calls existing
store.SaveNotificationPrefs()— no store changes needed - Logs preference updates at INFO level
- Hub: Notification section on customer detail page (
hub/internal/web/,hub/internal/store/store.go):- New
GetRecentNotifications()store method returns last N notification_log entries handleCustomerDetail()loads NotifPrefs + RecentNotificationsjoinStringstemplate function added for event list displaycustomer.htmltemplate: new "Notifications" section showing email, events, and last 10 notification log entries (time, event, status, message)
- New
- Controller:
SyncPreferencesmethod (internal/notify/notifier.go):- New
preferencesRequeststruct for JSON payload SyncPreferences(email, enabledEvents)— synchronous POST to hub/api/v1/preferencesIsEnabled()getter for checking hub connectivity- Hungarian error messages for user-facing feedback
- New
- Controller: Sync on settings save (
internal/web/handlers.go):settingsNotificationsHandlernow callsSyncPreferencesafter saving tosettings.json- Three flash message variants: success (synced), warning (local save OK, sync failed), error (save failed)
- Local save always succeeds even if hub sync fails
- Controller: Sync on startup (
cmd/controller/main.go):- Non-blocking goroutine syncs preferences to hub when controller starts
- Only runs if hub is enabled and email is configured
- Handles hub DB rebuild recovery (re-populates preferences after hub redeployment)
- Files changed: hub (3 files: handler.go, store.go, server.go, customer.html), controller (3 files: notifier.go, handlers.go, main.go)
- Documentation: README.md updated (version, notify module, phase checklist), CONTEXT.md updated
What was previously completed (2026-02-16 session 23)
- v0.7.1 — Phase 2: Monitoring Warnings, Dashboard Alerts & Notification System:
- Three workstreams across two repos (deploy-felhom-compose + felhom.eu):
- Monitoring page "Távoli monitoring" section (
monitoring.html,handlers.go):- New section between System Overview and System Metrics showing healthcheck ping UUID status
- 5 rows: Heartbeat, System Health, DB Dump, Backup, Backup Integrity — each shows ✅ configured or ⚠️ missing
- Banner: green (all configured), yellow (some missing), red (monitoring disabled)
isPingConfigured()helper checks non-empty AND not "CHANGEME" prefix
- Dashboard alert banners (new
alerts.go,layout.html):AlertManagerstruct withRefresh()+GetAlerts()— generates alerts from health report, missing pings, backup disabled- Alert types:
Alert{ID, Level, Message, Link, LinkText}— levels: error/warning/info - Renders colored banners (red/yellow/blue) after
<main class="content">on all pages - Caps at 5 alerts with "+N more" overflow; monitoring page excludes "pings-missing" (shown in table instead)
- Refreshed every 5 min via system-health scheduler task + once at startup
- Hub notification relay (felhom.eu repo —
hub/internal/api/handler.go,hub/internal/store/store.go):POST /api/v1/notifyendpoint: Bearer auth, JSON payload (customer_id, event_type, severity, message, details)- New
customer_notificationstable (email, enabled_events JSON) +notification_logaudit table - Resend email integration: direct HTTP POST to
https://api.resend.com/emails - Hungarian email template with event details, timestamp, severity
hub.yaml.exampleupdated with notifications config section
- Controller-side notifier (new
internal/notify/notifier.go):Notifierstruct: fires HTTP POST to hub/api/v1/notify, non-blocking (goroutine)- Cooldown tracking per event type (default 6h, configurable via UI)
- Checks notification preferences (email configured + event enabled) before sending
NotifyHealthChange(): only notifies on status degradation (ok→warn, ok→fail, warn→fail)NotifyBackupFailed/NotifyDBDumpFailed/NotifyIntegrityFailedconvenience methodsSendTest()for test email flow- Wired into scheduler: system-health task calls
NotifyHealthChange(), backup tasks call failure notifiers
- Notification preferences UI (
settings.html,handlers.go):- New "Értesítések" Section C on Settings page (only shown when hub enabled)
- Email input, 4 event checkboxes (disk_warning, backup_failed, update_available, security_update)
- Cooldown hours input (default 6)
- "Mentés" + "Teszt email küldése" buttons
- Saved to
settings.jsonviaNotificationPrefsstruct (Email, EnabledEvents, CooldownHours)
- Settings persistence expanded (
settings.go):NotificationPrefsstruct with Email, EnabledEvents, CooldownHoursDefaultEnabledEvents: disk_warning, backup_failed, update_availableGetNotificationPrefs()returns defaults if nil,SetNotificationPrefs()saves atomically
- Files changed: 3 new (alerts.go, notifier.go, notify package), ~12 modified across both repos
- Deployed: Controller v0.7.1 to demo-felhom.eu, verified healthy (0 alerts on clean system)
What was previously completed (2026-02-16 session 22)
- v0.7.0 — Phase 1: Authentication, Persistence & Settings Page:
- New
internal/settings/settings.go: Shared persistence layer viasettings.jsonin the data directory. Atomic writes (tmp + rename), thread-safe withsync.RWMutex. Stores password hash overrides and DB validation cache. Graceful handling if file doesn't exist. - Auth improvements:
- Password resolution priority:
settings.json→controller.yaml→ none (open dashboard) - Startup logs which source is active:
Auth: using password from settings.json/controller.yaml/no password configured - Session duration extended to 7 days (was 24h)
?next=redirect after session expiry — returns user to the page they were on- Flash messages on login page (green info box, used after password change)
- Conditional logout link — hidden when auth is disabled (no password configured)
invalidateAllSessions()method for password change flow
- Password resolution priority:
- New Settings page (
/settings):- "Rendszer konfiguráció" section: read-only display of controller.yaml values (customer ID/name/domain, git repo/sync interval, backup enabled/schedule, monitoring, healthchecks URL, hub status, controller version)
- "Jelszó módosítás" section: form with current password, new password, confirm — validates min 8 chars, match check, bcrypt comparison
- Password saved to
settings.json, all sessions invalidated, redirect to login with flash message - Only shown if auth is enabled; otherwise shows info message to contact operator
- Sidebar update:
- "Beállítások" menu item with ⚙ icon pinned to bottom (above version/logout)
- Version and logout link separated from nav links
- Logout link conditionally shown only when auth is enabled
- DB validation persistence:
- After each successful dump, validation results saved to
settings.json(db_validationsmap keyed by filename) - Cached data survives container restarts
DBValidationCachestruct withvalidated_at,table_count,has_header,error
- After each successful dump, validation results saved to
- 10 files changed (3 new: settings.go, settings.html; 7 modified: main.go, backup.go, auth.go, handlers.go, server.go, layout.html, login.html, style.css)
- Deployed: Controller v0.7.0 to demo-felhom.eu, verified healthy
- New
What was previously completed (2026-02-16 session 21)
- v0.6.3 — Bug fixes from v0.6.2 code scan (4 minor fixes):
- Bug 1:
--hdd-pathindocker-setup.shnow usesrequire_argvalidation like all other flags. Previously,--hdd-pathas the last argument without a value would crash with a cryptic bash error underset -uinstead of a friendly message. - Bug 2:
stackAction()inlayout.htmlnow receiveseventas an explicit parameter instead of relying on the deprecated implicitwindow.event. All 10 onclick call sites indashboard.htmlandstacks.htmlupdated to passeventas first argument. - Bug 3: Page
<title>now has an em dash separator:"Vezérlőpult — Felhom.eu"instead of"VezérlőpultFelhom.eu". - Bug 4:
nextPruneLabel()infuncmap.gonow returns"ma"(Hungarian for "today") on Sunday before 4am, consistent with thenextRunLabelfunction. Previously returned the date in"2006-01-02"format. - Deployed: Controller v0.6.3 to demo-felhom.eu, verified healthy
- Bug 1:
What was previously completed (2026-02-16 session 20)
- Hub Dashboard Bugs + Backup Validation Fix (3 bugs):
- Bug 1&2 (Hub repo, felhom-hub v0.1.2): Hub timestamp parsing failure —
time.Parsewith single hardcoded format silently failed for formats returned bymodernc.org/sqlite. AddedparseSQLiteTime()that tries 6 common formats. Fixed: hub main page showing DOWN despite OK status, and report history timestamps showing 00:00:00. - Bug 3 (Controller repo, v0.6.2): Backup page showing "Hiba" for all DB validations — zero-value
DumpValidation{}(never assigned) hit the{{else}}branch in template. Three fixes:- Template: 4-branch guard (Valid → OK / Error → Hiba / zero-value → "–" with tooltip)
- Debug logging: Added
[DEBUG]and[WARN]log lines to allValidateDump()code paths - Re-validation:
RefreshCache()now cross-checkslastDBDumpresults against freshListDumpFiles()validation, healing stale in-memory state
- Deployed: Hub v0.1.2 to k3s, Controller v0.6.2 to demo-felhom
- Verified: Controller logs show
ValidateDump OKfor all 3 databases (immich: 60 tables, paperless: 67 tables, romm: 14 tables)
- Bug 1&2 (Hub repo, felhom-hub v0.1.2): Hub timestamp parsing failure —
What was previously completed (2026-02-16 session 19)
- v0.6.1 — Code Review Bugfixes (7 fixes):
- Fix 1:
http.NotFound(w, nil)→ pass actual*http.RequestindeployHandlerandappDetailHandler - Fix 2: Dashboard running/stopped counts now computed from the filtered
deployedStacksset (was counting ALL stacks including non-deployed) - Fix 3: Session cookie
Secureflag now dynamic based onr.TLS != nil || X-Forwarded-Proto == "https".SameSitechanged fromStricttoLax(Strict breaks Cloudflare Tunnel redirects) - Fix 4: Removed misleading
subtle.ConstantTimeComparefromisValidSession()(map lookup already leaks timing; comparing token to itself is meaningless). Removed unusedtokenfield fromsessionstruct. Removedcrypto/subtleimport. - Fix 5: Replaced
time.Tick()(goroutine leak) with propertime.NewTicker+donechannel incleanupSessions(). AddedClose()method to Server. Addeddone chan struct{}to Server struct. - Fix 6: Added
http.MaxBytesReader(w, req.Body, 1<<20)(1MB limit) todeployStack,updateOptionalConfig,deleteStackAPI handlers vialimitBody()helper. - Fix 7: Cached
time.LoadLocation("Europe/Budapest")once at top oftemplateFuncMap(), removed 5 per-functionLoadLocationcalls (timeAgo, fmtTime, fmtTimeShort, nextRunLabel, nextPruneLabel). - Post-fix verification: All 4 grep checks pass (0 results for NotFound(w,nil), ConstantTimeCompare, time.Tick(, Secure:.*true).
go vet ./...clean. - Controller version: v0.6.1 — deployed and verified on demo-felhom.eu
- Fix 1:
What was previously completed (2026-02-16 session 18)
- v0.6.0 — Healthcheck Implementation + Central Push + Hub Dashboard:
- Part 1 — Healthcheck enhancements (controller-side):
- Added
heartbeatping — lightweight "I'm alive" signal every 5 min (no logic, just ping) - Added
backup_integrityping — weeklyrestic checkon Sunday 04:00, pings healthchecks with result - Added
HeartbeatandBackupIntegrityfields toPingUUIDsConfig - Added
RunIntegrityCheck()to backup Manager (calls restic Check(), updates lastCheckTime/lastCheckOK, pings) - Updated
controller.yaml.examplewith new monitoring ping_uuids - Created
monitoring/DEPRECATED.mdfor legacy bash monitoring scripts
- Added
- Part 2 — Central hub reporting (controller-side):
- New
internal/report/package: types.go (Report struct), builder.go (BuildReport), pusher.go (HTTP push) - Report builder gathers data from all subsystems: system info (via metrics.GetStaticInfo + system.GetInfo), container stats (via metricsStore.QueryContainerSummary), backup status (via backupMgr.GetFullStatus), health (via monitor.RunHealthCheck), stacks (via stackMgr.GetStacks)
- Report pusher: POST JSON to hub with Bearer token auth, 3 retries with 5s backoff, never fails caller
- Added
HubConfigto config.go (enabled, url, api_key, push_interval) - Wired hub reporting into scheduler (configurable interval, default 15m)
- Hub reporting disabled by default (hub.enabled: false)
- New
- Part 3 — Hub service (felhom.eu repo, new
hub/subfolder):- Full Go service:
cmd/hub/main.go,internal/api/handler.go,internal/store/store.go,internal/web/server.go - SQLite store with WAL mode, auto-migration, denormalized fields for fast queries
- REST API: POST /api/v1/report (Bearer token auth), GET /api/v1/customers, GET /api/v1/customers/{id}, GET /api/v1/customers/{id}/history
- Dark theme dashboard (English): multi-customer overview table with status indicators, customer detail page with system/storage/containers/backup/health sections
- Color coding: green (OK, <30min), yellow (warn or 30-60min), red (fail or >60min)
- K8s manifest: Deployment + Service + Ingress for hub.felhom.eu in felhom-system namespace
- Dockerfile, Makefile, hub.yaml.example config
- 90-day report retention with daily auto-prune
- Full Go service:
- Controller version: v0.6.0 — deployed and verified on demo-felhom.eu (9 scheduler jobs, all new jobs registered)
- Manual steps remaining for Viktor (Part 4 of TASK.md):
- Create 5 healthcheck checks on status.felhom.eu (heartbeat, system-health, db-dump, backup, backup-integrity)
- Update controller.yaml on demo-felhom with real UUIDs
- Build and deploy felhom-hub to k3s cluster
- Configure hub.felhom.eu DNS in Cloudflare
- Enable hub reporting on demo-felhom controller.yaml
- Part 1 — Healthcheck enhancements (controller-side):
What was previously completed (2026-02-16 session 17)
- v0.5.4 — Monitoring Page Frontend Fixes (4 bugs, frontend-only):
- Bug 1: Tooltip "Invalid Date" —
items[0].parsed.xunreliable across Chart.js versions. Fixed tooltip callback to useitems[0].raw.x(direct {x,y} data access) withparsed.xas fallback. - Bug 2: Charts fill full width regardless of data density —
setChartXBounds()settingmin/maxat runtime was ignored because the scale was created without them. Fixed by includingmin: now - defaultRangeMs, max: nowin the initialchartOpts()options. Now "7 nap" shows full 7-day x-axis with data clustered on the right. - Bug 3: Sysinfo values not consistently right-aligned —
.sysinfo-gridusedauto-fillcreating variable-width cells. Fixed to1fr 1fr(fixed 2-column). Addedalign-items: baseline,gap: 1rem,white-space: nowrapon labels,font-weight: 600+word-break: break-wordon values. Removed redundant<style>block from monitoring.html (styles now in style.css). - Bug 4: Charts overflow on mobile — Added
min-width: 0on.chart-box(critical CSS grid fix),overflow: hidden+max-width: 100%on.chart-wrapand.chart-wrap-bar,max-width: 100%on canvas. - Controller version: v0.5.4 — deployed and verified on demo-felhom.eu
- Bug 1: Tooltip "Invalid Date" —
What was previously completed (2026-02-16 session 16)
- v0.5.1 — Monitoring Page Bugfixes:
- Bug 1: Hostname —
os.Hostname()returns the container ID inside Docker. Fixed by mounting/etc/hostname:/host/etc/hostname:roand reading it first insysinfo.go. Now showsdemo-felhom. - Bug 2: Tooltip timestamps — Chart.js tooltip callback used
items[0].parsed.x(category index 0,1,2...) instead ofitems[0].label(actual timestamp). Index 0 worked by accident (0 || labelfalls through), but all other points showed 1970-01-01. - Bug 3+4: Default range + empty charts — Default range was
24hbut new system had only minutes of data. Changed to1hdefault for both system and container detail charts. Movedactiveclass to "1 óra" button. - Controller version: v0.5.1 — deployed and verified on demo-felhom.eu
- Bug 1: Hostname —
What was previously completed (2026-02-16 session 15)
- v0.5.0 — Backup Bugfixes + Monitoring Page with Metrics Store:
- Task 1: Fixed "Helyi mentés" showing "–" after restart —
GetFullStatus()now synthesizesLastBackupfromSnapshotHistoryandLastDBDumpfromDumpFileson disk when the in-memory values are nil (e.g., after controller restart). Dashboard handler also updated to useGetFullStatus()instead ofGetStatus()for consistent behavior. - Task 2: Verified backup page caching — Already implemented in v0.4.7 (
RefreshCache, scheduler job,AfterBackupcallback). No changes needed. - Task 3: New Monitoring Page ("Rendszermonitor") — Full system monitoring subsystem:
- SQLite metrics store (
internal/metrics/store.go,types.go): WAL-mode SQLite viamodernc.org/sqlite(pure Go, no CGO). Stores system metrics (CPU%, memory, temperature, load) and container metrics (CPU%, memory, net/block I/O) with timestamp. Downsampled queries via bucket-basedGROUP BYfor Chart.js. 30-day auto-prune via daily scheduler job at 04:00. - Metrics collector (
internal/metrics/collector.go): Background goroutine collects system + container metrics every 60 seconds. System data fromsystem.GetInfo(), container data fromdocker stats --no-streamwith tab-separated format parsing. - System info provider (
internal/metrics/sysinfo.go,sysinfo_other.go): Reads hostname, OS, kernel, CPU model/cores, uptime from/procfilesystem. Linux-specific with build-tag fallback for cross-compilation. - REST API endpoints (4 new routes in
router.go):GET /api/metrics/system(time-series with range presets),GET /api/metrics/containers/summary(current stats),GET /api/metrics/containers/{name}(per-container time-series),GET /api/metrics/sysinfo(static system info). - Monitoring page template (
monitoring.html): 5 sections — System Overview (sysinfo via API), System Metrics Charts (4 line charts: CPU, Memory, Temperature, Load in 2×2 grid), Container Resources (2 horizontal bar charts: CPU% and Memory), Per-container Detail (click to expand with historical charts), Storage (server-rendered progress bars). Time range selectors (1h/6h/24h/7d/30d). Auto-refresh every 60s. - Chart.js 4.4.7 embedded locally (offline environments, ~200KB UMD), dark theme configuration matching site design.
- CSS: ~100 lines added for monitoring page (
.monitor-card,.charts-grid,.chart-box,.container-charts-row,.storage-bars, responsive rules). - Wiring: 4th sidebar nav item "Rendszermonitor", metrics DB path in named volume (
data/metrics.db),/etc/os-release:/host/etc/os-release:rovolume mount in docker-compose.yml, Dockerfile updated togolang:1.24-bookworm(required bymodernc.org/sqlite),go.modupgraded togo 1.24.0.
- SQLite metrics store (
- Controller version: v0.5.0 — deployed and verified on demo-felhom.eu (metrics collecting, 16 containers reporting, sysinfo showing Intel N100 correctly)
- Task 1: Fixed "Helyi mentés" showing "–" after restart —
What was previously completed (2026-02-16 session 14)
- v0.4.7 — Protected Stack Detail Pages + Backup Page Caching:
- Protected stacks clickable —
data-hrefgating changed from{{if not .Protected}}to{{if .Meta.Slug}}on bothstacks.htmlanddashboard.html. Protected stacks with.felhom.yml(i.e. a slug) are now clickable, linking to/apps/{slug}. Stacks without.felhom.ymlremain non-clickable. - "Részletek" button for protected stacks — Protected stack action section in
stacks.htmlnow shows a "Részletek" link when the stack has a slug, next to the restart button. - FileBrowser
.felhom.ymlresources — Addedresourcessection (mem_request: 128M, mem_limit: 256M, pi_compatible: true, needs_hdd: true) to bothinstall_filebrowser()indocker-setup.shand manually on the demo node. FileBrowser detail page now shows memory/Pi/HDD badges. - Backup page caching —
GetFullStatus()no longer runs expensive subprocess calls (restic stats, docker inspect, disk listing) on every page load. Instead, a newRefreshCache()method runs these in the background:- Every 5 minutes via
backup-cachescheduler job - After each successful backup via
AfterBackupcallback - On startup via a goroutine (non-blocking)
- Every 5 minutes via
GetFullStatus()returns the cachedFullBackupStatusinstantly, updating only dynamic fields (running flag, next run times, snapshot history). Falls back to a minimal status if cache hasn't populated yet.- Controller version: v0.4.7 — deployed and verified on demo-felhom.eu
- Protected stacks clickable —
What was previously completed (2026-02-16 session 13)
- v0.4.6 — MariaDB Validation Fix + Dashboard & Protected Stack UX:
- Bugfix: MariaDB dump validation false positive — MariaDB 11.4+ prepends
/*M!999999\- enable the sandbox mode */before the dump header comment.ValidateDump()now scans the first 10 lines for the expected header pattern instead of just checking line 1. Accepts-- MariaDB dump,-- MySQL dump,-- mysqldumpfor MariaDB and-- PostgreSQL database dumpfor PostgreSQL. - Dashboard shows deployed apps only —
dashboardHandler()filters to deployed + protected stacks only. Non-deployed apps remain on the Alkalmazások page. Section heading changed to "Telepített alkalmazások".TotalCountstat card still shows all 52 apps. - Protected stack restart button — Protected stacks (traefik, cloudflared, felhom-controller, filebrowser) now show an "Újraindítás" restart button when operational, on both dashboard (compact ↻) and Alkalmazások page (full button). "Védett" / "Védett rendszerkomponens" badge still shown.
- API protection guard — Centralized guard in
actionStack()blocks all actions exceptrestarton protected stacks (HTTP 403). Defense-in-depth:StopStack()andDeleteStack()retain their own guards. - FileBrowser
.felhom.yml—install_filebrowser()indocker-setup.shnow creates.felhom.ymlwithsubdomain: filesmetadata, so the controller shows thefiles.DOMAIN ↗URL link. Manually created on demo node. - Controller version: v0.4.6 — deployed and verified on demo-felhom.eu
- Bugfix: MariaDB dump validation false positive — MariaDB 11.4+ prepends
What was previously completed (2026-02-16 session 12)
- v0.4.5 — Dedicated Backup Page ("Biztonsági mentés"):
- New
/backupspage with full backup system visibility — 5 sections:- Status overview cards: Local backup status (green/gray), remote placeholder (gray), DB count, repo size
- Schedule section: DB dump/restic/prune schedule with next-run times, last backup time + duration, retention policy, "Mentés most" button
- Database table: Lists all discovered DBs with type badge (PostgreSQL/MariaDB), dump file size, last dump time, validation (table count), status
- Snapshot history table: Last 20 snapshots with ID, time, data added, files new/changed
- Repository info card: Path, size, snapshot count, integrity check status, backed-up paths list, remote copy placeholder
- Backend extensions:
SnapshotRecordtype + ring buffer (20 entries) in Manager for per-snapshot statsDumpValidation— scans dump files for CREATE TABLE statements, validates header and file sizeValidateDump()runs after each successful dump inDumpOne()ListDumpFiles()scans dump directory for existing.sqlfiles (fallback when in-memory results empty)ListSnapshots()on ResticManager — returns all snapshots from restic (newest first)GetFullStatus()on Manager — single call returns everything the page needsLoadSnapshotHistory()populates history from restic on startup (without delta stats)- Restic check result tracking (
lastCheckTime,lastCheckOK) NextDailyRun()exported from scheduler for next-run time calculation
- Server wiring:
Serverstruct now holds*scheduler.SchedulerNewServer()accepts scheduler parameter/backupsroute +backupsHandler()in handlers.go
- New template functions (
funcmap.go):timeAgo,fmtTime,fmtTimeShort,dbTypeLabel,nextRunLabel,pruneLabel,nextPruneLabel,fmtDuration,fmtBytes,shortID - Navigation: Sidebar now has 3 items (Vezérlőpult, Alkalmazások, Biztonsági mentés)
- Dashboard: Backup card title is now a clickable link to
/backups - Auto-refresh: Page polls
/api/backup/statusevery 3s during backup-in-progress, reloads when complete - CSS: Full dark-theme styles for schedule card, database table, snapshot table, repository card, validation badges, DB type badges, empty state
- Controller version: v0.4.5 — deployed and verified on demo-felhom.eu (2 historical snapshots loaded)
- New
What was previously completed (2026-02-15 session 11)
- v0.4.1 — App Filtering + Bugfixes:
- Filter bar on Alkalmazások page: Four pill-shaped filter buttons (Mind/Futó/Leállítva/Telepíthető) with live count badges computed from DOM. Filters stack cards via
display: none, updates URL with?filter=runningviahistory.replaceState. Reads filter from URL on page load for deep-linking support. - New
filterCategorytemplate function (funcmap.go): Maps container state + deployed flag to filter categories (running/stopped/available). Each stack card gets adata-filter-stateattribute for client-side filtering. - Clickable dashboard stat cards: Stat cards (Futó/Leállítva/Összes) changed from
<div>to<a>withhreflinking to/stacks?filter=running,/stacks?filter=stopped,/stacksrespectively. Hover effect with translateY + box-shadow. - docker-compose.yml synced to demo node: Fixed the stale compose file that still had
dashboard.${DOMAIN}Traefik label (from pre-v0.3.0). Now uses correctfelhom.${DOMAIN}label +/sys:/host/sys:romount. - Controller version: v0.4.1 — deployed and verified on demo-felhom.eu
- Remaining manual tasks for Viktor (Task 2 & 3 from TASK.md):
- Verify
felhom.demo-felhom.euresolves correctly (Cloudflare Tunnel public hostname may need updating fromdashboard.*tofelhom.*) - Update Pi-hole local DNS if applicable
- Enable backup in
controller.yamlon demo node (backup.enabled: true) - Create
/srv/backupsdirectories on demo node
- Verify
- Filter bar on Alkalmazások page: Four pill-shaped filter buttons (Mind/Futó/Leállítva/Telepíthető) with live count badges computed from DOM. Filters stack cards via
What was previously completed (2026-02-15 session 10)
- v0.4.0 — Monitoring & Health + Backups (Phase 2 & 3):
- Central job scheduler (
internal/scheduler/scheduler.go):- Replaces ad-hoc goroutines in main.go with a unified scheduler
Every(name, interval, fn)for periodic jobs,Daily(name, timeStr, fn)for scheduled tasks- Panic recovery, skip-if-running, quiet mode for high-frequency jobs (≤30s)
- Daily jobs use
Europe/Budapesttimezone withtime.Timerfor DST correctness - Graceful shutdown with 30s timeout for running jobs
- CPU usage collector (
internal/system/cpu_linux.go):- Background goroutine samples
/proc/statevery 5s, computes delta-based CPU % - Platform stubs for non-Linux in
cpu_other.go
- Background goroutine samples
- Temperature & load metrics (
internal/system/info_linux.go):- Reads
/proc/loadavgfor 1/5/15 min load averages - Reads thermal zones from
/host/sys/class/thermal/(Docker mount) with/sys/fallback - Handles millidegree values, picks highest zone, with hwmon fallback
- Reads
- Healthchecks.io pinger (
internal/monitor/pinger.go):- HTTP ping client for Healthchecks.io-compatible endpoints
- POST to
/ping/{uuid}(success),/fail(failure),/start(started) - 10s timeout, 3 retries with 2s backoff, skips CHANGEME UUIDs
- System health checks (
internal/monitor/healthcheck.go):- Checks disk, memory, CPU, temperature, Docker reachability, protected containers
- Returns HealthReport with status "ok"/"warn"/"fail" + formatted message for pings
- Database dump engine (
internal/backup/dbdump.go):- Auto-discovers PostgreSQL/MariaDB containers via
docker ps+docker inspect - Dumps via
docker exec pg_dump/mariadb-dumpwith 5min timeout - Atomic writes (
.tmp→.sql), empty file detection, stale temp cleanup
- Auto-discovers PostgreSQL/MariaDB containers via
- Restic integration (
internal/backup/restic.go):- Auto-generates repository password (32 random bytes, base64url)
- Init, snapshot (JSON output), prune, check, stats, latest snapshot
- Stale lock detection with automatic unlock + retry
- Backup orchestrator (
internal/backup/backup.go):- DB dumps + restic snapshots, weekly prune on Sundays
- Thread-safe running flag, Healthchecks.io pings with results
RunFullBackup()for manual trigger (sequential: dumps → snapshot)
- Wiring updates:
main.go: scheduler-based job registration, cpuCollector lifecycle, pinger + backupMgr initapi/router.go:GET /api/backup/status,POST /api/backup/runweb/server.go+handlers.go: pass cpuCollector to GetInfo(), backup status on dashboardfuncmap.go:tempColor,fmtTemp,fmtLoadtemplate functions
- Dashboard UI enhancements:
- CPU usage bar with load average display below
- Temperature with colored indicator dot (green/yellow/red at 60°/75°C)
- Backup status card: last run time, DB count, repo size/snapshots
- "Mentés most" button triggers manual backup via API
- Config updates:
controller.yaml.example: addedsystem_health_interval,hdd_path,system.reserved_memory_mbdocker-compose.yml: added/sys:/host/sys:romount for temperature readingrestic_password_filedefault changed todata/subdir (auto-generated in named volume)
- Central job scheduler (
- Controller version: v0.4.0 — deployed and verified on demo-felhom.eu
What was previously completed (2026-02-15 session 9)
- v0.3.0 — Structural refactoring (templates + server split + domain rename):
- Templates: go:embed migration — moved all 7 HTML templates + CSS from Go string constants to individual files in
internal/web/templates/. Createdembed.gowith//go:embeddirective. Template loading now usesParseFS()instead ofParse(). CSS served from embed.FS viaReadFile(). Zero runtime file dependencies — still compiled into the binary. - Server decomposition — split monolithic
server.go(540 lines) into focused files:auth.go: session struct, auth middleware, login/logout handlers, session managementhandlers.go: page handlers (dashboard, stacks, logs, deploy, app detail)funcmap.go: template FuncMap with 14 custom functionsserver.go: Server struct, NewServer, loadTemplates (3-liner), ServeHTTP routing, render helper, static file serving
- Domain rename — controller subdomain changed from
dashboard.*tofelhom.*in Traefik labels and setup script - Documentation updated — CLAUDE.md, README.md, CONTEXT.md all reflect new file structure
- Reminder for Viktor: Update Cloudflare Tunnel public hostname (
dashboard.demo-felhom.eu→felhom.demo-felhom.eu) and Pi-hole DNS if needed
- Templates: go:embed migration — moved all 7 HTML templates + CSS from Go string constants to individual files in
- Controller version: v0.3.0
What was previously completed (2026-02-15 session 8)
- FileBrowser as infrastructure service:
- Created
scripts/hdd-setup.sh(adapted from deploy-portainer) — sets up HDD folder structure withDokumentumokuser dir - Created
scripts/docker-setup.sh(adapted from deploy-portainer) — installs Docker, Traefik, FileBrowser as infra services - Added
filebrowserto protected stacks incontroller.yaml.example - Removed
templates/filebrowser/from app-catalog-felhom.eu (no longer a catalog app)
- Created
- Orphan stack detection and deletion:
- Added
Orphanedfield to Stack struct +getCatalogTemplateSlugs()helper - Orphan detection in
ScanStacks()— deployed stacks with no matching catalog template marked as orphaned - New
delete.go:DeleteStack()(compose down + HDD cleanup + dir removal),GetStackHDDData(),parseComposeHDDMounts() - Safety: protected HDD paths (root, media, storage, Dokumentumok, appdata) can never be deleted
- New API endpoints:
DELETE /api/stacks/{name}andGET /api/stacks/{name}/hdd-data - UI: orange "Elavult" badge on orphaned stacks, "Törlés" button, delete confirmation modal
- Modal shows HDD data paths/sizes, checkbox for "Felhasználói adatok törlése a merevlemezről"
- Hides "Frissítés" and "Részletek" buttons for orphaned stacks
- Added
- Verified: 1 orphaned stack detected on startup (filebrowser — now infra, removed from catalog)
- Controller version: v0.2.15
Previously completed (2026-02-14 session 7)
- Fixed YAML parse error in romm
.felhom.yml(app-catalog repo):- Root cause: Hungarian opening quote
„(U+201E) paired with ASCII"(0x22) inside YAML double-quoted strings terminated the string prematurely - Affected lines:
help_textfor IGDB Client Secret and SteamGridDB API Key fields - Fix: escaped inner ASCII double quotes with
\"in the YAML strings - This caused
LoadMetadata()to silently fail and return empty defaults for ALL romm metadata (tagline, resources, category — everything)
- Root cause: Hungarian opening quote
- Added error logging to
LoadMetadata()inmetadata.go:[ERROR]log on YAML parse failure (was silently swallowed — critical bug)- Temporary
[DEBUG]log used for diagnosis, then removed
- Fixed deploy command in CLAUDE.md:
sedpattern now targets onlyimage:lines (was matching service name too, breaking YAML)- Added
sudofor both sed and docker compose (directory is root-owned)
- Controller version: v0.2.14
Previously completed (2026-02-14 session 6)
- Bug fix: App info logo SVG rendering —
.app-info-logoCSS intemplates.go:- Added
min-width,min-height,max-width,max-height: 80pxandoverflow: hidden - Prevents SVG images with explicit dimensions or no viewBox from overflowing container
- Logo now reliably renders at 80x80 regardless of SVG intrinsic size
- Added
- Controller version: v0.2.12
Previously completed (2026-02-14 session 5)
- App detail/info pages — new feature:
- New route:
GET /apps/{slug}renders a full info page (was redirect to deploy page) - Hero section with logo, tagline, resource badges
- Screenshots section (graceful — hidden via
onerrorif assets don't exist) - Info cards: use cases, first steps, prerequisites, default credentials, docs link
- Optional config form with AJAX save (POST
/api/stacks/{name}/optional-config) - New
.felhom.ymlfields:app_info(tagline, use_cases, first_steps, prerequisites, default_creds, docs_url) andoptional_config(groups of env var fields) - New structs in
metadata.go:AppInfo,OptionalConfigGroup,OptionalConfigField UpdateOptionalConfigindeploy.go: saves optional env vars toapp.yaml, restarts deployed stacks withdocker compose up -dto pick up new env vars- Navigation updated: stack cards on dashboard/stacks pages now link to
/apps/{slug}, deploy page has "Részletek" link back to info page
- New route:
- RoMM metadata updated (app-catalog repo):
- Full
app_infosection: tagline, 5 use cases, 6 first steps, 3 prerequisites, default creds, docs URL - 6 optional config fields for metadata providers: IGDB (client_id + secret), SteamGridDB, ScreenScraper (user + password), MobyGames
- docker-compose.yml updated with SCREENSCRAPER_USER, SCREENSCRAPER_PASSWORD, MOBYGAMES_API_KEY env vars
- Display name fixed: "ROMM" → "RomM"
- Full
- Controller version: v0.2.11
Previously completed (2026-02-14 session 4)
- Fixed deploy race condition in
internal/stacks/deploy.go:- In-memory
Deployedflag now set BEFOREdocker compose up -d(compose up can take 30-60s for image pulls) - On failure: both in-memory state and disk (app.yaml) are reverted
- Eliminates stale "Telepítés" button during long compose operations
- In-memory
- Added
checkBeforeDeploy()JS guard ininternal/web/templates.go:- Telepítés buttons on Vezérlőpult and Alkalmazások pages now fetch live state from
/api/stacks/{name}before navigating - If app is already deployed (e.g., another tab deployed it), shows alert and reloads page instead of navigating to deploy form
- Catches stale UI state gracefully
- Telepítés buttons on Vezérlőpult and Alkalmazások pages now fetch live state from
Previously completed (2026-02-14 session 3)
- Enhanced debug logging across all stack operations in
internal/stacks/:- Operation timing: All stack ops (start, stop, restart, update, deploy) now log elapsed time
- Post-start container state check: Async goroutine after start/restart/update/deploy
- Image pull detection: Checks local images before deploy/update (debug level)
- GetLogs/ScanStacks improvements: Byte count logging, deployed/available counts
- All verbose checks gated on
cfg.Logging.Level == "debug"; timing always at INFO
- UI improvements in
internal/web/templates.goandserver.go:- Memory bar fix on deploy page: Bar segments now always visible (min-width: 3px), new app segment uses translucent green with distinct border for clear visual separation from committed memory
- Clickable app cards: Cards on Vezérlőpult and Alkalmazások pages are now clickable (navigates to deploy/detail page). Uses
data-hrefattribute + delegated click handler. Protected stacks excluded. Actions area (buttons, state labels) excluded from click-to-navigate - Live-scrolling logs: Logs page now auto-refreshes every 3s via AJAX polling (
?raw=1returns plain text). Fixed-height container (70vh) with auto-scroll to bottom. Pulsing green "Élő" indicator. Pause/resume toggle ("Szüneteltetés"/"Folytatás"). User scroll position preserved when scrolled up to read history - Deployment progress UI: Deploy button no longer shows alert+redirect immediately. Instead shows 3-step progress panel: config saved → containers starting → app initializing. Polls
GET /api/stacks/{name}every 3s to track actual container health state. Handles running (auto-redirect), starting (keep polling), unhealthy (warning), exited (error), and 120s timeout. Shows elapsed time counter
- Mealie healthcheck fix (app-catalog-felhom.eu):
wget --spiderreplaced with Python TCP socket check — mealie image doesn't include wgetstart_periodincreased to 60s (DB migrations take ~40s on first start)
- Healthcheck audit: filebrowser (Alpine, has BusyBox wget — OK), stirling-pdf (Ubuntu, has wget — OK)
Previously completed (2026-02-15 session 2)
- Phase 4: Git Sync + App Catalog Audit — major milestone
- Git sync module (
internal/sync/sync.go):- Clones/pulls app-catalog-felhom.eu repo to local cache on startup
- Periodic sync based on
git.sync_interval(default 15m) - Copies
docker-compose.yml+.felhom.ymlto stacks dir (never overwritesapp.yaml/.env) - SHA-256 content comparison — only writes changed files
- Triggers
ScanStacks()after sync so dashboard updates immediately - Uses
os/execgit CLI — no Go git library dependency
- Manual sync button ("Sablonok frissítése") on Alkalmazások page:
POST /api/syncendpoint with 30s debounce- Toast notification shows result (success/failure/what changed)
- Auto-reloads page if new apps or updates detected
- Sync status added to
/api/system/info(last_sync, last_status, syncing flag) - .felhom.yml files created for all 10 apps (paperless-ngx already had one):
- actualbudget, docmost, filebrowser, homebox, immich, mealie, romm, stirling-pdf, vaultwarden
- All follow the same format: display_name, description, category, subdomain, resources, deploy_fields
- Docker Compose templates audited and fixed for all 10 apps:
- Fixed
{{DOMAIN}}→${DOMAIN}syntax in homebox, mealie, romm, stirling-pdf - Fixed
{{HDD_PATH}}→${HDD_PATH}in romm - Added
deploy.resources.limits.memoryto all services across all templates - Added
TZ=Europe/Budapestto all sidecar services (postgres, redis, mariadb) - Added healthcheck to romm main service
- Added
romm-rediscondition: service_healthy(wasservice_started) - Standardized header comment blocks across all templates
- Fixed
- Documentation updated: app-catalog README, CLAUDE.md, CONTEXT.md
Previously completed (2026-02-15 session 1)
- Memory validation during deployment:
- Pre-deploy memory check: compares
mem_requestsum against usable system RAM - Hard block if requests exceed usable memory (total - 384MB reserved)
- Soft warning if
mem_limitsum exceeds total RAM (overcommit OK for limits) ParseMemoryMB()supports "500M", "1G", "1.5G", "1024" formatsCommittedMemory()sums requests/limits across all deployed stacks- Memory summary bar shown on deploy page before user clicks deploy
system.reserved_memory_mbconfigurable in controller.yaml (default: 384)
- Pre-deploy memory check: compares
- Display:
~prefix on mem_request in UI badges (display-only, exact value stored) - Felhom.eu logo replaced text logos in sidebar and login page with actual SVG logo
- Logo SVG embedded as Go string constant, served at
/static/felhom-logo.svg
- Logo SVG embedded as Go string constant, served at
Previously completed (2026-02-14)
- System info bar on Vezérlőpult dashboard: RAM, SSD, and optional HDD usage
- Progress bars with color coding (green < 70%, yellow 70-85%, red > 85%)
- New
internal/systempackage reads/proc/meminfo+syscall.Statfs - Platform-specific: Linux impl + non-Linux stub (build tags)
- Hungarian labels: "Memória", "SSD tárhely", "Külső HDD"
- Docker Compose memory limits on paperless-ngx template:
- paperless-webserver: 768M, postgres: 256M, redis: 128M
- Added
mem_limitfield to.felhom.ymlResourceHints (total: 1152M)
/api/system/infoendpoint now returns live system metrics (was customer info)- Config: Added
paths.hdd_pathfor external HDD monitoring - Controller image builds via build.sh, pushes to Gitea container registry
Previously completed (2026-02-13)
- Built the entire felhom-controller from scratch (Go, no frameworks)
- Debugged and fixed 7 issues during first real deployment:
- Password validation (empty passwords accepted)
- In-memory Deployed flag not updating after deploy
- Health-aware state parsing (starting/unhealthy detection)
- Random card ordering (Go map iteration)
- "Részletek" button redirect for deployed apps
- Paperless OCR language installation (LANGUAGES vs LANGUAGE env var)
- Documentation: restart vs up -d for image updates
What's next (priorities)
- Test per-app backup — enable backup for Paperless-ngx HDD data, trigger manual backup, verify restic snapshot includes HDD paths
- Test restore — restore app data from snapshot, verify file recovery (now possible with /mnt:rw mount)
- Deploy Immich — tests HDD path + secrets + multi-storage (biggest real-world test)
- Add
app_info+optional_configto more apps (Immich, Mealie, Vaultwarden) - Test on Raspberry Pi (pi-customer-1)
- Self-update mechanism
- Hub alerting (webhook to Healthchecks for stale customers)
- Docker volume backup (mount
/var/lib/docker/volumes:rointo controller)