# TASK: Backup Page Overhaul — Unified App Backup Status & Bug Fixes ## Summary Overhaul the "Biztonsági mentés" page to show a single unified per-app backup overview with expandable detail rows, fix critical bugs (duplicate apps, misleading errors, dead toggles), and implement sequential backup chaining so cross-drive backups run after the nightly backup completes. --- ## Bug Fixes (Critical) ### Bug 1: Duplicate unconfigured apps on every page load **Root cause:** `GetFullStatus()` returns a pointer to `m.cachedStatus`. The `backupsHandler()` then appends to `UnconfiguredApps` and `CrossDriveSummary` on that cached object. Every page load appends again → 3 apps × 3 loads = 9 entries. **File:** `internal/backup/backup.go` — `GetFullStatus()` **Fix:** Return a **deep copy** of the cached status. Specifically, `CrossDriveSummary`, `UnconfiguredApps`, `CrossDriveWarnings`, and `AppDataInfo` slices must not share backing arrays with the cache. ```go func (m *Manager) GetFullStatus(nextDBDump, nextBackup time.Time) *FullBackupStatus { m.mu.Lock() defer m.mu.Unlock() if m.cachedStatus != nil { // Deep copy to prevent callers from mutating cached slices status := *m.cachedStatus status.AppDataInfo = make([]AppBackupInfo, len(m.cachedStatus.AppDataInfo)) copy(status.AppDataInfo, m.cachedStatus.AppDataInfo) status.CrossDriveSummary = nil // rebuilt by handler status.UnconfiguredApps = nil // rebuilt by handler status.CrossDriveWarnings = nil // rebuilt by handler // ... (keep existing dynamic field updates on &status) return &status } // ... (rest unchanged) } ``` **Alternative (simpler):** Don't populate `CrossDriveSummary`/`UnconfiguredApps` in the returned status at all — move that logic into a separate method or let `backupsHandler` build them from `AppDataInfo` + settings. This is cleaner architecturally since the cache shouldn't hold UI-assembly data. **Recommendation:** Alternative approach. The handler already builds this data; the cache should only hold the expensive-to-compute parts (app discovery, restic stats, snapshot history). ### Bug 2: Misleading error message for non-mount-point destinations **Root cause:** When cross-drive destination is configured to `/mnt/hdd_placeholder`, `IsMountPoint()` returns `false` (it's a directory on the system SSD, not a separate mount point). The code then shows: *"A cél tárhely (/mnt/hdd_placeholder) nem elérhető! Ellenőrizd a meghajtó csatlakozását."* — suggesting a disconnected drive, which is incorrect. **File:** `internal/web/handlers.go` — `deployHandler()` (line ~13818), `backupsHandler()` (line ~14036) **Current logic:** ```go if !system.IsMountPoint(path) || !system.IsWritable(path) { // "nem elérhető! Ellenőrizd a meghajtó csatlakozását." } ``` **Fix:** Replace the single check with tiered validation: ```go func validateBackupDestination(path string) (warning string, blocked bool) { // 1. Path doesn't exist at all if _, err := os.Stat(path); os.IsNotExist(err) { return "A cél tárhely (" + path + ") nem létezik!", true } // 2. Path exists but not writable if !system.IsWritable(path) { return "A cél tárhely (" + path + ") nem írható! Ellenőrizd a jogosultságokat.", true } // 3. Path is on the system drive (not a separate mount point) if !system.IsMountPoint(path) { return "A cél tárhely (" + path + ") a rendszermeghajtón van. " + "Meghajtóhiba esetén az eredeti adat és a mentés is elveszhet. " + "Külső meghajtó használata javasolt.", false // warning, not blocked } // 4. All good return "", false } ``` - `blocked = true` → red alert, backup should not run - `blocked = false, warning != ""` → yellow alert, backup can run but user is informed - No warning → green ### Bug 3: Dead `BackupEnabled` toggle **Root cause:** The `BackupEnabled` flag per app (toggled via `settingsAppBackupHandler`) saves to `settings.json` but nothing reads it to actually include/exclude apps from any backup process. There is no Backrest deployment; the nightly restic backup is a cron-driven script that doesn't consult settings.json. **Fix:** Remove the `settingsAppBackupHandler` and the bulk toggle form from the backup page. This toggle is replaced by the new unified per-app status rows (see Architecture section below). The concept of "include this app in nightly backup" becomes implicit: if the app has a DB, it gets dumped; if it has Docker volumes, they're in the restic paths; if it has HDD data, cross-drive must be explicitly configured. **Files to modify:** - `internal/web/handlers.go` — remove `settingsAppBackupHandler()` - `internal/web/server.go` — remove route registration for `POST /settings/app-backup` - `internal/web/templates/backups.html` — remove the bulk toggle form - `internal/settings/settings.go` — deprecate `SetAppBackupBulk()`, `IsAppBackupEnabled()` (keep getters for migration, remove after one version) --- ## Architecture Changes ### 1. Backup Coverage Model Each deployed app has up to 3 backup layers: | Layer | What | How | Needs user config? | |-------|------|-----|-------------------| | **DB dump** | PostgreSQL/MariaDB databases | `backup-db-dump.sh` via systemd timer | No (auto-discovered) | | **Docker volumes** | App configs, state, SQLite DBs | Nightly restic snapshot of named volumes | No (automatic for all deployed apps) | | **User data (HDD)** | Photos, documents, media files | Cross-drive rsync/restic to second drive | **Yes** — requires second drive + config | **Status colors per app:** | Color | Meaning | |-------|---------| | **Green** | All applicable backup layers are configured and last run was successful | | **Yellow** | Warning: last backup run failed/stale, OR destination drive has low space, OR backup is to system drive | | **Red** | Critical: app has HDD user data but no cross-drive backup configured, OR backup destination unreachable/disconnected | | **Gray/Auto** | App has no user-configurable backup needs (DB + volumes only, fully automatic) | **Logic for computing status per app:** ``` if app.HasHDDData: if no cross-drive configured: → RED ("Felhasználói adatokról nincs mentés") elif destination unreachable/disconnected: → RED ("Mentési cél nem elérhető") elif last cross-drive run failed: → YELLOW ("Utolsó mentés sikertelen") elif destination is system drive: → YELLOW ("Mentés a rendszermeghajtóra — külső meghajtó javasolt") elif destination drive >85% full: → YELLOW ("Mentési meghajtó majdnem megtelt") else: → GREEN else: → GREEN/AUTO (no user action needed) # Additionally, cross-cutting checks: if app.HasDB and last DB dump for this app failed: → YELLOW (even if user data is fine) ``` ### 2. Unified Per-App Backup Rows (UI) **Replace** the current two sections ("Alkalmazás adatok" + "Másolatok másik meghajtóra") with a **single section** containing one expandable row per deployed app. #### Collapsed row (default): ``` ┌──────────────────────────────────────────────────────────────────────┐ │ ● Immich Külső tárhely (hdd_1) 92 MB ▶ Részletek │ │ ● Paperless-ngx Külső tárhely (hdd_1) 12 MB ▶ Részletek │ │ ● Gokapi Auto ▶ Részletek │ │ ● Mealie Auto ▶ Részletek │ │ ● RomM Külső tárhely (hdd_1) 340 MB ▶ Részletek │ └──────────────────────────────────────────────────────────────────────┘ ``` - `●` = color indicator (green/yellow/red) - Storage label + size only shown for apps with HDD data - "Auto" badge for apps with only automatic backups - `▶ Részletek` = expand button (triangle/chevron) #### Expanded row (after clicking): ``` ┌──────────────────────────────────────────────────────────────────────┐ │ ● Immich Külső tárhely (hdd_1) 92 MB ▼ Részletek │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ Adatbázis mentés Auto Utolsó: 02-17 02:31 │ │ │ │ Docker kötetek Auto Utolsó: 02-17 03:02 │ │ │ │ Felhasználói adatok rsync → Külső HDD (hdd_1) │ │ │ │ Ütemezés: Naponta │ │ │ │ Utolsó: 02-17 03:35 Sikeres │ │ │ │ [Beállítás] [Futtatás most] │ │ │ └────────────────────────────────────────────────────────────────┘ │ ├──────────────────────────────────────────────────────────────────────┤ │ ● Gokapi Auto ▼ Részletek │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ Adatbázis mentés — (nincs adatbázis) │ │ │ │ Docker kötetek Auto Utolsó: 02-17 03:02 │ │ │ │ Felhasználói adatok — (nincs HDD adat) │ │ │ └────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────┘ ``` **For apps with HDD data but NO cross-drive configured (RED status):** ``` │ Felhasználói adatok ⚠ Nincs beállítva │ │ Mentés beállítása az alkalmazás │ │ oldalán: [Immich beállítások →] │ ``` **For apps with HDD data and destination is system drive (YELLOW):** ``` │ Felhasználói adatok rsync → Rendszer SSD (hdd_placeholder) │ │ ⚠ Rendszermeghajtóra mentés — │ │ meghajtóhiba esetén mindkét másolat │ │ elveszhet. Külső meghajtó javasolt. │ ``` #### No second drive at all — top-level warning If zero apps have cross-drive configured AND at least one app has HDD data, show a prominent card above the app list: ``` ┌──────────────────────────────────────────────────────────────────────┐ │ ⚠ Felhasználói adatokról nincs biztonsági mentés │ │ │ │ A szerveren tárolt fotók, dokumentumok és egyéb fájlok jelenleg │ │ csak egy példányban léteznek. Külső meghajtó csatlakoztatásával │ │ biztonsági másolat készíthető a 3-2-1 szabály szerint. │ │ │ │ [Meghajtó beállítása →] │ └──────────────────────────────────────────────────────────────────────┘ ``` ### 3. Sequential Backup Chaining **Current flow:** ``` 02:30 systemd timer → backup-db-dump.sh (DB dumps) 03:00 cron/scheduler → restic backup (Docker volumes + DB dumps) 03:30 independent scheduler → cross-drive jobs (own schedule) ``` **New flow:** ``` 02:30 systemd timer → backup-db-dump.sh (DB dumps) 03:00 scheduler → restic backup (Docker volumes + DB dumps) ↓ on completion (success or fail) scheduler → cross-drive jobs (for apps with daily schedule) (weekly jobs: only triggered on configured day, e.g., Sunday) ``` **Implementation:** The controller's main scheduler loop already triggers DB dump and restic backup in sequence. After the restic backup completes, add: ```go // After restic backup completes if crossDriveRunner != nil { schedule := "daily" if time.Now().Weekday() == time.Sunday { // Also run weekly jobs on Sunday crossDriveRunner.RunAllScheduled(ctx, "weekly") } crossDriveRunner.RunAllScheduled(ctx, schedule) } ``` **User-facing schedule options on deploy page (cross-drive config):** | Option | Meaning | |--------|---------| | Naponta | Runs every night after nightly backup completes | | Hetente (vasárnap) | Runs only on Sunday night after nightly backup | **Remove** the "Csak kézi indítás" (manual only) schedule option. If a user doesn't want automated cross-drive, they can disable the toggle. Manual trigger ("Futtatás most") remains available regardless. **Weekly + daily DB consistency:** When the user selects "Hetente" for cross-drive backup, show an informational note: ``` ℹ Heti mentés esetén visszaállításkor az adatbázis is a mentés napjára áll vissza a konzisztencia érdekében. A mentés napja és a visszaállítás között keletkezett adatbázis-változások elvesznek (max. 7 nap). ``` **Restore implication:** When restoring user data from a weekly cross-drive snapshot, the restore process should also restore the DB dump from the same date (matching by timestamp). This ensures DB ↔ file consistency. ### 4. Cross-Drive Destination Validation **Replace** the current binary `IsMountPoint || !IsWritable` check with tiered validation. **New function:** `internal/system/mounts_linux.go` ```go type DestinationHealth struct { Exists bool Writable bool MountPoint bool // different device from parent SystemDrive bool // on same device as / UsedPercent float64 // disk usage percentage FreeGB float64 Warning string // human-readable warning (Hungarian) Blocked bool // if true, backup should not run Severity string // "ok", "warning", "critical" } func CheckBackupDestination(path string) DestinationHealth { ... } ``` **Validation tiers:** | Condition | Severity | Blocked? | Message | |-----------|----------|----------|---------| | Path doesn't exist | critical | yes | "A cél tárhely nem létezik" | | Path not writable | critical | yes | "A cél tárhely nem írható" | | Same block device as source | critical | yes | "A forrás és a cél azonos meghajtón van" | | Path is on system drive (/) | warning | no | "Mentés a rendszermeghajtóra — meghajtóhiba esetén mindkét másolat elveszhet" | | Destination >90% full | warning | no | "A mentési meghajtó majdnem megtelt (X% használt)" | | Destination >95% full | critical | yes | "A mentési meghajtó megtelt (X%)" | | Mount point, writable, healthy | ok | no | "" | **Same block device detection:** ```go func isSameBlockDevice(pathA, pathB string) bool { var statA, statB syscall.Stat_t if err := syscall.Stat(pathA, &statA); err != nil { return false } if err := syscall.Stat(pathB, &statB); err != nil { return false } return statA.Dev == statB.Dev } ``` This replaces the current `IsMountPoint()` check in the destination dropdown filtering and the health warning logic. ### 5. Drive Disconnection Notifications **Trigger:** During the nightly backup chain, before running cross-drive jobs, check all configured destinations. If any are unreachable: ```go for _, app := range appsWithCrossDrive { health := system.CheckBackupDestination(app.DestinationPath) if health.Blocked { notifier.NotifyBackupFailed( "Mentési meghajtó nem elérhető", fmt.Sprintf("%s mentési célja (%s) nem elérhető: %s", app.DisplayName, app.DestinationPath, health.Warning), ) } } ``` **Also check on controller startup** (in `main.go` boot sequence) — if a configured destination is unreachable, log a warning and set the status. The backup page will show it immediately. **Also check in the 15-minute hub report** — include destination health in the status payload so the hub dashboard shows drive problems centrally. --- ## Implementation Steps ### Step 1: Fix duplicate apps bug (Bug 1) **Files:** - `internal/backup/backup.go` — `GetFullStatus()`: return deep copy with nil cross-drive/unconfigured slices - `internal/web/handlers.go` — `backupsHandler()`: verify no mutation of cached data **Test:** Load backup page 5 times → unconfigured apps count stays consistent. ### Step 2: Fix destination validation (Bug 2 + Architecture item 4) **Files:** - `internal/system/mounts_linux.go` — add `CheckBackupDestination()`, `isSameBlockDevice()` - `internal/system/mounts_other.go` — add stubs - `internal/web/handlers.go` — `deployHandler()`: replace `IsMountPoint || !IsWritable` with `CheckBackupDestination()` - `internal/web/handlers.go` — `backupsHandler()`: same replacement for cross-drive warnings - `internal/web/templates/deploy.html` — update warning display to handle `warning` vs `critical` severity **Test:** - Configure cross-drive to `hdd_placeholder` → shows yellow warning about system drive, backup still allowed - Configure cross-drive to `hdd_1` (external) → green, no warning - Disconnect external drive → shows red "nem elérhető" - Configure source and dest on same drive → blocked ### Step 3: Remove dead BackupEnabled toggle (Bug 3) **Files:** - `internal/web/handlers.go` — remove `settingsAppBackupHandler()` - `internal/web/server.go` — remove `POST /settings/app-backup` route - `internal/web/templates/backups.html` — remove bulk toggle form, remove "Aktív/Inaktív" badges from old section **Test:** Backup page loads without toggle form. No 500 errors. ### Step 4: Unified per-app backup rows (Architecture item 2) **New struct:** ```go // AppBackupRow holds all backup information for one app, used by the template. type AppBackupRow struct { StackName string DisplayName string Status string // "green", "yellow", "red", "auto" StatusText string // short Hungarian text for the indicator tooltip // Storage info (HDD apps only) HasHDDData bool StorageLabel string HDDSizeHuman string // Backup layers DBBackup *BackupLayerInfo // nil if app has no DB VolumeBackup *BackupLayerInfo // always present for deployed apps UserDataBackup *BackupLayerInfo // nil if app has no HDD data // Warnings (shown in expanded view) Warnings []string } type BackupLayerInfo struct { Type string // "db", "volume", "userdata" Label string // "Adatbázis mentés", "Docker kötetek", "Felhasználói adatok" Auto bool // true if no user config needed Configured bool // true if backup is set up for this layer Method string // "restic", "rsync", "" (for auto) Destination string // label of destination (for cross-drive) Schedule string // "Naponta", "Hetente", "" (for auto) LastRun string // formatted timestamp LastStatus string // "ok", "error", "running", "" LastError string // error message if failed StatusBadge string // "Sikeres", "Hiba", "Fut...", "Auto", "—" ConfigURL string // link to configure (deploy page) } ``` **Files:** - `internal/web/handlers.go` — new `buildAppBackupRows()` function, called from `backupsHandler()` - `internal/web/templates/backups.html` — replace both sections with unified expandable rows - `internal/web/templates/style.css` — new styles for expandable rows, status indicators, layer detail grid **Template structure (backups.html):** ```html

