192 lines
8.5 KiB
Markdown
192 lines
8.5 KiB
Markdown
# 0.12.2 - Bug 4: Restore Section ("Visszaállítás") Simplification — TASK.md
|
|
|
|
## Current State
|
|
|
|
### Backup architecture (what goes where)
|
|
|
|
**Restic snapshots** (daily, on SSD):
|
|
- `/opt/docker/stacks/` — all Docker configs, compose files, app.yaml
|
|
- `/opt/docker/db-dumps/` — DB dump SQL files
|
|
- `/opt/docker/felhom-controller/controller.yaml`
|
|
- App HDD data paths (if backup enabled) — e.g., `/mnt/hdd_1/storage/immich/`
|
|
|
|
**Cross-drive backups** (per-app, rsync/restic to secondary drive):
|
|
- Copies app user data between drives (e.g., HDD→SSD or SSD→HDD)
|
|
- Separate from restic — not part of snapshot restore
|
|
|
|
**Key detail:** `SnapshotInfo.Paths []string` from `restic snapshots --json` includes the backup source paths. Snapshots created BEFORE an app's HDD backup was enabled won't have that app's HDD paths listed. This can be used for filtering.
|
|
|
|
### Current restore UI flow (backups.html Section 7)
|
|
1. **App dropdown** — only apps with `HasHDDData && BackupEnabled` (filters out auto-only apps)
|
|
2. **Snapshot dropdown** — loads ALL snapshots from `/api/backup/snapshots` (no filtering by app)
|
|
3. **Path display** — shows HDD paths that will be restored (technical, confusing for customers)
|
|
4. **Warning block** — "FELÜLÍRJA a jelenlegi adatait... NEM vonható vissza!... Javasoljuk az alkalmazás leállítását"
|
|
5. **Confirmation checkbox** — "Megértettem, visszaállítás saját felelősségre"
|
|
6. **Restore button** → POST `/backup/restore` with `stack_name` + `snapshot_id`
|
|
|
|
### Current restore backend (`RestoreApp` in `restore.go`)
|
|
- Validates backup enabled + snapshot exists
|
|
- Resolves app's HDD paths via `GetStackHDDMounts()`
|
|
- Runs `restic restore {snapshot} --target / --include {path1} --include {path2}`
|
|
- Only restores HDD data — does NOT restore Docker configs or DB dumps
|
|
|
|
---
|
|
|
|
## Problems
|
|
|
|
1. **Snapshot selector shows ALL snapshots** — user can pick one that predates their app's backup setup → restore fails or does nothing
|
|
2. **Path display is confusing** — customers don't need to see `/mnt/hdd_1/storage/immich/`
|
|
3. **Warning is intimidating** — multiple strong statements, feels dangerous
|
|
4. **No auto-stop** — just "recommended" to stop the app, user can forget
|
|
5. **DB-only restore** is implicitly possible (pick old snapshot) but not clear what it does
|
|
|
|
---
|
|
|
|
## Proposed Design
|
|
|
|
### Simplified UI (3 elements instead of 6)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Visszaállítás │
|
|
│ │
|
|
│ Alkalmazás: [▼ Immich ] │
|
|
│ Pillanatkép: [▼ 2026-02-17 03:00 (a3f2b1) ] │
|
|
│ │
|
|
│ ⚠ A visszaállítás felülírja az alkalmazás │
|
|
│ jelenlegi adatait a kiválasztott mentés │
|
|
│ állapotával. Az alkalmazás a folyamat során │
|
|
│ automatikusan leáll és újraindul. │
|
|
│ │
|
|
│ ☐ Megértettem, visszaállítás indítása │
|
|
│ │
|
|
│ [ Visszaállítás indítása ] │
|
|
└─────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Changes
|
|
|
|
| # | What | Details |
|
|
|---|------|---------|
|
|
| 1 | **Filter snapshots by app** | When app selected, `/api/backup/snapshots?stack={name}` returns only snapshots whose `Paths` contain at least one of the app's HDD paths. Show snapshot time in human-friendly format, no raw IDs |
|
|
| 2 | **Remove path display** | Customer doesn't need to see mount paths. The restore handler knows what to include |
|
|
| 3 | **Single calm warning** | Replace 3-line warning + separate checkbox label with one concise block |
|
|
| 4 | **Auto-stop + restart** | Restore handler stops the app's containers before restore, restarts after. Eliminates "javasoljuk leállítását" advice |
|
|
| 5 | **Hide DB-only restore** | Not exposed in UI — support contacts Viktor for CLI-level `restic restore` |
|
|
|
|
---
|
|
|
|
## Implementation Plan
|
|
|
|
### Backend changes
|
|
|
|
**1. Filtered snapshots API** (`internal/web/api_router.go` or `router.go`)
|
|
|
|
Extend `GET /api/backup/snapshots` with optional `?stack={name}` query param:
|
|
```go
|
|
func (r *Router) snapshotsHandler(w http.ResponseWriter, req *http.Request) {
|
|
snapshots, err := r.backupMgr.ListSnapshots(50)
|
|
// ... existing error handling ...
|
|
|
|
// Filter by stack if requested
|
|
if stackName := req.URL.Query().Get("stack"); stackName != "" {
|
|
hddMounts := r.stackProvider.GetStackHDDMounts(stackName)
|
|
if len(hddMounts) > 0 {
|
|
snapshots = filterSnapshotsByPaths(snapshots, hddMounts)
|
|
}
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: snapshots})
|
|
}
|
|
|
|
func filterSnapshotsByPaths(snapshots []backup.SnapshotInfo, requiredPaths []string) []backup.SnapshotInfo {
|
|
var filtered []backup.SnapshotInfo
|
|
for _, snap := range snapshots {
|
|
for _, req := range requiredPaths {
|
|
for _, sp := range snap.Paths {
|
|
if strings.HasPrefix(req, sp) || strings.HasPrefix(sp, req) {
|
|
filtered = append(filtered, snap)
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
next:
|
|
}
|
|
return filtered
|
|
}
|
|
```
|
|
|
|
**2. Auto-stop/restart in RestoreApp** (`internal/backup/restore.go`)
|
|
|
|
```go
|
|
func (m *Manager) RestoreApp(stackName, snapshotID string) error {
|
|
// ... existing validation ...
|
|
|
|
// Stop the app's containers before restore
|
|
if m.stackProvider != nil {
|
|
m.logger.Printf("[INFO] Stopping %s for restore...", stackName)
|
|
if err := m.stackProvider.StopStack(stackName); err != nil {
|
|
m.logger.Printf("[WARN] Could not stop %s: %v (proceeding anyway)", stackName, err)
|
|
}
|
|
}
|
|
|
|
// Execute restore (existing logic)
|
|
if err := m.restic.RestoreAppData(snapshotID, hddMounts); err != nil {
|
|
// Try to restart even on failure
|
|
m.stackProvider.StartStack(stackName)
|
|
return err
|
|
}
|
|
|
|
// Restart after successful restore
|
|
if m.stackProvider != nil {
|
|
m.logger.Printf("[INFO] Restarting %s after restore...", stackName)
|
|
m.stackProvider.StartStack(stackName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
```
|
|
|
|
### Frontend changes
|
|
|
|
**3. Template simplification** (`backups.html` Section 7)
|
|
- Remove `restore-paths` div entirely
|
|
- Replace warning text with single concise block
|
|
- Keep app dropdown + snapshot dropdown + confirm checkbox + button
|
|
- Update `onRestoreAppChange()` JS to call `/api/backup/snapshots?stack={name}`
|
|
|
|
**4. Snapshot display format**
|
|
- Show: `2026-02-17 vasárnap 03:00` instead of `a3f2b1 — 2026.02.17. 3:00:00`
|
|
- Keep snapshot ID in `option.value`, show human time in label
|
|
|
|
---
|
|
|
|
## Design Decisions
|
|
|
|
**D1: Snapshot dropdown format** → Show `date (id)` — human-friendly time with short ID in parentheses for support debugging. E.g. `2026-02-17 vasárnap 03:00 (a3f2b1)`
|
|
|
|
**D2: Zero filtered snapshots** → Show inline message instead of empty dropdown: "Még nincs mentés felhasználói adattal." (shortened from original)
|
|
|
|
**D3: Auto-stop/restart** → Yes, stacks must be stopped during restore. Check if StackProvider interface already exposes Stop/Start; extend it if not.
|
|
|
|
**D4: Progress indication** → Keep current simple behavior (button text change + page redirect) for v0.13.0. Async polling (like backup progress) is a future enhancement.
|
|
|
|
**D5: Restore scope** → User data (HDD paths) only. Docker configs and DB dumps are NOT restored — that's a disaster recovery task handled via CLI/support.
|
|
|
|
---
|
|
|
|
## Files to modify
|
|
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `internal/web/api_router.go` (or `router.go`) | Add `?stack=` filter to snapshots endpoint |
|
|
| `internal/backup/restore.go` | Add auto-stop/restart logic |
|
|
| `internal/backup/types.go` | Add `filterSnapshotsByPaths` helper if needed |
|
|
| `internal/web/templates/backups.html` | Simplify restore section HTML |
|
|
| `internal/web/templates/backups.html` (JS) | Update `onRestoreAppChange()` to pass stack param |
|
|
| `internal/stacks/` (maybe) | Ensure StackProvider exposes Stop/Start if needed for Q3 |
|
|
|
|
## Estimated effort
|
|
- Backend: ~2 hours (snapshot filtering + auto-stop/restart + interface extension)
|
|
- Frontend: ~1 hour (template simplification + JS update)
|
|
- Testing: ~1 hour (restore flow with different apps/snapshots) |