0.12.1 Bug Fixes
This commit is contained in:
@@ -1,724 +1,200 @@
|
|||||||
# TASK: Backup Page Overhaul — Unified App Backup Status & Bug Fixes
|
# 0.12.1 Bug Fixes
|
||||||
|
|
||||||
## Summary
|
## Bug 1: Version displays "vv0.12.0"
|
||||||
|
|
||||||
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.
|
**Severity:** Low (cosmetic)
|
||||||
|
**Location:** Two templates with hardcoded `v` prefix
|
||||||
|
|
||||||
|
The sidebar and settings page templates both prepend `v` to `{{.Version}}`:
|
||||||
|
```
|
||||||
|
<span class="version">v{{.Version}}</span> ← line 17845 (layout.html)
|
||||||
|
<span class="settings-value mono">v{{.Version}}</span> ← line 19061 (settings.html)
|
||||||
|
```
|
||||||
|
|
||||||
|
`build.sh` takes VERSION as its first argument (`VERSION="${1:-dev}"`), and Viktor passes `v0.12.0` → the template renders `vv0.12.0`.
|
||||||
|
|
||||||
|
**Fix (template side — recommended):**
|
||||||
|
Replace `v{{.Version}}` with `{{.Version}}` in both locations. This way the version displayed matches the git tag exactly. The `v` prefix comes from the tag/build arg naturally.
|
||||||
|
|
||||||
|
**Affected files:**
|
||||||
|
- `internal/web/templates/layout.html` — sidebar footer
|
||||||
|
- `internal/web/templates/settings.html` — version info row
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Bug Fixes (Critical)
|
## Bug 2: "Külső HDD" stats show same values as SSD
|
||||||
|
|
||||||
### Bug 1: Duplicate unconfigured apps on every page load
|
**Severity:** Medium (wrong data displayed)
|
||||||
|
**Location:** Dashboard and Backup page storage bars; also metrics collector in `main.go`
|
||||||
|
|
||||||
**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.
|
**Setup on demo-felhom.eu:**
|
||||||
|
- Internal SSD (512G): system disk, mounted at `/`. `/mnt/hdd_placeholder` is a normal folder on this.
|
||||||
|
- External USB HDD (1TB): mounted at `/mnt/hdd_1` (ext4, `/host-dev/sdb1`)
|
||||||
|
- Two storage paths registered (visible in Settings > Adattárolók):
|
||||||
|
- `/mnt/hdd_placeholder` — "Rendszermeghajtón" badge, Aktív, 19.8 GB / 460.2 GB
|
||||||
|
- `/mnt/hdd_1` — "Alapértelmezett" + "Aktív" badge, 0.0 GB / 915.8 GB
|
||||||
|
- No `hdd_path` in `controller.yaml`, no `HDD_PATH` in any `app.yaml`
|
||||||
|
- The Settings page (Adattárolók section) shows **correct** values for both paths
|
||||||
|
|
||||||
**File:** `internal/backup/backup.go` — `GetFullStatus()`
|
**Current behavior:**
|
||||||
|
- Dashboard: "Külső HDD" shows 19.8 GB / 460 GB — same as SSD (wrong!)
|
||||||
|
- Backup page: Same duplicate in "Tárhely áttekintés"
|
||||||
|
|
||||||
**Fix:** Return a **deep copy** of the cached status. Specifically, `CrossDriveSummary`, `UnconfiguredApps`, `CrossDriveWarnings`, and `AppDataInfo` slices must not share backing arrays with the cache.
|
**Root cause:**
|
||||||
|
`primaryHDDPath()` (line 15164) returns `paths[0].Path` — the **first** element in the slice, which happens to be `/mnt/hdd_placeholder` (on the SSD). Then `system.GetInfo("/mnt/hdd_placeholder", ...)` calls `syscall.Statfs` on a directory that lives on the root filesystem → returns SSD stats.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (m *Manager) GetFullStatus(nextDBDump, nextBackup time.Time) *FullBackupStatus {
|
// current — broken
|
||||||
m.mu.Lock()
|
func (s *Server) primaryHDDPath() string {
|
||||||
defer m.mu.Unlock()
|
if paths := s.settings.GetStoragePaths(); len(paths) > 0 {
|
||||||
|
return paths[0].Path // ← returns first element, NOT the default!
|
||||||
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)
|
return s.cfg.Paths.HDDPath
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**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.
|
But `GetDefaultStoragePath()` (line 8946) already exists and returns the path with `IsDefault: true` — which is `/mnt/hdd_1` (the actual HDD).
|
||||||
|
|
||||||
**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).
|
**Same bug in `main.go`** (line 2403-2405) for the metrics collector:
|
||||||
|
|
||||||
### 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
|
```go
|
||||||
if !system.IsMountPoint(path) || !system.IsWritable(path) {
|
metricsHDDPath := cfg.Paths.HDDPath
|
||||||
// "nem elérhető! Ellenőrizd a meghajtó csatlakozását."
|
if paths := sett.GetStoragePaths(); len(paths) > 0 {
|
||||||
|
metricsHDDPath = paths[0].Path // ← same bug
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fix:** Replace the single check with tiered validation:
|
**Fix:**
|
||||||
|
1. `primaryHDDPath()` → use `s.settings.GetDefaultStoragePath()` instead of `paths[0].Path`
|
||||||
|
2. `main.go` metrics collector → use `sett.GetDefaultStoragePath()` instead of `paths[0].Path`
|
||||||
|
3. Fallback: if no default is set, fall back to `cfg.Paths.HDDPath` (existing behavior)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func validateBackupDestination(path string) (warning string, blocked bool) {
|
// fixed
|
||||||
// 1. Path doesn't exist at all
|
func (s *Server) primaryHDDPath() string {
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if p := s.settings.GetDefaultStoragePath(); p != "" {
|
||||||
return "A cél tárhely (" + path + ") nem létezik!", true
|
return p
|
||||||
}
|
}
|
||||||
// 2. Path exists but not writable
|
return s.cfg.Paths.HDDPath
|
||||||
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
|
**Affected files:**
|
||||||
- `blocked = false, warning != ""` → yellow alert, backup can run but user is informed
|
- `internal/web/handlers.go` — `primaryHDDPath()` function
|
||||||
- No warning → green
|
- `cmd/controller/main.go` — metrics collector HDD path selection
|
||||||
|
|
||||||
### 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
|
## Bug 3: Gokapi/Mealie show gray ("auto") status — should be green
|
||||||
|
|
||||||
### 1. Backup Coverage Model
|
**Severity:** Medium (misleading status)
|
||||||
|
**Location:** `buildAppBackupRows()` in `internal/web/handlers.go` (line 14302-14367)
|
||||||
|
|
||||||
Each deployed app has up to 3 backup layers:
|
Apps without HDD data (like Gokapi, Mealie) always get `status = "auto"` (gray dot). From the user's perspective, these apps ARE fully backed up — their Docker volumes are included in the restic snapshot, and their configs are under `/opt/docker/stacks/`. The gray dot misleadingly suggests they're not backed up at all.
|
||||||
|
|
||||||
| 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:
|
|
||||||
|
|
||||||
|
**Current logic (line 14365-14368):**
|
||||||
```go
|
```go
|
||||||
// After restic backup completes
|
} else {
|
||||||
if crossDriveRunner != nil {
|
// No HDD data — fully automatic
|
||||||
schedule := "daily"
|
row.Status = "auto"
|
||||||
if time.Now().Weekday() == time.Sunday {
|
row.StatusText = "Automatikus mentés (nincs felhasználói adat)"
|
||||||
// Also run weekly jobs on Sunday
|
|
||||||
crossDriveRunner.RunAllScheduled(ctx, "weekly")
|
|
||||||
}
|
|
||||||
crossDriveRunner.RunAllScheduled(ctx, schedule)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**User-facing schedule options on deploy page (cross-drive config):**
|
**Desired behavior:**
|
||||||
|
- Apps with no HDD data whose Docker volumes are successfully backed up → **green** dot with text "Mentés rendben"
|
||||||
| Option | Meaning |
|
- Apps with no HDD data where last volume backup failed → **yellow** dot
|
||||||
|--------|---------|
|
- Keep "auto" only if there's genuinely no backup info available (edge case)
|
||||||
| 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`
|
|
||||||
|
|
||||||
|
**Fix approach:**
|
||||||
|
Replace the `else` block (line 14365-14368):
|
||||||
```go
|
```go
|
||||||
type DestinationHealth struct {
|
} else {
|
||||||
Exists bool
|
// No HDD data — everything backed up automatically via restic
|
||||||
Writable bool
|
if volumeLastStatus == "ok" {
|
||||||
MountPoint bool // different device from parent
|
row.Status = "green"
|
||||||
SystemDrive bool // on same device as /
|
row.StatusText = "Mentés rendben"
|
||||||
UsedPercent float64 // disk usage percentage
|
} else if volumeLastStatus == "error" {
|
||||||
FreeGB float64
|
row.Status = "yellow"
|
||||||
Warning string // human-readable warning (Hungarian)
|
row.StatusText = "Kötetek mentése sikertelen"
|
||||||
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
|
|
||||||
<div class="backup-section-card">
|
|
||||||
<h3>Alkalmazások mentési állapota</h3>
|
|
||||||
|
|
||||||
{{if .NoUserDataBackupWarning}}
|
|
||||||
<div class="alert alert-error" style="margin-bottom:1.5rem">
|
|
||||||
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.
|
|
||||||
<a href="/settings">Meghajtó beállítása</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{range .AppBackupRows}}
|
|
||||||
<div class="app-backup-row" data-status="{{.Status}}">
|
|
||||||
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
|
|
||||||
<span class="status-dot status-{{.Status}}" title="{{.StatusText}}"></span>
|
|
||||||
<span class="app-backup-row-name">{{.DisplayName}}</span>
|
|
||||||
<div class="app-backup-row-meta">
|
|
||||||
{{if .HasHDDData}}
|
|
||||||
{{if .StorageLabel}}<span class="meta-badge meta-badge-storage">{{.StorageLabel}}</span>{{end}}
|
|
||||||
<span class="mono app-backup-size">{{.HDDSizeHuman}}</span>
|
|
||||||
{{else}}
|
|
||||||
<span class="meta-badge meta-badge-auto">Auto</span>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<span class="expand-icon">▶</span>
|
|
||||||
</div>
|
|
||||||
<div class="app-backup-row-detail" style="display:none">
|
|
||||||
<!-- DB layer -->
|
|
||||||
<div class="backup-layer-row">
|
|
||||||
<span class="layer-label">Adatbázis mentés</span>
|
|
||||||
{{if .DBBackup}}
|
|
||||||
<span class="layer-badge">Auto</span>
|
|
||||||
{{if .DBBackup.LastRun}}<span class="layer-last">{{.DBBackup.LastRun}}</span>{{end}}
|
|
||||||
{{else}}
|
|
||||||
<span class="layer-na">— (nincs adatbázis)</span>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<!-- Volume layer -->
|
|
||||||
<div class="backup-layer-row">
|
|
||||||
<span class="layer-label">Docker kötetek</span>
|
|
||||||
<span class="layer-badge">Auto</span>
|
|
||||||
{{if .VolumeBackup.LastRun}}<span class="layer-last">{{.VolumeBackup.LastRun}}</span>{{end}}
|
|
||||||
</div>
|
|
||||||
<!-- User data layer -->
|
|
||||||
<div class="backup-layer-row">
|
|
||||||
<span class="layer-label">Felhasználói adatok</span>
|
|
||||||
{{if .UserDataBackup}}
|
|
||||||
{{if .UserDataBackup.Configured}}
|
|
||||||
<span class="layer-method">{{.UserDataBackup.Method}}</span>
|
|
||||||
<span class="layer-dest">→ {{.UserDataBackup.Destination}}</span>
|
|
||||||
<span class="layer-schedule">{{.UserDataBackup.Schedule}}</span>
|
|
||||||
{{if .UserDataBackup.LastRun}}
|
|
||||||
<span class="layer-last">{{.UserDataBackup.LastRun}}
|
|
||||||
{{.UserDataBackup.StatusBadge}}</span>
|
|
||||||
{{end}}
|
|
||||||
<div class="layer-actions">
|
|
||||||
<a href="/stacks/{{$.StackName}}/deploy" class="btn btn-xs">Beallitas</a>
|
|
||||||
<button class="btn btn-xs btn-outline"
|
|
||||||
onclick="triggerCrossDriveBackup('{{$.StackName}}', this)">
|
|
||||||
Futtatás most</button>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<span class="layer-unconfigured">Nincs beállítva</span>
|
|
||||||
<a href="/stacks/{{$.StackName}}/deploy">Beállítás →</a>
|
|
||||||
{{end}}
|
|
||||||
{{else}}
|
|
||||||
<span class="layer-na">— (nincs HDD adat)</span>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<!-- Warnings -->
|
|
||||||
{{range .Warnings}}
|
|
||||||
<div class="backup-layer-warning">{{.}}</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**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 {
|
} else {
|
||||||
detail.style.display = 'none';
|
row.Status = "auto"
|
||||||
icon.textContent = '▶';
|
row.StatusText = "Automatikus mentés"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Test:**
|
Additionally, if the app has a DB and the DB backup was successful, that should also contribute to green status. The existing DB error check at line 14372 already degrades to yellow, so this is covered.
|
||||||
- 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)
|
**Affected files:**
|
||||||
|
- `internal/web/handlers.go` — `buildAppBackupRows()` status logic
|
||||||
**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
|
|
||||||
<select name="cross_drive_schedule" ...>
|
|
||||||
<option value="daily">Naponta (az éjszakai mentés után)</option>
|
|
||||||
<option value="weekly">Hetente, vasárnap (az éjszakai mentés után)</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<!-- Show note for weekly -->
|
|
||||||
<div class="form-hint weekly-note" style="display:none">
|
|
||||||
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).
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**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)}}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
Az alkalmazás felhasználói adatairól nincs biztonsági mentés.
|
|
||||||
Állítsd be alább, vagy csatlakoztass egy
|
|
||||||
<a href="/settings">külső meghajtót</a>.
|
|
||||||
</div>
|
|
||||||
{{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
|
## Bug 4: Restore section ("Visszaállítás") UX rethink
|
||||||
|
|
||||||
### New files:
|
**Severity:** Medium (UX complexity)
|
||||||
- None (all changes in existing files)
|
**Location:** Backup page restore section in `backups.html`
|
||||||
|
|
||||||
### Modified files:
|
Now that user data folders are also backed up via cross-drive (rsync), the restore section needs rethinking. Currently:
|
||||||
|
- DB backup is a separate snapshot tied to user data
|
||||||
|
- Multiple warnings accumulate, making the UI intimidating
|
||||||
|
- User must understand the difference between DB-only and full restore
|
||||||
|
|
||||||
| File | Changes |
|
**Decision (from Viktor):**
|
||||||
|------|---------|
|
- If user data is backed up, the "Pillanatkép" selector should only show snapshots that include user data → restores everything together (most failsafe)
|
||||||
| `internal/backup/backup.go` | Deep copy in `GetFullStatus()`, remove cross-drive/unconfigured population from cache |
|
- Daily DB-only backups remain useful as intermediate recovery points, but DB-only restore is an advanced task → contact support
|
||||||
| `internal/backup/crossdrive.go` | Pre-run destination validation, notification on blocked destination |
|
- This simplifies the UI: one dropdown, one action, one clear warning
|
||||||
| `internal/system/mounts_linux.go` | `CheckBackupDestination()`, `isSameBlockDevice()` |
|
|
||||||
| `internal/system/mounts_other.go` | Stubs for new functions |
|
**Implementation plan:**
|
||||||
| `internal/web/handlers.go` | `buildAppBackupRows()`, remove `settingsAppBackupHandler()`, updated `deployHandler()` validation |
|
1. Snapshot selector: Filter to show only snapshots containing user data paths when cross-drive is configured
|
||||||
| `internal/web/server.go` | Remove `POST /settings/app-backup` route |
|
2. Restore action: Restores both Docker volumes/configs AND user data in one operation
|
||||||
| `internal/web/templates/backups.html` | Complete rewrite of app backup sections → unified expandable rows |
|
3. Simplify warnings: Single clear warning about data overwrite, remove redundant ones
|
||||||
| `internal/web/templates/deploy.html` | Updated schedule dropdown, weekly note, tiered warnings, unconfigured warning |
|
4. DB-only restore: Not exposed in UI — support-level operation via CLI
|
||||||
| `internal/web/templates/style.css` | New styles for expandable rows, status dots, layer detail grid |
|
|
||||||
| `internal/settings/settings.go` | Deprecate `SetAppBackupBulk()`, `IsAppBackupEnabled()` |
|
**Note:** This is a design change, not a bugfix. Needs more detailed specification before implementation. Possibly v0.13.0 scope.
|
||||||
| `cmd/controller/main.go` | Sequential chaining in scheduler, startup drive check |
|
|
||||||
|
**Affected files:**
|
||||||
|
- `internal/web/handlers.go` — restore handler logic
|
||||||
|
- `internal/web/templates/backups.html` — restore section template
|
||||||
|
- `internal/backup/restore.go` — restore logic (if separate)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Design Decisions
|
## Bug 5: Restic key buttons barely readable
|
||||||
|
|
||||||
### Why merge the two backup sections?
|
**Severity:** Low (cosmetic)
|
||||||
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.
|
**Location:** Backup page, "Titkosítási kulcs" section (line 16660-16661 in `backups.html`)
|
||||||
|
|
||||||
### Why remove the BackupEnabled toggle?
|
The "Megjelenítés" and "Másolás" buttons use `class="btn btn-sm"` without a color variant. The `.btn` base class sets `color: #fff` (white text) but has **no background color**. The browser default background (light gray) makes white text nearly invisible on the dark theme.
|
||||||
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?
|
**Current HTML:**
|
||||||
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.
|
```html
|
||||||
|
<button type="button" class="btn btn-sm" onclick="toggleResticPw()">Megjelenítés</button>
|
||||||
|
<button type="button" class="btn btn-sm" onclick="copyResticPw()">Másolás</button>
|
||||||
|
```
|
||||||
|
|
||||||
### Why allow system drive as backup target with warning?
|
**Fix options:**
|
||||||
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.
|
A. **Add a default background to `.btn` base class** — e.g. `background: var(--card-bg);` or `background: rgba(255,255,255,0.1);` — This fixes all unclassed buttons globally
|
||||||
|
B. **Add variant class to these specific buttons** — e.g. `class="btn btn-sm btn-secondary"` with a defined `.btn-secondary` style
|
||||||
|
|
||||||
### Why "Hetente" restores also roll back the DB?
|
**Recommended: Option A** — add a sensible dark default to `.btn`:
|
||||||
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.
|
```css
|
||||||
|
.btn {
|
||||||
|
/* existing properties... */
|
||||||
|
background: rgba(255,255,255,0.1); /* subtle dark default */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This ensures any `btn` without a specific variant is still readable on dark backgrounds.
|
||||||
|
|
||||||
|
**Affected files:**
|
||||||
|
- `internal/web/templates/style.css` (or wherever `.btn` is defined) — add default background
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority / Implementation Order
|
||||||
|
|
||||||
|
1. **Bug 1** (version `vv`) — trivial template fix, ~2 min
|
||||||
|
2. **Bug 5** (button contrast) — trivial CSS fix, ~2 min
|
||||||
|
3. **Bug 2** (HDD stats wrong path) — 2-line fix in `primaryHDDPath()` + `main.go`, ~5 min
|
||||||
|
4. **Bug 3** (gray → green status) — small logic change, ~10 min
|
||||||
|
5. **Bug 4** (restore rethink) — design decision needed, deferred to v0.13.0
|
||||||
Reference in New Issue
Block a user