958 lines
107 KiB
Markdown
958 lines
107 KiB
Markdown
## Changelog
|
||
|
||
### What was just completed (2026-02-19 session 55)
|
||
- **v0.15.5 — Fix startup hub report silently failing:**
|
||
|
||
`Push()` now returns actual errors instead of always `nil`. Previously, push failures were logged internally but the caller could never detect them, leading to a misleading `[INFO] Startup hub report sent` log even when the push actually failed (e.g., hub returning HTTP 503 during simultaneous deployment). Removed the "Never returns error to caller" behavior: marshal error returns a wrapped error, and after 3 failed retries the error is returned to the caller (the internal `[WARN]` log before `return nil` is gone).
|
||
|
||
Startup hub push now retries 3 times with 15-second delays between outer attempts, giving the hub time to come up when both are deployed together. Each outer attempt uses `Push()`'s own internal 3-retry logic (5s backoff), so the hub gets up to ~40s total to become ready. If all 3 outer attempts fail, logs a clear warning with the next scheduled push interval.
|
||
|
||
**Files modified (2):** `internal/report/pusher.go`, `cmd/controller/main.go`
|
||
|
||
### What was just completed (2026-02-19 session 54)
|
||
- **v0.15.4 (controller) + hub v0.1.6 — Hub reporting improvements:**
|
||
|
||
**Controller:** When `hub.enabled: false` but URL+API key are configured, the controller now creates the `Pusher` and sends a one-time "disabled" notification on startup (`health.status = "disabled"`, `reporting_disabled: true`). This replaces the old behavior where a disabled controller was indistinguishable from a crashed node. Added `PushOnce()` method to `Pusher` (bypasses the `enabled` flag). Added `ReportingDisabled` field to the `Report` struct.
|
||
|
||
**Hub:** Added "disabled" status handling — when the latest report has `health_status = "disabled"`, the overall status is "disabled" (checked BEFORE the stale-time logic, so it stays "PAUSED" even after 30min+). Dashboard shows gray "PAUSED" badge. Customer detail shows "Reporting has been disabled on this node" with a hint to re-enable. Storage labels now shown (`label` field with fallback to `mount`). Report history timestamps now show date + time ("Feb 19 09:46" instead of "09:46:54"). New `.status-badge-disabled` CSS (neutral gray `#475569`).
|
||
|
||
**Files modified (controller):** `internal/report/types.go`, `internal/report/pusher.go`, `cmd/controller/main.go`
|
||
**Files modified (hub):** `hub/internal/web/server.go`, `hub/internal/web/templates/dashboard.html`, `hub/internal/web/templates/customer.html`, `hub/internal/web/templates/style.css`
|
||
|
||
### What was just completed (2026-02-19 session 53)
|
||
- **v0.15.3 — Show all storage paths on dashboard + fix hub report:**
|
||
|
||
Dashboard ("Vezérlőpult") and monitoring ("Rendszermonitor") pages now show usage bars for ALL registered storage paths instead of just one hardcoded "Külső HDD" bar. New `StorageBarInfo` type and `buildStorageBars()` helper build bars from `settings.GetStoragePaths()`. Each bar shows the storage label and live disk usage.
|
||
|
||
Hub storage report now correctly includes all registered storage paths with proper mount paths and labels. Previously it sent only root `/` plus one HDD entry using the deprecated (empty) `cfg.Paths.HDDPath`. Now uses `system.GetDiskUsage()` per storage path, same as the dashboard bars. Added `Label` field to `StorageReport` in `types.go`.
|
||
|
||
**Files modified (5):** `internal/web/handlers.go`, `internal/web/templates/dashboard.html`, `internal/web/templates/monitoring.html`, `internal/report/builder.go`, `internal/report/types.go`
|
||
|
||
### What was just completed (2026-02-19 session 52)
|
||
- **v0.15.2 — Fix data loss on container restart (2 bugs):**
|
||
|
||
**Bug 1:** Snapshot history delta stats (HOZZÁADOTT, ÚJ FÁJL, VÁLTOZOTT) showed 0 after container restart because restic doesn't store these stats — they were only in memory. Fixed by persisting the snapshot history ring buffer to `data/snapshot-history.json`. On startup, persisted stats are merged with restic repo snapshots. Added `saveSnapshotHistory()` (atomic write via tmp+rename), `loadSnapshotHistoryFromFile()`, updated `appendSnapshotRecord()` to save after each backup, and updated `LoadSnapshotHistory()` to merge persisted + restic data.
|
||
|
||
**Bug 2:** DB validation (ÉRVÉNYESÍTÉS column) showed "–" after restart because the synthesized `LastDBDump.Results` didn't copy `Validation` from `DumpFileInfo`. One-line fix: added `Validation: f.Validation` to the synthesized `DumpResult` in `GetFullStatus()`.
|
||
|
||
**Files modified:** `internal/backup/backup.go`
|
||
|
||
### What was just completed (2026-02-19 session 51)
|
||
- **v0.15.1 — Backup Page "Részletek" Overhaul:**
|
||
|
||
Replaced the "Tároló" section on the backup page with a new "Részletek" section containing 3 collapsible tier sections with per-drive breakdowns.
|
||
|
||
**Tier 1 (Helyi mentés):** Shows per-drive restic repo stats (size, snapshot count) with storage labels. Includes aggregated totals when multiple drives exist, plus DB dump summary, integrity check, and encryption key (all carried over).
|
||
|
||
**Tier 2 (Másodlagos másolat):** Groups cross-drive backup items by destination drive, separated into restic and rsync method sections with per-app sizes.
|
||
|
||
**Tier 3 (Távoli mentés):** Placeholder for future B2/S3/SFTP remote backup.
|
||
|
||
**Restore UI improvements:** Snapshot dropdown now groups by tier (optgroup), shows tier label + drive name per snapshot (e.g., "1. szint, hdd_1"), and marks Tier 1 as recommended. Also lists Tier 2 (secondary restic) snapshots for visibility.
|
||
|
||
**Backend:** New `DriveRepoInfo` struct, `perDriveRepoStats()` method, `ListAllSnapshots()` that includes secondary restic repos, and `Tier2DriveGroup` handler struct. `SnapshotInfo` now carries `Tier` and `DriveLabel` fields.
|
||
|
||
**Files modified (5):** `internal/backup/backup.go`, `internal/backup/restic.go`, `internal/web/handlers.go`, `internal/api/router.go`, `internal/web/templates/backups.html`, `internal/web/templates/style.css`
|
||
|
||
### What was just completed (2026-02-18 session 50)
|
||
- **v0.15.0 — Attach Existing Drive (bind mount wizard):**
|
||
|
||
New feature: Settings → "Meglévő meghajtó csatolása" wizard. Allows attaching a drive that already has a filesystem (ext4, etc.) without formatting. Solves the real-world scenario where a customer's drive contains existing data that must be preserved.
|
||
|
||
**How it works:** The partition is mounted read-only at a hidden staging path (`/mnt/.felhom-raw/<label>`). A directory browser lets the user navigate the drive's contents and create a new folder. The selected folder is bind-mounted at `/mnt/<hdd-name>`, keeping the controller's data isolated from existing files. Two fstab entries (raw + bind, both with `nofail`) ensure the mount survives reboots.
|
||
|
||
**Wizard flow:** Scan → Select partition (only shows partitions with existing FS) → Mount raw + Browse directories → Create folder if needed → Configure mount name + label → Finalize (bind mount + fstab + permissions + register). Cancel cleans up the temp mount.
|
||
|
||
**New files (4):** `internal/storage/attach.go`, `internal/storage/attach_linux.go`, `internal/storage/attach_other.go`, `internal/web/templates/storage_attach.html`
|
||
**Modified files (3):** `internal/web/storage_handlers.go` (6 new API handlers), `internal/web/server.go` (route + activeRawMount field), `internal/web/templates/settings.html` (button)
|
||
|
||
### What was just completed (2026-02-18 session 49)
|
||
- **v0.14.2 — Backup Bug Fixes (4 fixes from code review):**
|
||
|
||
**Bug 1 (HIGH):** rsync `--delete` was destroying `_db/` and `_config/` directories on every single-mount run. Fixed by adding `--exclude _*` to the rsync command in `runRsyncBackup()`. Controller-managed directories (underscore prefix) are now excluded from `--delete` cleanup. (`crossdrive.go`)
|
||
|
||
**Bug 2 (MEDIUM):** Scheduled backups (`RunBackup`, `RunDBDumps`) did not set `m.running`, so UI showed "not running" during nightly jobs and restore could overlap. Fixed by extracting `acquireRunning()` / `releaseRunning()` helpers and `runDBDumpsInternal()` / `runBackupInternal()` internal methods. All three public entry points now guard with the running flag; `RunFullBackup()` calls the internal methods directly to avoid deadlock. (`backup.go`)
|
||
|
||
**Bug 3 (MEDIUM):** `ValidateDestination` silently succeeded when `GetDiskUsage` returned nil (exotic filesystems, FUSE, NFS). Fixed by logging `[WARN]` and returning nil (backward-compatible). (`crossdrive.go`)
|
||
|
||
**Bug 4 (MEDIUM):** Empty `systemDataPath` produced relative dump paths. Fixed with: startup `[WARN]` in `NewManager()`, `[ERROR]` log in `GetAppDrivePath()`, and explicit guard in `DumpStackDB()` that returns an error when path is empty or non-absolute. (`backup.go`)
|
||
|
||
**Files modified (2):** `internal/backup/backup.go`, `internal/backup/crossdrive.go`
|
||
|
||
### What was just completed (2026-02-18 session 48)
|
||
- **v0.13.1 — UI Polish Fixes Round 2 (4 fixes):**
|
||
|
||
**Fix 1:** Deploy page "Biztonsági mentés" section now has proper card border. Root cause: `.deploy-cross-drive` used undefined CSS variables `--card-bg` and `--border` (only `--bg-secondary` and `--border-color` exist). Fixed by using correct vars (`style.css`).
|
||
|
||
**Fix 2:** Auto-generated env values section cleaned up (`deploy.html`, `style.css`). Badge moved inline with label. "Másolás" buttons removed (native select+copy sufficient). Secret fields keep show/hide toggle. Non-secret fields now plain readonly input without button wrapper. Removed `copyAutoField()` JS. CSS updated: `.form-group-auto` now block layout (was flex row), label uses `display: flex; gap: .5rem`, badge downsized to `0.75rem / normal weight`, readonly inputs get muted background.
|
||
|
||
**Fix 3:** Snapshot table n/a → 0 (`backups.html`). Replaced `<span class="col-na" title="...">n/a</span>` with plain `0` in all three stats columns. Removed `.col-na` CSS class (no longer used).
|
||
|
||
**Fix 4:** Disk warnings moved from top banner to inline under storage bars (`alerts.go`, `layout.html`, `handlers.go`, `dashboard.html`, `monitoring.html`, `style.css`). Added `Inline bool` field to `Alert` struct. Disk-related warnings set `Inline: true`. Layout banner skips inline alerts. New `GetInlineAlerts(page)` method on `AlertManager`. Dashboard and monitoring handlers pass `DiskWarnings`. Inline warning block rendered below storage bars. New `.inline-warning*` CSS classes (compact, subtle, colored).
|
||
|
||
**Files modified (8):** `alerts.go`, `handlers.go`, `templates/style.css`, `templates/dashboard.html`, `templates/backups.html`, `templates/deploy.html`, `templates/monitoring.html`, `templates/layout.html`
|
||
|
||
### What was just completed (2026-02-18 session 47)
|
||
- **v0.13.0 — UI Polish Fixes (8 independent fixes):**
|
||
|
||
**Fix 1:** backup-status-card border already correct (verified same styling as system-info-card).
|
||
|
||
**Fix 2:** Deploy page auto-generated fields now show actual values for deployed apps (`deploy.html`, `handlers.go`). Secrets show as password fields with show/hide toggle; domain/plain values show as readonly text with copy button. JS helpers `toggleAutoField()` / `copyAutoField()` added.
|
||
|
||
**Fix 3:** Temperature display made more prominent (`dashboard.html`, `style.css`). Dot enlarged to 11px; value wrapped in colored pill badge (`.temp-value-pill` / `.temp-pill-{green|yellow|red}`).
|
||
|
||
**Fix 4:** Dashboard backup card reworked (`dashboard.html`, `handlers.go`). Removed "Mentés most" button and `triggerBackup()` JS. Removed "Tároló méret" line. Added Tier 2 status line (configured/total apps) + warning row for failed cross-drive backups. Handler now computes `CrossDriveTotal`, `CrossDriveConfigured`, `CrossDriveFailed`.
|
||
|
||
**Fix 5:** HDD warning banner scoped to dashboard + monitoring pages only (`alerts.go`, `layout.html`, `funcmap.go`). Added `PageOnly []string` field to `Alert` struct. Disk-related warnings (keywords "meghajtón", "adattároló") get stable ID `"disk-not-separate"` + `PageOnly: ["dashboard", "monitoring"]`. `pageMatch()` template function added. Layout renders alerts conditionally.
|
||
|
||
**Fix 6:** Tárhely section moved up in Rendszermonitor — now appears right after "Rendszer áttekintés", before "Távoli monitoring" (`monitoring.html`).
|
||
|
||
**Fix 7:** Snapshot table improvements (`backups.html`, `style.css`). "MÉRET" renamed to "HOZZÁADOTT (új adat)". `–` for unavailable data replaced with `n/a` (with tooltip explaining restic limitations). New `.col-subtitle` and `.col-na` CSS classes.
|
||
|
||
**Fix 8:** Tároló section restructured into tiers (`backups.html`, `handlers.go`, `style.css`). Tier 1 (restic local), Tier 2 (cross-drive, only shown if configured), DB dump directory + total size. Removed "Távoli másolat: Nincs beállítva" placeholder. Handler passes `DBDumpDir`, `DBDumpTotalBytes`, `Tier2Dests` (deduplicated). New `.repo-tier` / `.repo-tier-title` CSS.
|
||
|
||
**Files modified (9):** `alerts.go`, `funcmap.go`, `handlers.go`, `templates/style.css`, `templates/dashboard.html`, `templates/backups.html`, `templates/deploy.html`, `templates/monitoring.html`, `templates/layout.html`
|
||
|
||
### What was just completed (2026-02-18 session 46)
|
||
- **v0.12.9 — Tier 2 for All Apps + Status Dot Update:**
|
||
|
||
**Fix 1: Tier 2 now configurable for ALL apps — not just HDD apps (`crossdrive.go`)**
|
||
- Removed `len(mounts) == 0` error gate from `RunAppBackup()` — empty mounts = config-only backup
|
||
- rsync: DB dump copy (`_db/`) + config rsync (`_config/`) still runs even with zero HDD mounts
|
||
- restic: config dir + DB dump dir still appended even without mount paths
|
||
- Non-HDD apps (Mealie, Gokapi, etc.) can now be protected against drive failure via Tier 2
|
||
|
||
**Fix 2: Status dot logic updated, HasHDDData gate removed (`handlers.go`)**
|
||
- `buildAppBackupRows()`: "auto" (gray) status removed — all apps start yellow ("Csak helyi mentés")
|
||
- Green requires Tier 2 configured + last status "ok" (not just "configured but never run")
|
||
- Tier2 section is now unconditional — no `if app.HasHDDData` gate
|
||
- Cross-drive summary loop: removed `if !app.HasHDDData { continue }` — all apps in summary
|
||
|
||
**Fix 3: Backup page template updates (`backups.html`)**
|
||
- Tier 2 row shown for all apps (removed `{{if .HasHDDData}}` gate)
|
||
- Meta badge: non-HDD apps show "Konfig" or "Konfig + DB" instead of "Auto"
|
||
- Tier 3 placeholder row added (grayed out "Hamarosan / távoli offsite")
|
||
- Button text: "Összes HDD mentés" → "Összes 2. mentés futtatása most"
|
||
|
||
**Fix 4: Deploy page cross-drive section visible for all deployed apps (`deploy.html`)**
|
||
- Removed `{{if .StorageInfo}}` double-gate — section now shows for all deployed apps
|
||
- Updated heading: "Másolat másik meghajtóra (felhasználói adatok)" → "2. mentés — másolat másik meghajtóra"
|
||
- Updated hint: "mint az alkalmazás adattárolója" → "a meghibásodás elleni védelem érdekében"
|
||
|
||
**Files modified (4):** `internal/backup/crossdrive.go`, `internal/web/handlers.go`, `internal/web/templates/backups.html`, `internal/web/templates/deploy.html`
|
||
|
||
### What was just completed (2026-02-18 session 45)
|
||
- **v0.12.8 — Complete Cross-Drive Backup + Per-Tier UI:**
|
||
|
||
**Fix 1: Cross-drive backup now includes DB dumps + app config (`crossdrive.go`, `main.go`)**
|
||
- `CrossDriveRunner` gets `dbDumpDir` field + `SetDBDumpDir(dir string)` setter
|
||
- `copyStackDBDumps()` helper copies `<stackName>_*.sql` files to `_db/` subfolder in rsync dest
|
||
- `runRsyncBackup()`: after HDD mount rsync loop, copies DB dumps to `_db/` and rsyncs config dir to `_config/` — both non-fatal on error
|
||
- `runResticBackup()`: appends config dir and full DB dump dir to restic paths (restic deduplicates)
|
||
- rsync destination layout: `backups/rsync/<app>/_db/` (dumps) + `_config/` (compose+yaml) + user data
|
||
- `main.go`: `crossDriveRunner.SetDBDumpDir(cfg.Paths.DBDumpDir)` wired after runner init
|
||
|
||
**Fix 2: UI restructured from per-layer to per-tier (`handlers.go`, `backups.html`, `style.css`)**
|
||
- `AppBackupRow` struct rebuilt: dropped old `DBLastRun/Status`, `VolumeLastRun/Status`, `HasUserData`, `UserDataConfigured/Method/Dest/Schedule/LastRun/LastStatus/LastError/StatusBadge` fields
|
||
- New fields: `BackupContents` (e.g., "DB + Konfig + Adatok"), `Tier1LastRun/LastStatus/DBStatus`, `Tier2Configured/Method/MethodLabel/Dest/Schedule/LastRun/LastStatus/LastError/StatusBadge/SizeHuman/Browsable`
|
||
- `buildAppBackupRows()` rewritten: destination health now via `s.crossDriveRunner.ValidateDestination()` instead of `system.CheckBackupDestination()`
|
||
- `backups.html`: two tier rows (1. mentés / 2. mentés) replace the old three layer rows (DB / Konfig / Userdata)
|
||
- `style.css`: added `.tier-label`, `.tier-location`, `.tier-contents`, `.tier-size`, `.tier-browsable` classes
|
||
|
||
**Fix 3: Cleanup (`router.go`)**
|
||
- `filterSnapshotsByPaths()` and `pathCovers()` deleted (were unused since v0.12.7a)
|
||
|
||
**Files modified (6):** `internal/backup/crossdrive.go`, `cmd/controller/main.go`, `internal/web/handlers.go`, `internal/web/templates/backups.html`, `internal/web/templates/style.css`, `internal/api/router.go`
|
||
|
||
### What was just completed (2026-02-18 session 44)
|
||
- **v0.12.7a — Post-deploy fixes:**
|
||
|
||
**Fix A: Restore now shows snapshots for all apps (`internal/api/router.go`)**
|
||
- Root cause: `filterSnapshotsByPaths` filtered older snapshots (pre-v0.12.7) by HDD paths. Older snapshots don't contain HDD paths (backup wasn't mandatory yet), so Immich got zero snapshots.
|
||
- Fix: removed HDD path filtering entirely from `backupSnapshots`. All snapshots contain config + DB dumps and are useful for any app. `RestoreApp` extracts whatever paths are available from the chosen snapshot.
|
||
- `filterSnapshotsByPaths` and `pathCovers` functions kept (unused, no compile error).
|
||
|
||
**Fix B: Clarified "no cross-drive" warning (`internal/web/handlers.go`, `backups.html`, `style.css`)**
|
||
- Root cause: "Nincs beállítva" / red dot implied no backup at all — misleading since nightly restic now always covers HDD data.
|
||
- `handlers.go`: status `"red"` → `"yellow"`, StatusText → `"Nincs második másolat (csak helyi mentés)"`
|
||
- `backups.html`: added `✓ Helyi mentés auto` badge before the `⚠ Nincs 2. másolat` warning
|
||
- `style.css`: `.layer-auto-ok` class added (green text for the auto badge)
|
||
|
||
**Files modified (3):** `internal/api/router.go`, `internal/web/handlers.go`, `internal/web/templates/backups.html`, `internal/web/templates/style.css`
|
||
|
||
### 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 via `ListDeployedStacks()` — no longer reads `GetAppBackupMap()` or checks `Enabled` flag
|
||
- `DiscoverAppData()` signature simplified: dropped `backupPrefs map[string]bool` parameter; `BackupEnabled` is now derived from `HasHDDData` (if app has HDD data, it's always backed up)
|
||
- `RefreshCache()` updated to call new `DiscoverAppData(m.stackProvider, status.DiscoveredDBs)` signature
|
||
- 5 dead settings methods deleted: `IsAppBackupEnabled`, `SetAppBackup`, `GetAppBackupMap`, `SetAppBackupBulk`, `GetAppBackupPrefs` — `AppBackupPrefs.Enabled` field 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 `DBDumper` interface with `DumpStackDB(ctx, stackName)` in `crossdrive.go`
|
||
- `CrossDriveRunner` gets `dbDumper` field + `SetDBDumper(d DBDumper)` setter
|
||
- `Manager.DumpStackDB()` discovers containers for that stack via `DiscoverDatabases()`, runs `DumpAll()`, persists validation cache — same logic as nightly dump but scoped to one stack
|
||
- `RunAppBackup()` calls `DumpStackDB()` before `ValidateDestination()` — non-fatal on failure (logs warn, proceeds with user data)
|
||
- `main.go` wires `crossDriveRunner.SetDBDumper(backupMgr)` after both are initialized
|
||
|
||
**Fix 3: Restore dropdown shows ALL deployed apps (`backups.html`, `restore.go`, `router.go`)**
|
||
- `restore.go` rewritten: no `IsAppBackupEnabled()` check; resolves `GetStackComposePath` + `DBDumpDir` + HDD mounts; always restores config+DB, adds user data if `hasHDD`; logs restore type (`config+DB` vs `full (config+DB+userdata)`)
|
||
- Restore dropdown template: removed `{{if and .HasHDDData .BackupEnabled}}` filter; every app gets an `<option>` with `data-has-hdd` and `data-has-db` attributes
|
||
- New `#restore-type-info` div added between snapshot selector and warnings
|
||
- `onRestoreAppChange()` JS updated: reads `data-has-hdd`/`data-has-db` from selected option, shows Hungarian restore type banner (full / config+DB / config only) with color-coded styling
|
||
- `router.go` `backupSnapshots`: 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-info` and `.restore-info-partial` classes added to `style.css`**
|
||
|
||
**Files 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.go` `runRsyncBackup`)**
|
||
- 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 of `backups/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 `_N` index suffix
|
||
- Loop variable changed from `_, srcMount` to `i, srcMount` to support the index-based disambiguation
|
||
- Old nested `storage/immich/` folder will remain orphaned after first run (no data loss; `--delete` only affects the target subtree)
|
||
|
||
**Fix 4: Exclude app-internal DB dump files from rsync (`internal/backup/crossdrive.go` `runRsyncBackup`)**
|
||
- 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_dump` separately — copying these again via rsync is redundant and wastes space
|
||
- Added `--exclude backups/*.sql.gz`, `--exclude backups/*.sql`, `--exclude backups/*.dump` to rsync command
|
||
- The `backups/` directory itself and non-dump files within it are preserved
|
||
|
||
**Files modified (1):** `internal/backup/crossdrive.go`
|
||
|
||
### 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 point` because `ValidateDestination()` hard-blocked non-mount-point destinations. The `/mnt/hdd_placeholder` folder 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`)**
|
||
- `onSystemDrive` flag 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 `CheckBackupDestination` UI thresholds for system drives (`internal/system/mounts_linux.go`)**
|
||
- Tier 4 disk checks now branch on `h.SystemDrive` flag (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: `SetAppBackupBulk` data 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 for `s.AppBackup`. (`internal/settings/settings.go`)
|
||
- **C2: `UpdateStackConfig` nil Env map panic** — Added nil check `if appCfg.Env == nil { appCfg.Env = make(...) }` before the field assignment loop. (`internal/stacks/deploy.go`)
|
||
- **C3: `ValidateDump` missing scanner.Err() check** — Added `if 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: `nextDailyRun` DST bug** — Replaced `next.Add(24 * time.Hour)` with `time.Date(day+1, ...)` for correct scheduling across Europe/Budapest DST transitions. (`internal/scheduler/scheduler.go`)
|
||
- **H2: `nextDailyRun` repeated `LoadLocation`** — Cached timezone in package-level `sync.Once` variable; `getBudapestLocation()` now loaded only once. (`internal/scheduler/scheduler.go`)
|
||
- **H3: `settings.save()` .tmp file leak** — Added `os.Remove(tmpPath)` cleanup on `WriteFile` failure path. (`internal/settings/settings.go`)
|
||
- **H4: `SetNotificationPrefs` nil pointer panic** — Added nil guard at start of function, returns error instead of panicking. (`internal/settings/settings.go`)
|
||
- **H5: `appDirSize` ignores `Sscanf` return value** — Now checks `n != 1` and returns `(0, "?")` on parse failure. Same fix applied to `getDirSizeBytes` in `stacks/delete.go`. (`internal/backup/appdata.go`, `internal/stacks/delete.go`)
|
||
- **H6: `getDirSizeBytes` no timeout** — Added `exec.CommandContext` with 30s timeout. Added `"context"` import. (`internal/stacks/delete.go`)
|
||
- **H7: `dbdump.go` tmpFile not using `defer Close`** — Replaced explicit `tmpFile.Close()` call with `defer tmpFile.Close()` so the file handle is released even on panic. (`internal/backup/dbdump.go`)
|
||
- **H8: `UpdateCrossDriveStatus` misleading 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`/`containsBytes` replaced** — Removed bespoke `containsBytes` and simplified `contains` to delegate to `strings.Contains`. Added `"strings"` import. (`internal/notify/notifier.go`)
|
||
- **M2: `scheduler.Every()` doesn't validate interval** — Added early return with error log if `interval <= 0` to prevent panic in `time.NewTicker`. (`internal/scheduler/scheduler.go`)
|
||
- **M3: `executeJob` panic recovery missing `LastRun`** — Panic recovery defer now also sets `job.LastRun = time.Now()` so the job status shows a timestamp after a panic. (`internal/scheduler/scheduler.go`)
|
||
- **M4: `logPostStartStatus` goroutine captures env by reference** — Copies the env slice before launching the goroutine (`envCopy`). (`internal/stacks/manager.go`)
|
||
- **M5: Multiple `time.LoadLocation` calls in web package** — Added package-level `getTimezone()` with `sync.Once` in `funcmap.go`. Replaced all `time.LoadLocation("Europe/Budapest")` calls in the web package with `getTimezone()`. (`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`
|
||
|
||
### 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.Results` mutation inside `m.mu.Lock()`. Was previously mutating shared state without the lock, causing potential torn writes visible to `GetFullStatus()` goroutines. (`internal/backup/backup.go`)
|
||
- **C2: SnapshotHistory reversed after unlock** — Moved snapshot reversal loop before `m.cachedStatus = status` (inside the lock). Previously reversed after `Unlock()`, so `m.cachedStatus.SnapshotHistory` was reversed without protection. (`internal/backup/backup.go`)
|
||
- **C3: SetStackProvider write without lock** — `m.stackProvider = provider` now wrapped in `m.mu.Lock()`. Read by `resolveAppBackupPaths()` concurrently. (`internal/backup/backup.go`)
|
||
- **C4: GetFullStatus shallow-copies mutable pointers** — `LastDBDump` and `LastBackup` are 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 & 0xff` with `unix.Major()`/`unix.Minor()` (12-bit extraction). Also compares disk-portion of minor (groups of 16) to correctly distinguish physical disks of the same type. Adds `golang.org/x/sys/unix` import. (`internal/storage/safety_linux.go`)
|
||
- **C6: No /dev/ prefix validation on DevicePath** — `FormatAndMount` now validates `DevicePath` starts 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 `TargetPath` against 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 `DestinationPath` against registered storage paths when `enabled=true`. (`internal/api/router.go`)
|
||
- **C10: Path traversal in ParseComposeHDDMounts** — `filepath.Clean()` applied before prefix check; uses separator-aware check `cleanHDD + string(filepath.Separator)` to prevent `${HDD_PATH}/../../etc/passwd` escaping. (`internal/stacks/delete.go`)
|
||
|
||
**HIGH fixes (logic errors, resource leaks):**
|
||
- **H1: ValidateDump reads entire file into memory** — Replaced `os.ReadFile` with `bufio.Scanner` reading 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 single `appDirSize()` function using `exec.CommandContext` with 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 restore` returns 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.Walk` callback now returns errors instead of `nil`, propagating permission/IO issues. (`internal/backup/crossdrive.go`)
|
||
- **H8: Non-atomic fstab write** — `AppendFstabEntry` now 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`) or `p` (partition marker). Prevents `/dev/sdb` matching `/dev/sdba`. (`internal/storage/safety_linux.go`)
|
||
- **H10: eMMC device mapping bug** — `partitionToParentDisk` now handles `mmcblk0p1 → mmcblk0` and `nvme0n1p1 → nvme0n1` patterns. Uses `LastIndex("p")` with digit-suffix check before falling back to `TrimRight("0-9")`. (`internal/storage/scan_linux.go`)
|
||
- **H11: Data race on bytesCopied in rsync error path** — Error return path in `runRsync` now reads `bytesCopied` under 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/hdd` matching `/mnt/hdd_backup/data`. (`internal/storage/migrate.go`)
|
||
- **H14: DeleteStack continues after failed compose down** — `docker compose down` failure 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 uses `exec.CommandContext` with 60s timeout. (`internal/web/handlers.go`)
|
||
- **H17: SetNotificationPrefs stores caller's pointer** — Deep-copies `NotificationPrefs` struct and `EnabledEvents` slice 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 on `mount` failure and `findmnt` verify failure. (`internal/storage/safety_linux.go`, `format_linux.go`)
|
||
|
||
**MEDIUM fixes (edge cases, code quality):**
|
||
- **M1: formatBytes duplicate in dbdump.go** — Removed `formatBytes()` from `dbdump.go`; all callers (backup.go, restic.go, dbdump.go) now use `humanizeBytes()` from appdata.go. (`internal/backup/dbdump.go`, `backup.go`, `restic.go`)
|
||
- **M2: Dead code .tmp suffix check** — Reordered filter in `ListDumpFiles`: `.tmp` check now comes before `.sql` check to correctly skip `.sql.tmp` temp files (was unreachable before). (`internal/backup/dbdump.go`)
|
||
- **M3: sizeBytes() returns 0 for string types** — Added `case string:` to `sizeBytes()` using `strconv.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 `LoadLocation` error, falling back to `time.UTC`. (`internal/web/handlers.go`)
|
||
- **M10: filterSnapshotsByPaths imprecise prefix** — Added `pathCovers()` helper using separator-aware prefix check. Prevents `/mnt/hdd_1` matching `/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 of `innerHTML` for 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`
|
||
|
||
### 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 whose `Paths` overlap with the app's HDD mount paths. Uses prefix matching (snapshot path is prefix of required, or vice versa). New `filterSnapshotsByPaths()` helper in `internal/api/router.go`. Manager gains `GetStackHDDMounts()` method to expose stackProvider's mount resolution.
|
||
- **Feature: Auto-stop/restart on restore** — `RestoreApp()` now stops the app's containers before running `restic restore` and 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) error` and `StartStack(name string) error` to the `backup.StackDataProvider` interface in `internal/backup/appdata.go`. `stackAdapter` in `cmd/controller/main.go` wires these through to `stacks.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-paths` attribute 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`
|
||
|
||
### 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`, and `CrossDriveWarnings` slices 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 || !IsWritable` check with tiered `CheckBackupDestination()` validation (new in `internal/system/mounts_linux.go` and stub in `mounts_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()` replaces `IsMountPoint()` for source/dest same-device detection. Used in both `deployHandler()` and `backupsHandler()` for display, and in `crossdrive.go` logic via `CheckBackupDestination()`.
|
||
- **Bug Fix 3: Dead BackupEnabled toggle** — Removed `settingsAppBackupHandler()` from handlers.go and its `POST /settings/app-backup` route 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 `AppBackupRow` struct and `buildAppBackupRows()` 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) and `cross-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`
|
||
|
||
### 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-drive` `margin-bottom` increased from `1rem` to `1.5rem` for 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.html` backup section (h4, warning, status, hint, stale data) and `backups.html` cross-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`
|
||
|
||
### 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** — `AppBackupPrefs` extended with `CrossDrive *CrossDriveBackup` field in `settings.go`. New methods: `GetCrossDriveConfig`, `SetCrossDriveConfig`, `UpdateCrossDriveStatus`, `GetAllCrossDriveConfigs`, `GetOrCreateCrossDrivePassword`. Existing `SetAppBackup`/`SetAppBackupBulk` now preserve cross-drive config. Auto-generated restic password stored in `settings.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-daily` at 03:30 (for apps with `schedule: daily`), `cross-drive-weekly` at 04:30 Sundays only (for `schedule: 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.5rem` added 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`
|
||
|
||
### 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()` and `generateFileBrowserCompose()` to `handlers.go`. After a storage path is added (via storage init wizard) or removed, the controller regenerates `/opt/docker/stacks/filebrowser/docker-compose.yml` with volume mounts for all registered paths (`/mnt/hdd_1:/srv/hdd_1` etc.), 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!" (red `state-red`) badge to "Rendszermeghajtón" (yellow `badge-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; }` to `style.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-color `progress-bar-task` bar. Added `.progress-bar-task` and `.progress-bar-task .progress-fill` CSS classes to `style.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`
|
||
|
||
### 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 -a` before sfdisk; changed sfdisk input from `,,,L` (unsupported GPT type shorthand) to `,,` (default Linux GUID); added `--force --wipe always` flags. Previous table confusing sfdisk and `L` type not accepted for GPT.
|
||
- **Bug 2 (mount):** Replaced `mount mountPath` (fstab lookup — uses container's /etc/fstab, not host's) with explicit `mount -t ext4 -o defaults,noatime /host-dev/sdb1 /mnt/hdd_1`. fstab entry still written to `/host-fstab` for host reboot persistence.
|
||
- **Bug 3 (mount propagation):** Changed `/mnt` volume in compose to long-form bind with `propagation: rshared`. Also ran `mount --bind /mnt /mnt && mount --make-rshared /mnt` on demo host. Confirmed `Propagation=rshared` in `docker inspect`. Mounts created inside container now propagate to host.
|
||
- **Safety 1 (post-mount verification):** Added `findmnt` check after mount — fails with clear error if mount isn't actually visible.
|
||
- **Safety 2 (ASCII label):** Use `req.MountName` (always ASCII) for ext4 `-L` label (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`
|
||
|
||
### What was just completed (2026-02-17 session 31)
|
||
- **v0.11.3 — Bugfix: Missing sfdisk in container (fdisk package):**
|
||
- `sfdisk` is in the `fdisk` package on Debian bookworm, not `util-linux`. Dockerfile had `util-linux` but not `fdisk`, so `sfdisk` was missing and partitioning failed.
|
||
- Added `fdisk` to Dockerfile's `apt-get install` list. 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 `/dev` inside containers. Even with `privileged: true`, the bind mount `- /dev:/dev` is silently dropped. Block device nodes like `/dev/sdb` don't exist inside the container.
|
||
- **Fix:** Mount host `/dev` at `/host-dev` instead. With `privileged: 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 missing `privileged: true`, `/etc/fstab:/host-fstab`, and `/run/udev:/run/udev:ro` to demo node's live compose (never applied after v0.11.0).
|
||
- **safety.go:** Added `HostDevPath = "/host-dev"` constant and `HostDevicePath(devPath) string` helper (`/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 via `HostDevicePath()`.
|
||
- **scan_linux.go:** `enrichWithBlkid()` probes each partition individually (`blkid -o value -s TYPE/UUID/LABEL /host-dev/sdXN`) instead of batch `blkid -o export` (which fails when `/dev` is Docker's minimal tmpfs).
|
||
- **Verified:** `/host-dev/sda`, `/host-dev/sdb`, partitions visible; `blkid /host-dev/sdb1` returns 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`
|
||
|
||
### 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, `lsblk` reports container mount points (e.g. `/opt/docker/felhom-controller/data`), not host mount points. New `getSystemDiskNames()` reads `/host-fstab` (fallback: `/etc/fstab`), finds system entries (`/`, `/boot`, `/boot/efi`, `swap`), resolves `UUID=` entries to device paths via `blkid -U`, and marks parent disks as system. `partitionToParentDisk()` handles both standard (`sda2→sda`) and NVMe (`nvme0n1p2→nvme0n1`) naming.
|
||
- **Bug 2 fix: FSType enrichment** — `lsblk` returns null fstype in containers (udev/blkid cache incomplete). New `enrichWithBlkid()` runs `blkid -o export` after lsblk scan and fills in missing `FSType`, `UUID`, `Label` per partition from direct device probing. Runs on both `AvailableDisks` and `SystemDisks`.
|
||
- **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`
|
||
|
||
### 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). `hubPusher` instance 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()` via `lsblk -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()` in `internal/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=progress2` progress parsing → update `app.yaml` HDD_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: true` to `docker-compose.yml`. New volume mounts: `/dev:/dev`, `/etc/fstab:/host-fstab`, `/run/udev:/run/udev:ro`. Docker socket changed from `:ro` to writable. `Dockerfile` adds: `util-linux`, `e2fsprogs`, `rsync`, `parted`.
|
||
- **Storage API routing** — New `/api/storage/` prefix registered in `main.go` before `/api/` catch-all (longer prefix takes priority in Go ServeMux). `ServeStorageAPI` method 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-info` styles.
|
||
- **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`
|
||
|
||
### 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-warn` CSS 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 in `settings.go`. New `POST /settings/storage/label` route + handler. Inline edit UI with ✏️ button, text input, OK/Cancel. Added `.btn-ghost` CSS 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. New `StorageAppDetail` struct + `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. `StorageLabels` map built from deployed apps' HDD_PATH → registered storage path label lookup. Added `.meta-badge-storage` CSS.
|
||
- **Step 5: Deploy dropdown enhancements** — Storage path dropdown now shows free space ("234 GB szabad"). `DeployStoragePath` struct wraps `StoragePath` with `FreeHuman`/`FreePercent` from `GetDiskUsage()`. JS `checkStorageSpace()` shows yellow warning when selected storage has <20% free.
|
||
- **Step 6: Filesystem & disk info** — New `FSInfo` struct + `GetFSInfo()` in `mounts_linux.go` using `findmnt` command + `/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 `StorageLabel` field to `AppBackupInfo`. Backup page shows storage label badge per app by matching HDD path prefixes against registered storage paths. Uses existing `.meta-badge-storage` CSS.
|
||
- **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`
|
||
|
||
### 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.yaml` had no `paths.hdd_path` set → `ParseComposeHDDMounts` returned 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 own `HDD_PATH` from its `app.yaml` env section (Priority 1), falling back to all registered storage paths (Priority 2). Removed dependency on global `cfg.Paths.HDDPath`.
|
||
- **Storage paths registry** (`settings.json`) — new `StoragePath` struct with Path, Label, IsDefault, Schedulable, AddedAt. Thread-safe CRUD methods in `settings.go` (Get/Add/Remove/SetDefault/SetSchedulable). Multiple external storage paths supported.
|
||
- **Auto-discovery** — On startup, `discoverHDDPaths()` scans deployed apps' `app.yaml` for `HDD_PATH` values. `AutoDiscoverStoragePaths()` registers discovered paths with inferred labels. Legacy `cfg.Paths.HDDPath` used as fallback.
|
||
- **Mount-point validation** — New `mounts_linux.go` (build-tagged): `IsMountPoint()` via `syscall.Stat_t.Dev` comparison, `IsWritable()`, `PathsOverlap()`, `GetDiskUsage()` via `syscall.Statfs`. Non-Linux stubs in `mounts_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** — `path` field 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 accepts `storagePaths` parameter. 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}:...:ro` to `/mnt:/mnt:rw` for multi-storage support + restore capability.
|
||
- **Removed unused `hddPath` param** from `DiscoverAppData()` 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`
|
||
|
||
### 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()` and `RepoStats`.
|
||
- **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 (`ResticPassword` field added to `BackupReport`).
|
||
- **App data discovery** — new `internal/backup/appdata.go`:
|
||
- `StackDataProvider` interface to avoid circular imports between backup and stacks packages
|
||
- `AppBackupInfo`, `AppDataPath`, `AppDockerVolume` structs
|
||
- `DiscoverAppData()` iterates deployed stacks, discovers HDD bind mounts (via adapter calling `ParseComposeHDDMounts`), Docker named volumes (via `parseComposeNamedVolumes` using YAML parser), and DB dump status
|
||
- Stack adapter in `main.go` implements `StackDataProvider` using `stacks.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-backup` handler saves preferences to `settings.json`
|
||
- `AppBackupPrefs` struct + bulk getter/setter in `settings.go`
|
||
- `RefreshCache()` populates `AppDataInfo` via `DiscoverAppData()`
|
||
- **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
|
||
- `BackupPaths` display on backup page includes app data paths
|
||
- **Limited app restore** — new restore section on backup page:
|
||
- `RestoreApp()` in `restore.go`: validates enabled, resolves HDD paths, validates snapshot exists, uses running mutex
|
||
- `RestoreAppData()` on `ResticManager`: runs `restic restore` with `--include` flags for specific paths
|
||
- `POST /backup/restore` web handler with confirmation flow
|
||
- `GET /api/backup/snapshots` JSON endpoint for restore dropdown
|
||
- UI: app/snapshot dropdowns, warning box, confirmation checkbox, JS-driven form submission
|
||
- **Exported `ParseComposeHDDMounts`** from stacks package (was unexported `parseComposeHDDMounts`)
|
||
- **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`
|
||
|
||
### 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/preferences` endpoint** (`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 + RecentNotifications
|
||
- `joinStrings` template function added for event list display
|
||
- `customer.html` template: new "Notifications" section showing email, events, and last 10 notification log entries (time, event, status, message)
|
||
- **Controller: `SyncPreferences` method** (`internal/notify/notifier.go`):
|
||
- New `preferencesRequest` struct for JSON payload
|
||
- `SyncPreferences(email, enabledEvents)` — synchronous POST to hub `/api/v1/preferences`
|
||
- `IsEnabled()` getter for checking hub connectivity
|
||
- Hungarian error messages for user-facing feedback
|
||
- **Controller: Sync on settings save** (`internal/web/handlers.go`):
|
||
- `settingsNotificationsHandler` now calls `SyncPreferences` after saving to `settings.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`):
|
||
- `AlertManager` struct with `Refresh()` + `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/notify` endpoint: Bearer auth, JSON payload (customer_id, event_type, severity, message, details)
|
||
- New `customer_notifications` table (email, enabled_events JSON) + `notification_log` audit table
|
||
- Resend email integration: direct HTTP POST to `https://api.resend.com/emails`
|
||
- Hungarian email template with event details, timestamp, severity
|
||
- `hub.yaml.example` updated with notifications config section
|
||
- **Controller-side notifier** (new `internal/notify/notifier.go`):
|
||
- `Notifier` struct: 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/NotifyIntegrityFailed` convenience methods
|
||
- `SendTest()` 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.json` via `NotificationPrefs` struct (Email, EnabledEvents, CooldownHours)
|
||
- **Settings persistence expanded** (`settings.go`):
|
||
- `NotificationPrefs` struct with Email, EnabledEvents, CooldownHours
|
||
- `DefaultEnabledEvents`: disk_warning, backup_failed, update_available
|
||
- `GetNotificationPrefs()` 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 via `settings.json` in the data directory. Atomic writes (tmp + rename), thread-safe with `sync.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
|
||
- **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_validations` map keyed by filename)
|
||
- Cached data survives container restarts
|
||
- `DBValidationCache` struct with `validated_at`, `table_count`, `has_header`, `error`
|
||
- **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
|
||
|
||
### 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-path` in `docker-setup.sh` now uses `require_arg` validation like all other flags. Previously, `--hdd-path` as the last argument without a value would crash with a cryptic bash error under `set -u` instead of a friendly message.
|
||
- **Bug 2:** `stackAction()` in `layout.html` now receives `event` as an explicit parameter instead of relying on the deprecated implicit `window.event`. All 10 onclick call sites in `dashboard.html` and `stacks.html` updated to pass `event` as 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()` in `funcmap.go` now returns `"ma"` (Hungarian for "today") on Sunday before 4am, consistent with the `nextRunLabel` function. Previously returned the date in `"2006-01-02"` format.
|
||
- **Deployed:** Controller v0.6.3 to demo-felhom.eu, verified healthy
|
||
|
||
### 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.Parse` with single hardcoded format silently failed for formats returned by `modernc.org/sqlite`. Added `parseSQLiteTime()` 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 all `ValidateDump()` code paths
|
||
- Re-validation: `RefreshCache()` now cross-checks `lastDBDump` results against fresh `ListDumpFiles()` 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 OK` for all 3 databases (immich: 60 tables, paperless: 67 tables, romm: 14 tables)
|
||
|
||
### 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.Request` in `deployHandler` and `appDetailHandler`
|
||
- **Fix 2:** Dashboard running/stopped counts now computed from the filtered `deployedStacks` set (was counting ALL stacks including non-deployed)
|
||
- **Fix 3:** Session cookie `Secure` flag now dynamic based on `r.TLS != nil || X-Forwarded-Proto == "https"`. `SameSite` changed from `Strict` to `Lax` (Strict breaks Cloudflare Tunnel redirects)
|
||
- **Fix 4:** Removed misleading `subtle.ConstantTimeCompare` from `isValidSession()` (map lookup already leaks timing; comparing token to itself is meaningless). Removed unused `token` field from `session` struct. Removed `crypto/subtle` import.
|
||
- **Fix 5:** Replaced `time.Tick()` (goroutine leak) with proper `time.NewTicker` + `done` channel in `cleanupSessions()`. Added `Close()` method to Server. Added `done chan struct{}` to Server struct.
|
||
- **Fix 6:** Added `http.MaxBytesReader(w, req.Body, 1<<20)` (1MB limit) to `deployStack`, `updateOptionalConfig`, `deleteStack` API handlers via `limitBody()` helper.
|
||
- **Fix 7:** Cached `time.LoadLocation("Europe/Budapest")` once at top of `templateFuncMap()`, removed 5 per-function `LoadLocation` calls (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
|
||
|
||
### 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 `heartbeat` ping — lightweight "I'm alive" signal every 5 min (no logic, just ping)
|
||
- Added `backup_integrity` ping — weekly `restic check` on Sunday 04:00, pings healthchecks with result
|
||
- Added `Heartbeat` and `BackupIntegrity` fields to `PingUUIDsConfig`
|
||
- Added `RunIntegrityCheck()` to backup Manager (calls restic Check(), updates lastCheckTime/lastCheckOK, pings)
|
||
- Updated `controller.yaml.example` with new monitoring ping_uuids
|
||
- Created `monitoring/DEPRECATED.md` for legacy bash monitoring scripts
|
||
- **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 `HubConfig` to 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)
|
||
- **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
|
||
- **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
|
||
|
||
### 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.x` unreliable across Chart.js versions. Fixed tooltip callback to use `items[0].raw.x` (direct {x,y} data access) with `parsed.x` as fallback.
|
||
- **Bug 2: Charts fill full width regardless of data density** — `setChartXBounds()` setting `min/max` at runtime was ignored because the scale was created without them. Fixed by including `min: now - defaultRangeMs, max: now` in the initial `chartOpts()` 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-grid` used `auto-fill` creating variable-width cells. Fixed to `1fr 1fr` (fixed 2-column). Added `align-items: baseline`, `gap: 1rem`, `white-space: nowrap` on labels, `font-weight: 600` + `word-break: break-word` on values. Removed redundant `<style>` block from monitoring.html (styles now in style.css).
|
||
- **Bug 4: Charts overflow on mobile** — Added `min-width: 0` on `.chart-box` (critical CSS grid fix), `overflow: hidden` + `max-width: 100%` on `.chart-wrap` and `.chart-wrap-bar`, `max-width: 100%` on canvas.
|
||
- **Controller version:** v0.5.4 — deployed and verified on demo-felhom.eu
|
||
|
||
### 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:ro` and reading it first in `sysinfo.go`. Now shows `demo-felhom`.
|
||
- **Bug 2: Tooltip timestamps** — Chart.js tooltip callback used `items[0].parsed.x` (category index 0,1,2...) instead of `items[0].label` (actual timestamp). Index 0 worked by accident (`0 || label` falls through), but all other points showed 1970-01-01.
|
||
- **Bug 3+4: Default range + empty charts** — Default range was `24h` but new system had only minutes of data. Changed to `1h` default for both system and container detail charts. Moved `active` class to "1 óra" button.
|
||
- **Controller version:** v0.5.1 — deployed and verified on demo-felhom.eu
|
||
|
||
### 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 synthesizes `LastBackup` from `SnapshotHistory` and `LastDBDump` from `DumpFiles` on disk when the in-memory values are nil (e.g., after controller restart). Dashboard handler also updated to use `GetFullStatus()` instead of `GetStatus()` for consistent behavior.
|
||
- **Task 2: Verified backup page caching** — Already implemented in v0.4.7 (`RefreshCache`, scheduler job, `AfterBackup` callback). 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 via `modernc.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-based `GROUP BY` for 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 from `system.GetInfo()`, container data from `docker stats --no-stream` with tab-separated format parsing.
|
||
- **System info provider** (`internal/metrics/sysinfo.go`, `sysinfo_other.go`): Reads hostname, OS, kernel, CPU model/cores, uptime from `/proc` filesystem. 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:ro` volume mount in docker-compose.yml, Dockerfile updated to `golang:1.24-bookworm` (required by `modernc.org/sqlite`), `go.mod` upgraded to `go 1.24.0`.
|
||
- **Controller version:** v0.5.0 — deployed and verified on demo-felhom.eu (metrics collecting, 16 containers reporting, sysinfo showing Intel N100 correctly)
|
||
|
||
### What was previously completed (2026-02-16 session 14)
|
||
- **v0.4.7 — Protected Stack Detail Pages + Backup Page Caching:**
|
||
- **Protected stacks clickable** — `data-href` gating changed from `{{if not .Protected}}` to `{{if .Meta.Slug}}` on both `stacks.html` and `dashboard.html`. Protected stacks with `.felhom.yml` (i.e. a slug) are now clickable, linking to `/apps/{slug}`. Stacks without `.felhom.yml` remain non-clickable.
|
||
- **"Részletek" button for protected stacks** — Protected stack action section in `stacks.html` now shows a "Részletek" link when the stack has a slug, next to the restart button.
|
||
- **FileBrowser `.felhom.yml` resources** — Added `resources` section (mem_request: 128M, mem_limit: 256M, pi_compatible: true, needs_hdd: true) to both `install_filebrowser()` in `docker-setup.sh` and 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 new `RefreshCache()` method runs these in the background:
|
||
- Every 5 minutes via `backup-cache` scheduler job
|
||
- After each successful backup via `AfterBackup` callback
|
||
- On startup via a goroutine (non-blocking)
|
||
- `GetFullStatus()` returns the cached `FullBackupStatus` instantly, 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
|
||
|
||
### 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`, `-- mysqldump` for MariaDB and `-- PostgreSQL database dump` for 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". `TotalCount` stat 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 except `restart` on protected stacks (HTTP 403). Defense-in-depth: `StopStack()` and `DeleteStack()` retain their own guards.
|
||
- **FileBrowser `.felhom.yml`** — `install_filebrowser()` in `docker-setup.sh` now creates `.felhom.yml` with `subdomain: files` metadata, so the controller shows the `files.DOMAIN ↗` URL link. Manually created on demo node.
|
||
- **Controller version:** v0.4.6 — deployed and verified on demo-felhom.eu
|
||
|
||
### What was previously completed (2026-02-16 session 12)
|
||
- **v0.4.5 — Dedicated Backup Page ("Biztonsági mentés"):**
|
||
- **New `/backups` page** with full backup system visibility — 5 sections:
|
||
1. **Status overview cards**: Local backup status (green/gray), remote placeholder (gray), DB count, repo size
|
||
2. **Schedule section**: DB dump/restic/prune schedule with next-run times, last backup time + duration, retention policy, "Mentés most" button
|
||
3. **Database table**: Lists all discovered DBs with type badge (PostgreSQL/MariaDB), dump file size, last dump time, validation (table count), status
|
||
4. **Snapshot history table**: Last 20 snapshots with ID, time, data added, files new/changed
|
||
5. **Repository info card**: Path, size, snapshot count, integrity check status, backed-up paths list, remote copy placeholder
|
||
- **Backend extensions:**
|
||
- `SnapshotRecord` type + ring buffer (20 entries) in Manager for per-snapshot stats
|
||
- `DumpValidation` — scans dump files for CREATE TABLE statements, validates header and file size
|
||
- `ValidateDump()` runs after each successful dump in `DumpOne()`
|
||
- `ListDumpFiles()` scans dump directory for existing `.sql` files (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 needs
|
||
- `LoadSnapshotHistory()` 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:**
|
||
- `Server` struct now holds `*scheduler.Scheduler`
|
||
- `NewServer()` accepts scheduler parameter
|
||
- `/backups` route + `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/status` every 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)
|
||
|
||
### 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=running` via `history.replaceState`. Reads filter from URL on page load for deep-linking support.
|
||
- **New `filterCategory` template function** (`funcmap.go`): Maps container state + deployed flag to filter categories (running/stopped/available). Each stack card gets a `data-filter-state` attribute for client-side filtering.
|
||
- **Clickable dashboard stat cards**: Stat cards (Futó/Leállítva/Összes) changed from `<div>` to `<a>` with `href` linking to `/stacks?filter=running`, `/stacks?filter=stopped`, `/stacks` respectively. 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 correct `felhom.${DOMAIN}` label + `/sys:/host/sys:ro` mount.
|
||
- **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.eu` resolves correctly (Cloudflare Tunnel public hostname may need updating from `dashboard.*` to `felhom.*`)
|
||
- Update Pi-hole local DNS if applicable
|
||
- Enable backup in `controller.yaml` on demo node (`backup.enabled: true`)
|
||
- Create `/srv/backups` directories on demo node
|
||
|
||
### 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/Budapest` timezone with `time.Timer` for DST correctness
|
||
- Graceful shutdown with 30s timeout for running jobs
|
||
- **CPU usage collector** (`internal/system/cpu_linux.go`):
|
||
- Background goroutine samples `/proc/stat` every 5s, computes delta-based CPU %
|
||
- Platform stubs for non-Linux in `cpu_other.go`
|
||
- **Temperature & load metrics** (`internal/system/info_linux.go`):
|
||
- Reads `/proc/loadavg` for 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
|
||
- **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-dump` with 5min timeout
|
||
- Atomic writes (`.tmp` → `.sql`), empty file detection, stale temp cleanup
|
||
- **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 init
|
||
- `api/router.go`: `GET /api/backup/status`, `POST /api/backup/run`
|
||
- `web/server.go` + `handlers.go`: pass cpuCollector to GetInfo(), backup status on dashboard
|
||
- `funcmap.go`: `tempColor`, `fmtTemp`, `fmtLoad` template 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`: added `system_health_interval`, `hdd_path`, `system.reserved_memory_mb`
|
||
- `docker-compose.yml`: added `/sys:/host/sys:ro` mount for temperature reading
|
||
- `restic_password_file` default changed to `data/` subdir (auto-generated in named volume)
|
||
- **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/`. Created `embed.go` with `//go:embed` directive. Template loading now uses `ParseFS()` instead of `Parse()`. CSS served from embed.FS via `ReadFile()`. 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 management
|
||
- `handlers.go`: page handlers (dashboard, stacks, logs, deploy, app detail)
|
||
- `funcmap.go`: template FuncMap with 14 custom functions
|
||
- `server.go`: Server struct, NewServer, loadTemplates (3-liner), ServeHTTP routing, render helper, static file serving
|
||
- **Domain rename** — controller subdomain changed from `dashboard.*` to `felhom.*` 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
|
||
- **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 with `Dokumentumok` user dir
|
||
- Created `scripts/docker-setup.sh` (adapted from deploy-portainer) — installs Docker, Traefik, FileBrowser as infra services
|
||
- Added `filebrowser` to protected stacks in `controller.yaml.example`
|
||
- Removed `templates/filebrowser/` from app-catalog-felhom.eu (no longer a catalog app)
|
||
- **Orphan stack detection and deletion:**
|
||
- Added `Orphaned` field 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}` and `GET /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
|
||
- **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_text` for 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)
|
||
- **Added error logging to `LoadMetadata()`** in `metadata.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**:
|
||
- `sed` pattern now targets only `image:` lines (was matching service name too, breaking YAML)
|
||
- Added `sudo` for 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-logo` CSS in `templates.go`:
|
||
- Added `min-width`, `min-height`, `max-width`, `max-height: 80px` and `overflow: hidden`
|
||
- Prevents SVG images with explicit dimensions or no viewBox from overflowing container
|
||
- Logo now reliably renders at 80x80 regardless of SVG intrinsic size
|
||
- **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 `onerror` if 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.yml` fields: `app_info` (tagline, use_cases, first_steps, prerequisites, default_creds, docs_url) and `optional_config` (groups of env var fields)
|
||
- New structs in `metadata.go`: `AppInfo`, `OptionalConfigGroup`, `OptionalConfigField`
|
||
- `UpdateOptionalConfig` in `deploy.go`: saves optional env vars to `app.yaml`, restarts deployed stacks with `docker compose up -d` to 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
|
||
- **RoMM metadata updated** (app-catalog repo):
|
||
- Full `app_info` section: 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"
|
||
- **Controller version:** v0.2.11
|
||
|
||
### Previously completed (2026-02-14 session 4)
|
||
- **Fixed deploy race condition** in `internal/stacks/deploy.go`:
|
||
- In-memory `Deployed` flag now set BEFORE `docker 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
|
||
- **Added `checkBeforeDeploy()` JS guard** in `internal/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
|
||
|
||
### 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.go` and `server.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-href` attribute + 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=1` returns 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 --spider` replaced with Python TCP socket check — mealie image doesn't include wget
|
||
- `start_period` increased 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.yml` to stacks dir (never overwrites `app.yaml`/`.env`)
|
||
- SHA-256 content comparison — only writes changed files
|
||
- Triggers `ScanStacks()` after sync so dashboard updates immediately
|
||
- Uses `os/exec` git CLI — no Go git library dependency
|
||
- **Manual sync button** ("Sablonok frissítése") on Alkalmazások page:
|
||
- `POST /api/sync` endpoint 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.memory` to all services across all templates
|
||
- Added `TZ=Europe/Budapest` to all sidecar services (postgres, redis, mariadb)
|
||
- Added healthcheck to romm main service
|
||
- Added `romm-redis` `condition: service_healthy` (was `service_started`)
|
||
- Standardized header comment blocks across all templates
|
||
- **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_request` sum against usable system RAM
|
||
- Hard block if requests exceed usable memory (total - 384MB reserved)
|
||
- Soft warning if `mem_limit` sum exceeds total RAM (overcommit OK for limits)
|
||
- `ParseMemoryMB()` supports "500M", "1G", "1.5G", "1024" formats
|
||
- `CommittedMemory()` sums requests/limits across all deployed stacks
|
||
- Memory summary bar shown on deploy page before user clicks deploy
|
||
- `system.reserved_memory_mb` configurable in controller.yaml (default: 384)
|
||
- **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`
|
||
|
||
### 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/system` package 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_limit` field to `.felhom.yml` ResourceHints (total: 1152M)
|
||
- **`/api/system/info` endpoint** now returns live system metrics (was customer info)
|
||
- **Config**: Added `paths.hdd_path` for 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:
|
||
1. Password validation (empty passwords accepted)
|
||
2. In-memory Deployed flag not updating after deploy
|
||
3. Health-aware state parsing (starting/unhealthy detection)
|
||
4. Random card ordering (Go map iteration)
|
||
5. "Részletek" button redirect for deployed apps
|
||
6. Paperless OCR language installation (LANGUAGES vs LANGUAGE env var)
|
||
7. Documentation: restart vs up -d for image updates
|
||
|
||
### What's next (priorities)
|
||
1. **Test per-app backup** — enable backup for Paperless-ngx HDD data, trigger manual backup, verify restic snapshot includes HDD paths
|
||
2. **Test restore** — restore app data from snapshot, verify file recovery (now possible with /mnt:rw mount)
|
||
3. **Deploy Immich** — tests HDD path + secrets + multi-storage (biggest real-world test)
|
||
4. Add `app_info` + `optional_config` to more apps (Immich, Mealie, Vaultwarden)
|
||
5. Test on Raspberry Pi (pi-customer-1)
|
||
6. Self-update mechanism
|
||
7. Hub alerting (webhook to Healthchecks for stale customers)
|
||
8. Docker volume backup (mount `/var/lib/docker/volumes:ro` into controller) |