Alkalmazások mentési állapota

{{if .NoUserDataBackupWarning}}
Felhasználói adatokról nincs biztonsági mentés. A szerveren tárolt fotók, dokumentumok és egyéb fájlok jelenleg csak egy példányban léteznek. Meghajtó beállítása
{{end}} {{range .AppBackupRows}}
{{.DisplayName}}
{{if .HasHDDData}} {{if .StorageLabel}}{{.StorageLabel}}{{end}} {{.HDDSizeHuman}} {{else}} Auto {{end}}
{{end}}
``` **JS for expand/collapse:** ```javascript function toggleBackupDetail(header) { const detail = header.nextElementSibling; const icon = header.querySelector('.expand-icon'); if (detail.style.display === 'none') { detail.style.display = 'block'; icon.textContent = '▼'; } else { detail.style.display = 'none'; icon.textContent = '▶'; } } ``` **Test:** - All deployed apps appear in list - Apps with HDD data show storage label + size - Apps without HDD data show "Auto" badge - Expanding shows correct layer info - Red dot for unconfigured user data - Green dot for fully covered apps ### Step 5: Sequential backup chaining (Architecture item 3) **Files:** - `cmd/controller/main.go` — modify nightly scheduler to chain cross-drive after restic - `internal/backup/crossdrive.go` — `RunAllScheduled()` already exists, verify it handles "daily" and "weekly" correctly - `internal/web/templates/deploy.html` — update schedule dropdown (remove "Csak kézi indítás", add weekly consistency note) **Current scheduler code (conceptual location in main.go):** ```go // In the nightly backup goroutine go func() { // ... wait for scheduled time ... // Phase 1: DB dumps backupMgr.RunDBDump(ctx) // Phase 2: Restic snapshot backupMgr.RunBackup(ctx) // Phase 3: Cross-drive (NEW — chained) if crossDriveRunner != nil { crossDriveRunner.RunAllScheduled(ctx, "daily") if isWeeklyTriggerDay() { // e.g., Sunday crossDriveRunner.RunAllScheduled(ctx, "weekly") } } }() ``` **Deploy page schedule dropdown update:** ```html ``` **Test:** - Set app to daily → cross-drive runs after nightly backup every night - Set app to weekly → cross-drive runs only on Sunday night - Manual trigger still works regardless of schedule ### Step 6: Drive disconnection detection & notification (Architecture item 5) **Files:** - `cmd/controller/main.go` — startup drive check - `internal/backup/crossdrive.go` — pre-run destination check, notification on failure - `internal/web/handlers.go` — `backupsHandler()`: include destination health in row data **Startup check (main.go):** ```go // After settings are loaded, check all configured cross-drive destinations crossConfigs := sett.GetAllCrossDriveConfigs() for appName, cfg := range crossConfigs { if cfg == nil || !cfg.Enabled || cfg.DestinationPath == "" { continue } health := system.CheckBackupDestination(cfg.DestinationPath) if health.Blocked { logger.Printf("[WARN] Backup destination for %s unreachable: %s (%s)", appName, cfg.DestinationPath, health.Warning) } } ``` **Pre-run check (crossdrive.go RunAppBackup):** ```go func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) error { cfg := r.sett.GetCrossDriveConfig(stackName) // ... existing checks ... // NEW: Tiered destination validation health := system.CheckBackupDestination(cfg.DestinationPath) if health.Blocked { r.sett.UpdateCrossDriveStatus(stackName, "error", health.Warning, 0, "") // Send notification if r.notifier != nil { r.notifier.NotifyBackupFailed( "Mentési meghajtó nem elérhető", fmt.Sprintf("%s mentési célja (%s): %s", stackName, cfg.DestinationPath, health.Warning), ) } return fmt.Errorf("destination blocked: %s", health.Warning) } // ... proceed with backup ... } ``` **Hub report inclusion:** Add destination health summary to the 15-minute hub status report payload so the central dashboard can show drive problems. **Test:** - Disconnect external USB drive → backup page shows red status, notification sent - Reconnect → status recovers on next page load - Controller restart with disconnected drive → warning in logs ### Step 7: Deploy page updates **Files:** - `internal/web/templates/deploy.html` — update cross-drive section - `internal/web/handlers.go` — `deployHandler()`: use `CheckBackupDestination()` for warnings **Changes:** - Remove "Csak kézi indítás" schedule option - Add weekly consistency note (shown/hidden via JS based on dropdown) - Replace flat error message with tiered warning/critical display - Add warning on app deploy page if app has HDD data but no cross-drive configured: ```html {{if and .StorageInfo (not .CrossDriveConfig)}}
Az alkalmazás felhasználói adatairól nincs biztonsági mentés. Állítsd be alább, vagy csatlakoztass egy külső meghajtót.
{{end}} ``` ### Step 8: Version bump & testing - Update CHANGELOG.md, CONTEXT.md - Bump version (suggest 0.12.0 — this is a significant feature change) - Build + deploy to demo-felhom.eu - Full test cycle: 1. Backup page loads without duplicates ✓ 2. All deployed apps appear in unified list ✓ 3. Expandable rows show correct layer info ✓ 4. Apps without HDD data show "Auto" ✓ 5. Immich (HDD data, cross-drive configured) → green ✓ 6. Paperless/RomM (HDD data, no cross-drive) → red ✓ 7. Configure cross-drive to hdd_placeholder → yellow warning ✓ 8. Configure cross-drive to hdd_1 → green ✓ 9. Cross-drive runs after nightly backup (check logs) ✓ 10. Disconnect external drive → red on backup page, notification sent ✓ 11. Manual trigger still works ✓ --- ## Files Summary ### New files: - None (all changes in existing files) ### Modified files: | File | Changes | |------|---------| | `internal/backup/backup.go` | Deep copy in `GetFullStatus()`, remove cross-drive/unconfigured population from cache | | `internal/backup/crossdrive.go` | Pre-run destination validation, notification on blocked destination | | `internal/system/mounts_linux.go` | `CheckBackupDestination()`, `isSameBlockDevice()` | | `internal/system/mounts_other.go` | Stubs for new functions | | `internal/web/handlers.go` | `buildAppBackupRows()`, remove `settingsAppBackupHandler()`, updated `deployHandler()` validation | | `internal/web/server.go` | Remove `POST /settings/app-backup` route | | `internal/web/templates/backups.html` | Complete rewrite of app backup sections → unified expandable rows | | `internal/web/templates/deploy.html` | Updated schedule dropdown, weekly note, tiered warnings, unconfigured warning | | `internal/web/templates/style.css` | New styles for expandable rows, status dots, layer detail grid | | `internal/settings/settings.go` | Deprecate `SetAppBackupBulk()`, `IsAppBackupEnabled()` | | `cmd/controller/main.go` | Sequential chaining in scheduler, startup drive check | --- ## Design Decisions ### Why merge the two backup sections? Separation between "Alkalmazás adatok" and "Másolatok másik meghajtóra" forced users to mentally correlate information across two lists. A single per-app row with expandable detail gives complete backup coverage at a glance without cross-referencing. ### Why remove the BackupEnabled toggle? It wrote to settings.json but nothing consumed the flag. No Backrest deployment exists; the nightly restic script doesn't check settings.json. Showing a toggle that doesn't affect anything is worse than showing nothing — it gives false confidence. ### Why sequential chaining instead of independent schedules? DB ↔ file consistency. If cross-drive runs at a different time than the DB dump, a restore could produce an inconsistent state (DB references files that don't exist, or vice versa). Chaining ensures DB dump → restic → cross-drive all happen in one window, and a "restore to date X" operation can grab all three from the same night. ### Why allow system drive as backup target with warning? For small data (configs, documents), having a second copy on the system SSD is better than no copy at all. For large data (Immich photos), the system drive may not have enough space and a drive failure would lose both copies. The warning lets users make an informed choice rather than blocking a useful configuration for small-data apps. ### Why "Hetente" restores also roll back the DB? A weekly user data snapshot from Sunday combined with a daily DB from Wednesday would create inconsistency — the DB would reference files from Mon-Wed that don't exist in Sunday's snapshot. Rolling both back to the same point (Sunday night) guarantees consistency at the cost of losing up to 7 days of DB changes. This is the correct trade-off for data integrity.