From 24a21505201601801d557550b02d1041c4c9e77f Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 16 Feb 2026 08:40:29 +0100 Subject: [PATCH] v0.4.6: MariaDB Validation Fix + Dashboard & Protected Stack UX --- TASK.md | 767 +++++++++++++++++--------------------------------------- 1 file changed, 226 insertions(+), 541 deletions(-) diff --git a/TASK.md b/TASK.md index 62113af..9029123 100644 --- a/TASK.md +++ b/TASK.md @@ -1,616 +1,301 @@ -# TASK.md — v0.4.5: Dedicated Backup Page ("Biztonsági mentés") +# TASK.md — v0.4.6: MariaDB Validation Fix + Dashboard & Protected Stack UX -> Version bump: **v0.4.5** -> Scope: New page + backend extensions for detailed backup visibility +> Version bump: **v0.4.6** +> Scope: Bug fix + 2 UI improvements --- ## Overview -Add a third page to the controller dashboard: **Biztonsági mentés** (Backup), accessible from the sidebar navigation alongside Vezérlőpult and Alkalmazások. +Three items: -The page gives full visibility into the backup system — what's being backed up, when, where, and whether it's healthy. Designed so a non-technical customer can see "everything is green" at a glance, while Viktor (or a future admin panel) gets the details needed for troubleshooting. +1. **Bugfix**: MariaDB dump validation false positive — header check fails because MariaDB 11.4+ prepends a sandbox comment before the dump header +2. **UI**: Dashboard should only show deployed apps (not 47 "Nincs telepítve" entries) +3. **UI**: Protected stacks (especially FileBrowser) should show subdomain URL + allow restart --- -## Page Layout +## Task 1: Fix MariaDB dump validation false positive -### Section 1: Status Overview (top of page) +### Root cause -A row of stat cards (same style as dashboard), showing at-a-glance backup health: +MariaDB 11.4+ prepends a sandbox directive before the header comment: -``` -┌────────────────┐ ┌────────────────┐ ┌─────────────────┐ ┌───────────────┐ -│ ✅ Helyi │ │ ⬜ Távoli │ │ 3 │ │ 87.5 MB │ -│ mentés aktív │ │ nincs beállítva │ │ adatbázis mentve│ │ tároló méret │ -└────────────────┘ └────────────────┘ └─────────────────┘ └───────────────┘ +```sql +/*M!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19-11.4.10-MariaDB, for debian-linux-gnu (x86_64) ``` -- **Helyi mentés** (Local backup): green if last backup < 36h old and successful, yellow if > 36h, red if failed, gray if disabled -- **Távoli mentés** (Remote backup): gray "nincs beállítva" for now (Phase 3 v1 is local-only). Placeholder for future B2/S3/SFTP offsite. When implemented: green/red status like local. -- **Adatbázis mentve** (Databases backed up): count of successfully dumped DBs from last run -- **Tároló méret** (Repository size): total restic repo size with snapshot count +The validation code checks only the first line for `-- MariaDB dump` or `-- MySQL dump`, but line 1 is now `/*M!999999...*/`. So the header check fails with "MariaDB dump missing comment header". -### Section 2: Ütemezés (Schedule) +### Fix -A compact info card showing backup timing: +In `ValidateDump()` (file: `internal/backup/dbdump.go`), change the header check to scan the **first 10 lines** (not just line 1) for the expected pattern. Also accept `/*` and `/*!` lines as valid preamble. -``` -╔══════════════════════════════════════════════════════╗ -║ Ütemezés ║ -╠══════════════════════════════════════════════════════╣ -║ ║ -║ Adatbázis mentés 02:30 Következő: ma 02:30 ║ -║ Restic pillanatkép 03:00 Következő: ma 03:00 ║ -║ Karbantartás vasárnap Következő: 2026-02-22 ║ -║ ║ -║ Utolsó sikeres mentés: 2026-02-16 03:01 (15 órája) ║ -║ Mentés időtartam: 12s ║ -║ ║ -║ Megőrzés: 7 napi · 4 heti · 6 havi ║ -║ ║ -║ [Mentés most] ║ -╚══════════════════════════════════════════════════════╝ -``` +For MariaDB/MySQL dumps, valid header patterns (any of these in the first 10 lines): +- `-- MariaDB dump` +- `-- MySQL dump` +- `-- mysqldump` -Hungarian labels: -- "Ütemezés" = Schedule -- "Adatbázis mentés" = Database backup -- "Restic pillanatkép" = Restic snapshot -- "Karbantartás" = Maintenance (prune) -- "Következő" = Next -- "ma" = today, "holnap" = tomorrow -- "Utolsó sikeres mentés" = Last successful backup -- "órája" = hours ago -- "Mentés időtartam" = Backup duration -- "Megőrzés" = Retention -- "napi" = daily, "heti" = weekly, "havi" = monthly -- "Mentés most" = Backup now +For PostgreSQL dumps, valid header patterns: +- `-- PostgreSQL database dump` -The "Mentés most" button triggers `POST /api/backup/run` (same as dashboard card). Show spinner/loading state while running. The button should be disabled if a backup is already in progress. +### Implementation -### Section 3: Adatbázisok (Databases) - -A table listing every discovered database container and its dump status: - -``` -╔══════════════════════════════════════════════════════════════════════╗ -║ Adatbázisok ║ -╠══════════════════════════════════════════════════════════════════════╣ -║ ║ -║ Alkalmazás Típus Méret Utolsó Állapot ║ -║ ─────────────────────────────────────────────────────────────────── ║ -║ paperless-ngx PostgreSQL 4.2 MB 03:01 ✅ OK ║ -║ immich PostgreSQL 82.1 MB 03:01 ✅ OK ║ -║ romm MariaDB 1.2 MB 03:01 ✅ OK ║ -║ ║ -╚══════════════════════════════════════════════════════════════════════╝ -``` - -Table columns: -- **Alkalmazás** (Application): stack name (derived from container name) -- **Típus** (Type): PostgreSQL or MariaDB, with a small icon/badge -- **Méret** (Size): dump file size from last run -- **Utolsó** (Last): time of last dump (HH:MM format if today, date if older) -- **Állapot** (Status): ✅ OK / ❌ Hiba (error) / ⏳ Folyamatban (in progress) / ➖ Nem futott (never run) - -If a dump failed, show the error message in a tooltip or expandable row detail. - -**Érvényesítés (Validation) column** — NEW FEATURE (see Section 6 below for backend details): - -Add a column showing whether the dump file passed basic structural validation: - -``` -║ Érvényesítés ║ -║ ✅ 47 tábla ║ ← "47 tables" found in the dump -║ ✅ 123 tábla ║ -║ ✅ 12 tábla ║ -``` - -### Section 4: Pillanatképek (Snapshots) - -A table showing restic snapshot history (last 10-20 snapshots): - -``` -╔══════════════════════════════════════════════════════════════════════╗ -║ Pillanatképek ║ -╠══════════════════════════════════════════════════════════════════════╣ -║ ║ -║ Azonosító Időpont Méret Új fájl Változott ║ -║ ─────────────────────────────────────────────────────────────────── ║ -║ a3f2c91e 2026-02-16 03:01 +1.2 MB 3 2 ║ -║ 8bc4d312 2026-02-15 03:01 +82.5 MB 156 0 ║ ← first snapshot -║ ║ -║ Összesen: 2 pillanatkép · 87.5 MB ║ -╚══════════════════════════════════════════════════════════════════════╝ -``` - -Table columns: -- **Azonosító** (ID): short snapshot ID (8 chars) -- **Időpont** (Time): snapshot timestamp -- **Méret** (Size): data added in this snapshot ("+1.2 MB") -- **Új fájl** (New files): files_new count -- **Változott** (Changed): files_changed count - -Footer: total snapshot count and total repo size. - -### Section 5: Tároló (Repository) - -A compact info card about the restic repository: - -``` -╔══════════════════════════════════════════════════════╗ -║ Tároló ║ -╠══════════════════════════════════════════════════════╣ -║ ║ -║ Helyszín: /srv/backups/restic-repo (helyi) ║ -║ Méret: 87.5 MB ║ -║ Pillanatképek: 2 ║ -║ Integritás: ✅ Rendben (utolsó: 2026-02-16) ║ -║ ║ -║ Mentett útvonalak: ║ -║ • /opt/docker/stacks/ ║ -║ • /srv/backups/db-dumps/ ║ -║ • /opt/docker/felhom-controller/controller.yaml ║ -║ ║ -║ Távoli másolat: ║ -║ ⬜ Nincs beállítva ║ -║ (B2/S3/SFTP támogatás hamarosan) ║ -║ ║ -╚══════════════════════════════════════════════════════╝ -``` - -Hungarian labels: -- "Tároló" = Repository/Storage -- "Helyszín" = Location -- "helyi" = local -- "Integritás" = Integrity -- "Rendben" = OK -- "Mentett útvonalak" = Backed up paths -- "Távoli másolat" = Remote copy -- "Nincs beállítva" = Not configured -- "hamarosan" = coming soon - -### Section 6: Backup Not Configured State - -If `backup.enabled` is `false`, the entire page shows a friendly empty state: - -``` -╔══════════════════════════════════════════════════════╗ -║ ║ -║ 🛡️ Biztonsági mentés nincs beállítva ║ -║ ║ -║ A biztonsági mentés funkció nem aktív. ║ -║ Kérjük, vegye fel a kapcsolatot a Felhom ║ -║ csapattal a beállításhoz. ║ -║ ║ -╚══════════════════════════════════════════════════════╝ -``` - ---- - -## Backend Changes - -### New: Snapshot History (`ResticManager`) - -Add method to list all snapshots (not just latest): +Find the header validation logic in `ValidateDump()`. Replace the "first line must be" check with a loop over the first 10 lines: ```go -// ListSnapshots returns all snapshots, newest first. -func (r *ResticManager) ListSnapshots(limit int) ([]SnapshotInfo, error) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - defer cancel() - - cmd := r.command(ctx, "snapshots", "--json") - out, err := cmd.Output() - // ... parse JSON array, reverse for newest-first, limit to N -} -``` - -Extend `SnapshotInfo` with data-added fields if possible (restic's `snapshots --json` includes some stats). - -Note: restic's `snapshots --json` returns basic info (ID, time, paths, tags, hostname). To get per-snapshot size/files data, we'd need `restic diff` between snapshots — this is expensive. Instead, **store per-snapshot stats from the backup run** in the Manager's in-memory history. - -### New: Backup History (in-memory ring buffer) - -The `Manager` currently stores only `lastDBDump` and `lastBackup`. Extend to keep a history: - -```go -type Manager struct { - // ... existing fields ... - - mu sync.Mutex - lastDBDump *DBDumpStatus - lastBackup *BackupStatus - running bool - snapshotHistory []SnapshotRecord // NEW: ring buffer, last 20 entries -} - -// SnapshotRecord combines restic snapshot metadata with our run stats. -type SnapshotRecord struct { - SnapshotID string - Time time.Time - FilesNew int - FilesChanged int - DataAdded string - Duration time.Duration - Success bool - DBDumpCount int // how many DBs were dumped in this run - DBDumpSize int64 // total DB dump size -} -``` - -After each successful backup run, append to `snapshotHistory`. Cap at 20 entries (oldest dropped). This gives us per-snapshot stats without expensive `restic diff` calls. - -On startup, populate `snapshotHistory` from `restic snapshots --json` (we'll have IDs and times but NOT the per-snapshot file/size delta — mark those as "n/a" for historical entries). Only new entries from the running controller will have full stats. - -### New: DB Dump Validation - -After each dump, perform lightweight validation: - -```go -// ValidateDump checks a SQL dump file for basic structural integrity. -type DumpValidation struct { - Valid bool - TableCount int // number of CREATE TABLE / CREATE TABLE IF NOT EXISTS statements - Error string // validation error message, if any - FileSize int64 - ModTime time.Time -} - -func ValidateDump(filePath string, dbType DBType) DumpValidation -``` - -Validation logic: -- **PostgreSQL dumps**: Scan file for `CREATE TABLE` statements (count them). Check file starts with `--` (comment header). Check file ends with content (not truncated — look for final `\\.` or `--`). Check file size > 100 bytes. -- **MariaDB dumps**: Scan for `CREATE TABLE` statements. Check starts with `-- MySQL dump` or `-- MariaDB dump`. Check file size > 100 bytes. -- This is a fast file scan (read and grep), NOT a full SQL parse. - -Add `Validation` field to `DumpResult`: - -```go -type DumpResult struct { - DB DiscoveredDB - FilePath string - Size int64 - Duration time.Duration - Error error - Validation DumpValidation // NEW -} -``` - -Run validation after each successful dump in `DumpOne()`. - -### New: Dump File Listing from Disk - -For the DB table, also scan the dump directory for existing files (even if the controller restarted and has no in-memory results): - -```go -// ListDumpFiles returns info about SQL dump files on disk. -func ListDumpFiles(dumpDir string) ([]DumpFileInfo, error) - -type DumpFileInfo struct { - FileName string - StackName string // parsed from filename: "paperless-ngx-postgres.sql" → "paperless-ngx" - DBType DBType // parsed from filename: "...-postgres.sql" → postgres - Size int64 - ModTime time.Time -} -``` - -This provides a fallback for the DB table even when in-memory DumpResult is empty (e.g., after container restart before first backup run). - -### New: Restic Check Status Tracking - -Track the last `restic check` result: - -```go -type Manager struct { - // ... existing ... - lastCheckTime time.Time - lastCheckOK bool -} -``` - -`restic check` already runs during weekly prune. Store the result. Expose via `GetCheckStatus()`. - -### Extended: `GetStatus()` → `GetFullStatus()` - -New method that returns everything the backup page needs in one call: - -```go -type FullBackupStatus struct { - Enabled bool - Running bool - - // DB Dumps - LastDBDump *DBDumpStatus - DumpFiles []DumpFileInfo // from disk scan - DiscoveredDBs []DiscoveredDB // currently running DB containers - - // Restic - LastBackup *BackupStatus - SnapshotHistory []SnapshotRecord // last 20 runs - RepoStats *RepoStats - - // Schedule - DBDumpSchedule string // "02:30" - ResticSchedule string // "03:00" - PruneSchedule string // "sunday" - NextDBDump time.Time // from scheduler - NextBackup time.Time // from scheduler - Retention config.RetentionConfig - - // Repository health - RepoPath string - LastCheckTime time.Time - LastCheckOK bool - - // Remote (placeholder for future) - RemoteEnabled bool - RemoteType string // "b2", "s3", "sftp" — empty for now -} - -func (m *Manager) GetFullStatus(sched *scheduler.Scheduler) *FullBackupStatus -``` - -### Extended API: `GET /api/backup/status` - -Update the existing endpoint to return the full status when a `?detail=true` query param is provided (backward compatible). Or just always return full data — the dashboard card only uses what it needs. - -Add new endpoints: - -``` -GET /api/backup/snapshots → snapshot history list -GET /api/backup/databases → discovered DBs + dump file listing -``` - -Or keep it simple: one expanded `GET /api/backup/status` returns everything the page needs. - ---- - -## Routing & Navigation - -### Sidebar nav (layout.html) - -Add third nav item: - -```html - -``` - -### Web route (server.go ServeHTTP) - -Add case: -```go -case path == "/backups": - s.backupsHandler(w, r) -``` - -### Handler (handlers.go) - -```go -func (s *Server) backupsHandler(w http.ResponseWriter, _ *http.Request) { - data := s.baseData("backups", "Biztonsági mentés") - - if s.backupMgr != nil { - fullStatus := s.backupMgr.GetFullStatus(s.scheduler) - data["Backup"] = fullStatus - } else { - data["Backup"] = nil // template checks: {{if .Backup}} +// Check header — scan first 10 lines for expected dump header +scanner := bufio.NewScanner(file) +headerFound := false +linesChecked := 0 +for scanner.Scan() && linesChecked < 10 { + line := scanner.Text() + linesChecked++ + switch dbType { + case DBTypeMariaDB: + if strings.HasPrefix(line, "-- MariaDB dump") || + strings.HasPrefix(line, "-- MySQL dump") || + strings.HasPrefix(line, "-- mysqldump") { + headerFound = true + } + case DBTypePostgres: + if strings.HasPrefix(line, "-- PostgreSQL database dump") { + headerFound = true + } + } + if headerFound { + break } - - s.render(w, "backups", data) } ``` -### Server struct changes +### Verification -The `Server` needs access to the scheduler for next-run times: - -```go -type Server struct { - cfg *config.Config - stackMgr *stacks.Manager - cpuCollector *system.CPUCollector - backupMgr *backup.Manager - scheduler *scheduler.Scheduler // NEW - // ... -} -``` - -Pass scheduler to `NewServer()` from `main.go`. +After deploying, trigger a manual backup ("Mentés most") and check the Adatbázisok table on the backup page. The romm MariaDB entry should show a green validation badge with table count instead of "Hiba". --- -## Dashboard card update +## Task 2: Dashboard — show only deployed apps -The existing "Biztonsági mentés" card on the Vezérlőpult page stays, but make it clickable — clicking navigates to `/backups`: +### Current behavior + +The "Alkalmazások állapota" section on the Vezérlőpult page shows **all** apps (deployed + not deployed), meaning 47+ "Nincs telepítve / Telepítés" entries clutter the dashboard. + +### New behavior + +Only show **deployed** apps (including protected infra stacks) on the dashboard. Non-deployed apps remain accessible via the Alkalmazások page. + +### Implementation + +In `dashboardHandler()` (`internal/web/handlers.go`), filter the stack list before passing to the template: + +```go +// Filter to deployed-only for dashboard +stackList := s.stackMgr.GetStacks() +var deployedStacks []stacks.Stack +for _, st := range stackList { + if st.Deployed || st.Protected { + deployedStacks = append(deployedStacks, st) + } +} +``` + +Pass `deployedStacks` as `data["Stacks"]` to the template instead of the full `stackList`. + +**Important**: The `TotalCount` stat card should still show the total count of all apps (deployed + not deployed), not just the filtered list. Keep using `len(stackList)` for that. + +### Template change + +In `dashboard.html`, update the section heading: ```html - - ...existing content... - +

Telepített alkalmazások

``` +("Telepített alkalmazások" = "Installed applications") + +No other template changes needed — the `{{range .Stacks}}` loop just iterates over fewer items. + +### Edge case + +If no user apps are deployed yet (fresh install), the section still shows protected infra stacks (traefik, cloudflared, felhom-controller, filebrowser) — this is reassuring to the customer. + --- -## Template file +## Task 3: FileBrowser — show URL + allow restart on protected stacks -Create `internal/web/templates/backups.html`: +### Current behavior -The template uses the `{{.Backup}}` data (type `*FullBackupStatus`). +Protected stacks show only "Védett rendszerkomponens" badge with no actions or URL link, on both the dashboard compact list and the Alkalmazások detail cards. -Structure: -``` -{{define "backups"}} -{{template "layout_start" .}} +### New behavior - +For protected stacks that are **operational** (running): +- Show the subdomain URL link if configured (e.g., `files.demo-felhom.eu ↗`) +- Show "Újraindítás" (Restart) button +- Keep "Védett" badge +- NO stop/start/delete/update buttons (still protected from destructive actions) -{{if not .Backup}} - -{{else}} - - - - - -{{end}} +### The subdomain problem -{{template "layout_end" .}} -{{end}} +FileBrowser is deployed by `docker-setup.sh` as infrastructure — it may NOT have a `.felhom.yml` metadata file with the `subdomain` field set. Without it, the controller doesn't know FileBrowser's subdomain. + +**Solution**: Add `.felhom.yml` creation to the `install_filebrowser()` function in `scripts/docker-setup.sh`. + +For the demo node, create it manually after this deploy: + +```bash +sudo tee /opt/docker/stacks/filebrowser/.felhom.yml << 'EOF' +display_name: Filebrowser +slug: filebrowser +description: Fájlkezelő a külső merevlemezhez +subdomain: files +category: storage +app_info: + tagline: Web-alapú fájlkezelő a külső merevlemezhez + use_cases: + - Fájlok böngészése és letöltése a külső HDD-ről + - Médiafájlok megosztása családtagokkal + - Dokumentumok feltöltése és kezelése + first_steps: + - Nyisd meg a files.DOMAIN címet a böngészőben + - Jelentkezz be az admin fiókkal + - Tallózd a /srv mappákat + prerequisites: + - Külső HDD csatlakoztatva és felcsatolva +EOF ``` -### Auto-refresh +### Template changes — stacks.html (Alkalmazások page) -The page should auto-refresh backup status while a backup is running. Use the same pattern as the deploy page — poll `/api/backup/status` every 3 seconds when `running: true`, update the UI elements. +Update the protected stack actions section in the detail card: -When a manual backup is triggered via "Mentés most", show inline progress: -1. Button changes to "Mentés folyamatban..." with loading animation -2. Status cards update as backup progresses -3. When complete, full page data refreshes - ---- - -## CSS additions - -New styles needed in `style.css`: - -- `.backup-page-cards` — the overview stat cards row (reuse `.stats-grid` layout) -- `.schedule-card` — schedule info card (similar to `.system-info-card`) -- `.schedule-row` — individual schedule line -- `.db-table` — database dump table (dark theme table) -- `.db-type-badge` — PostgreSQL/MariaDB badge (colored pill) -- `.snapshot-table` — snapshot history table -- `.repo-card` — repository info card -- `.backup-empty-state` — empty state when backup not configured -- `.validation-badge` — green/red validation status badge -- `.relative-time` — muted color for "15 órája" type text - -Table styling should match the dark theme. Alternating row backgrounds for readability: -```css -.db-table tr:nth-child(even) { background: var(--bg-secondary); } -.db-table tr:nth-child(odd) { background: var(--bg-card); } +```html +{{if .Protected}} + Védett rendszerkomponens + {{if isOperational .State}} + + {{end}} +{{else if not .Deployed}} + ...existing deploy button... ``` ---- +The subdomain URL is already shown above the actions for all stacks — it uses `{{if .Meta.Subdomain}}`. If `.felhom.yml` has `subdomain: files`, it should render automatically. Verify this is not gated behind `{{if not .Protected}}` — if it is, remove that guard. -## Implementation Order +### Template changes — dashboard.html (Vezérlőpult page) -### Step 1: Backend data structures -1. Add `SnapshotRecord`, `DumpFileInfo`, `DumpValidation`, `FullBackupStatus` types to backup package -2. Add `snapshotHistory` ring buffer to `Manager` -3. Add `ValidateDump()` function to `dbdump.go` -4. Add `ListDumpFiles()` function to `dbdump.go` -5. Add `ListSnapshots()` method to `ResticManager` -6. Add `GetFullStatus()` method to `Manager` -7. Track restic check status in Manager +In the compact stack list, update the protected stack section similarly: -### Step 2: Integrate validation into dump flow -1. Call `ValidateDump()` after each successful dump in `DumpOne()` -2. Store validation result in `DumpResult` -3. Append `SnapshotRecord` to history after each backup run +```html +{{if .Protected}} + Védett + {{if isOperational .State}} + + {{end}} +{{else if not .Deployed}} +``` -### Step 3: Server wiring -1. Pass `*scheduler.Scheduler` to `NewServer()` and store in `Server` struct -2. Add `backupsHandler()` to `handlers.go` -3. Add `/backups` route to `ServeHTTP` in `server.go` -4. Update `layout.html` — add sidebar nav item +### Stack action handler check -### Step 4: Template + CSS -1. Create `internal/web/templates/backups.html` -2. Add new CSS styles to `style.css` -3. Add any new template functions to `funcmap.go` -4. Make dashboard backup card clickable (wrap in ``) -5. Update `embed.go` — the new template file is auto-included by `//go:embed templates/*` - -### Step 5: Auto-refresh JavaScript -1. Add polling logic for backup-in-progress state -2. Add "Mentés most" button with inline status updates - -### Step 6: Build, deploy, verify -1. Build v0.5.0 -2. Deploy to demo node (sync full docker-compose.yml this time) -3. Verify all sections render correctly -4. Trigger manual backup, verify page updates -5. Check DB validation shows table counts - -### Step 7: Documentation -1. Update README, CONTEXT.md -2. Bump version references - ---- - -## New template functions (`funcmap.go`) +In `internal/stacks/manager.go`, check if the action handler blocks restart on protected stacks. If there's a guard like: ```go -// timeAgo returns a human-readable Hungarian relative time string -// e.g., "15 perccel ezelőtt", "3 órája", "tegnap" -"timeAgo": func(t time.Time) string { ... } +if stack.Protected { + return fmt.Errorf("cannot perform action on protected stack") +} +``` -// dbTypeBadge returns the Hungarian display name for a DB type -// "postgres" → "PostgreSQL", "mariadb" → "MariaDB" -"dbTypeLabel": func(t backup.DBType) string { ... } +Change it to only block destructive actions: -// nextRunLabel formats the next run time relative to now -// Today → "ma 02:30", Tomorrow → "holnap 02:30", else → "2026-02-22 02:30" -"nextRunLabel": func(t time.Time) string { ... } +```go +if stack.Protected && action != "restart" { + return fmt.Errorf("cannot %s protected stack %s", action, name) +} +``` -// pruneLabel converts prune schedule config to Hungarian -// "sunday" → "vasárnap", "daily" → "naponta", "weekly" → "hetente (vasárnap)" -"pruneLabel": func(s string) string { ... } +**CRITICAL**: Only `restart` is allowed. `stop`, `start`, `update`, `delete` must remain blocked for protected stacks. + +### docker-setup.sh changes + +In `install_filebrowser()`, add `.felhom.yml` creation after the docker-compose.yml: + +```bash +# Create .felhom.yml metadata +cat > "${FILEBROWSER_DIR}/.felhom.yml" << 'METAEOF' +display_name: Filebrowser +slug: filebrowser +description: Fájlkezelő a külső merevlemezhez +subdomain: files +category: storage +app_info: + tagline: Web-alapú fájlkezelő a külső merevlemezhez + use_cases: + - Fájlok böngészése és letöltése a külső HDD-ről + - Médiafájlok megosztása családtagokkal + - Dokumentumok feltöltése és kezelése + first_steps: + - Nyisd meg a files.DOMAIN címet a böngészőben + - Jelentkezz be az admin fiókkal + - Tallózd a /srv mappákat + prerequisites: + - Külső HDD csatlakoztatva és felcsatolva +METAEOF ``` --- -## Files to create +## Implementation order -``` -internal/web/templates/backups.html # Backup page template -``` +### Step 1: MariaDB validation fix +1. Fix `ValidateDump()` in `internal/backup/dbdump.go` — scan first 10 lines for header + +### Step 2: Dashboard deployed-only +1. Filter stacks in `dashboardHandler()` in `handlers.go` +2. Update heading in `dashboard.html` to "Telepített alkalmazások" + +### Step 3: Protected stack UX +1. Update `stacks.html` protected section — add restart button, ensure URL link not gated +2. Update `dashboard.html` protected section — same (compact form) +3. Check `internal/stacks/manager.go` — allow restart on protected stacks +4. Add `.felhom.yml` creation to `install_filebrowser()` in `scripts/docker-setup.sh` + +### Step 4: Build, deploy, verify +1. Build v0.4.6 +2. Deploy to demo node (sync full docker-compose.yml) +3. Manually create `/opt/docker/stacks/filebrowser/.felhom.yml` on demo node +4. Trigger backup → verify MariaDB green validation +5. Check dashboard shows only deployed apps +6. Check FileBrowser shows URL and restart button + +### Step 5: Documentation +1. Update CONTEXT.md, README +2. Bump version + +--- ## Files to modify ``` -internal/backup/backup.go # SnapshotRecord, snapshotHistory, GetFullStatus(), check tracking -internal/backup/dbdump.go # DumpValidation, ValidateDump(), ListDumpFiles(), DumpFileInfo -internal/backup/restic.go # ListSnapshots(), extend SnapshotInfo -internal/web/server.go # Accept scheduler, add /backups route -internal/web/handlers.go # backupsHandler() -internal/web/funcmap.go # timeAgo, dbTypeLabel, nextRunLabel, pruneLabel -internal/web/templates/layout.html # Add sidebar nav item -internal/web/templates/dashboard.html # Make backup card clickable ( wrapper) -internal/web/templates/style.css # New backup page styles -cmd/controller/main.go # Pass scheduler to NewServer() +internal/backup/dbdump.go — fix ValidateDump() header check +internal/web/handlers.go — filter deployed-only in dashboardHandler() +internal/web/templates/dashboard.html — heading + protected stack restart +internal/web/templates/stacks.html — protected stack: ensure URL visible + restart button +scripts/docker-setup.sh — create .felhom.yml in install_filebrowser() +internal/stacks/manager.go — allow restart action on protected stacks ``` --- ## Verification checklist -- [ ] Sidebar shows 3 nav items: Vezérlőpult, Alkalmazások, Biztonsági mentés -- [ ] Active nav highlight works on backup page -- [ ] Backup page loads with all 5 sections -- [ ] Status overview cards show correct colors (green/yellow/red/gray) -- [ ] "Távoli mentés" shows gray "nincs beállítva" placeholder -- [ ] Schedule section shows correct times and "Következő" dates -- [ ] "Mentés most" button works and shows loading state -- [ ] Database table lists all discovered DBs with type, size, status -- [ ] Validation column shows table counts for successful dumps -- [ ] Snapshot history table shows recent snapshots with stats -- [ ] Historical snapshots (from before controller restart) show "n/a" for delta stats -- [ ] Repository card shows path, size, integrity status -- [ ] Backed up paths listed correctly -- [ ] Dashboard backup card is clickable → navigates to /backups -- [ ] Empty state shows correctly when backup.enabled is false -- [ ] Auto-refresh works during backup-in-progress -- [ ] Page renders correctly on mobile (responsive) -- [ ] All existing features still work \ No newline at end of file +- [ ] MariaDB (romm) shows green validation badge with table count on backup page +- [ ] Dashboard heading is "Telepített alkalmazások" +- [ ] Dashboard shows only deployed + protected apps (no "Nincs telepítve" entries) +- [ ] Dashboard stat cards still show correct total (52 apps) +- [ ] FileBrowser shows `files.demo-felhom.eu ↗` link on Alkalmazások page +- [ ] FileBrowser shows "Újraindítás" button on both pages +- [ ] Restart works on FileBrowser +- [ ] Other protected stacks also show restart when operational +- [ ] Stop/delete/update still blocked for all protected stacks +- [ ] No regressions on backup page, app detail pages, deploy flow \ No newline at end of file