Files
deploy-felhom-compose/TASK.md
T

405 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK.md — Tier 2 for All Apps + Status Dot Update (v0.12.9)
## Prompt (copy-paste this into Claude Code)
```
Read TASK.md for the full plan. Apply all code changes described, then build and deploy.
After all fixes are done:
1. Run `go build ./...` and `go vet ./...` from the controller/ directory — fix any errors
2. Update CHANGELOG.md with a new entry at the top (session 46, v0.12.9)
3. Commit, build, and deploy following the workflow in CLAUDE.md
```
---
## Context and Goals
Currently Tier 2 (cross-drive backup) is only available for apps with HDD data (Immich,
Paperless-ngx, etc.). Apps without HDD data (Mealie, Gokapi, etc.) cannot configure Tier 2
at all — the config section is hidden, the code rejects empty mounts, and the UI shows them
as "auto" (gray dot).
**Problem:** These apps have only 1 tier of protection. If the primary drive fails, their DB
and config are lost. The customer should be able to configure Tier 2 for ANY app.
**Changes in this version:**
1. **Tier 2 for all apps** — Remove all HDD-only gates. Non-HDD apps back up config + DB
dumps to the secondary drive (small, but protects against drive failure).
2. **Status dot update** — Remove "auto" (gray). All apps start as yellow (1 tier only).
Green requires 2+ tiers with successful backups.
3. **Tier 3 placeholder** — Show a disabled "3. mentés" row in the UI (future: remote backup).
4. **Deploy page** — Show cross-drive config form for ALL deployed apps, not just HDD ones.
**What non-HDD apps back up in Tier 2:**
- App with DB (e.g., Mealie): `_config/` + `_db/mealie_postgres.sql`
- App without DB (e.g., Gokapi): `_config/` only
- Small files — seconds to rsync/restic, but provides drive-failure protection.
---
## Fix 1: Remove empty-mounts gate from RunAppBackup
**File:** `internal/backup/crossdrive.go`
In `RunAppBackup()`, the code currently errors out when no HDD mounts exist (lines 98103):
```go
// CURRENT CODE — DELETE these 4 lines:
mounts := r.stackProvider.GetStackHDDMounts(stackName)
if len(mounts) == 0 {
r.updateStatus(stackName, "error", "no HDD data paths found for this app", time.Since(start), "")
return fmt.Errorf("no HDD data paths found for %s", stackName)
}
```
**Replace with:**
```go
// Resolve HDD mounts for this app (may be empty for config-only apps)
mounts := r.stackProvider.GetStackHDDMounts(stackName)
```
**Why this works:** The rest of the function already handles empty mounts correctly:
- Safety overlap check: empty loop = no overlap → passes
- `runRsyncBackup`: mount loop doesn't execute, but DB + config copy still runs
- `runResticBackup`: no mount paths appended, but config dir + DB dump dir still included
- Size calculation: destDir exists and can be measured even without mount data
---
## Fix 2: Update status dot logic + remove HasHDDData gates from handlers.go
**File:** `internal/web/handlers.go`
### 2a: Update `AppBackupRow` struct comments
In the `AppBackupRow` struct, update the Tier 2 comment:
```go
// Tier 2: Cross-drive backup (configurable for all apps)
```
(Remove the old "(only for apps with HDD data)" comment.)
### 2b: Rewrite `buildAppBackupRows` status + Tier2 section
Replace the current status + Tier2 block (lines 605672):
**CURRENT CODE:**
```go
// Default status = auto (no user data, just config)
row.Status = "auto"
row.StatusText = "Automatikus mentés"
if app.HasHDDData {
cfg, hasCfg := crossConfigs[app.StackName]
// ... full Tier2 block ...
}
```
**REPLACE WITH:**
```go
// Status dot — start as yellow (1 tier only)
row.Status = "yellow"
row.StatusText = "Csak helyi mentés (1 szint)"
cfg, hasCfg := crossConfigs[app.StackName]
if !hasCfg || cfg == nil || !cfg.Enabled {
// Only Tier 1 — no second copy
row.Tier2Configured = false
} else {
row.Tier2Configured = true
row.Tier2Method = cfg.Method
row.Tier2MethodLabel = cfg.Method // "rsync" or "restic"
row.Tier2Browsable = cfg.Method == "rsync"
row.Tier2Dest = destLabels[cfg.DestinationPath]
if row.Tier2Dest == "" {
row.Tier2Dest = cfg.DestinationPath
}
switch cfg.Schedule {
case "daily":
row.Tier2Schedule = "Naponta"
case "weekly":
row.Tier2Schedule = "Hetente"
default:
row.Tier2Schedule = cfg.Schedule
}
if cfg.LastRun != "" {
if t, err := time.Parse(time.RFC3339, cfg.LastRun); err == nil {
row.Tier2LastRun = t.In(loc).Format("01-02 15:04")
}
}
row.Tier2LastStatus = cfg.LastStatus
row.Tier2LastError = cfg.LastError
row.Tier2SizeHuman = cfg.LastSizeHuman
switch cfg.LastStatus {
case "ok":
row.Tier2StatusBadge = "Sikeres"
row.Status = "green"
row.StatusText = "Mentés rendben"
case "error":
row.Tier2StatusBadge = "Hiba"
// Status stays yellow
row.StatusText = "Utolsó mentés sikertelen"
case "running":
row.Tier2StatusBadge = "Fut..."
default:
row.Tier2StatusBadge = "—"
// Tier2 configured but never run — stay yellow
}
// Destination health check — can downgrade green to yellow/red
if cfg.DestinationPath != "" {
if err := s.crossDriveRunner.ValidateDestination(cfg.DestinationPath); err != nil {
if strings.Contains(err.Error(), "does not exist") || strings.Contains(err.Error(), "not writable") {
row.Status = "red"
row.StatusText = "Mentési cél nem elérhető"
} else if row.Status != "red" {
row.Status = "yellow"
row.StatusText = "Figyelmeztetés"
}
row.Warnings = append(row.Warnings, err.Error())
}
}
}
```
Note: `s.crossDriveRunner` is used instead of `s.crossDrive` — verify the field name
in `server.go` and use whatever the actual field is called. (The current code at the
old line 657 shows `s.crossDriveRunner.ValidateDestination`.)
### 2c: Remove HasHDDData gate from cross-drive summary builder
In `backupsHandler`, the cross-drive summary loop (around line 409) has:
```go
for _, app := range fullStatus.AppDataInfo {
if !app.HasHDDData {
continue
}
```
**Remove** the `if !app.HasHDDData { continue }` — all apps participate in cross-drive summary.
### 2d: Update the top-level warning logic
The "NoUserDataBackupWarning" check (around line 473) uses `HasHDDData`**keep this as-is**.
This warning is specifically about user data (photos, documents) being at risk, which only
applies to HDD apps. The status dot change already incentivizes Tier 2 for all apps.
---
## Fix 3: Update backup page template
**File:** `internal/web/templates/backups.html`
### 3a: Remove HasHDDData gate on Tier 2 row
**Find** (around line 284):
```html
{{if .HasHDDData}}
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
```
**Remove** the `{{if .HasHDDData}}` opening and its matching `{{end}}` (around line 313).
The Tier 2 row should always be shown for all apps.
### 3b: Update header meta badges
**Find** (around line 256261):
```html
{{if .HasHDDData}}
{{if .StorageLabel}}<span class="meta-badge meta-badge-storage">{{.StorageLabel}}</span>{{end}}
<span class="mono app-backup-size" style="font-size:.8rem">{{.HDDSizeHuman}}</span>
{{else}}
<span class="meta-badge">Auto</span>
{{end}}
```
**Replace with:**
```html
{{if .HasHDDData}}
{{if .StorageLabel}}<span class="meta-badge meta-badge-storage">{{.StorageLabel}}</span>{{end}}
<span class="mono app-backup-size" style="font-size:.8rem">{{.HDDSizeHuman}}</span>
{{else}}
<span class="meta-badge">Konfig{{if .HasDB}} + DB{{end}}</span>
{{end}}
```
This shows what type of data the app has (instead of meaningless "Auto").
### 3c: Add Tier 3 placeholder row
After the Tier 2 `</div>` (the closing div of the tier-2 backup-layer-row), add:
```html
<!-- Tier 3: Remote backup (future) -->
<div class="backup-layer-row" style="opacity:.5">
<span class="tier-label">3. mentés</span>
<span class="layer-badge" style="background:var(--bg-tertiary);color:var(--text-muted)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-muted)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
```
### 3d: Update "Run all" button text
**Find** (around line 328):
```html
<button class="btn btn-sm btn-outline" onclick="triggerAllCrossDrive(this)">Összes HDD mentés futtatása most</button>
```
**Replace with:**
```html
<button class="btn btn-sm btn-outline" onclick="triggerAllCrossDrive(this)">Összes 2. mentés futtatása most</button>
```
---
## Fix 4: Deploy page — show cross-drive config for all deployed apps
**File:** `internal/web/templates/deploy.html`
### 4a: Remove StorageInfo gate from cross-drive section
The cross-drive backup config section is currently double-gated (lines 95220):
```html
{{if .AlreadyDeployed}}
{{if .StorageInfo}} ← THIS IS THE GATE — remove it
<div class="deploy-cross-drive">
...
</div>
{{end}} ← remove matching end
{{end}}
```
**Change to** (keep only the AlreadyDeployed gate):
```html
{{if .AlreadyDeployed}}
<div class="deploy-cross-drive">
...
</div>
{{end}}
```
### 4b: Update section text for generality
**Find** (line 109):
```html
<p style="font-weight:500;margin-bottom:1rem">Másolat másik meghajtóra (felhasználói adatok):</p>
```
**Replace with:**
```html
<p style="font-weight:500;margin-bottom:1rem">2. mentés — másolat másik meghajtóra:</p>
```
**Find** (line 214216):
```html
<div class="form-hint" style="margin-top:.75rem;color:var(--text-muted)">
A cél meghajtó legyen más fizikai eszköz, mint az alkalmazás adattárolója.
</div>
```
**Replace with:**
```html
<div class="form-hint" style="margin-top:.75rem;color:var(--text-muted)">
A cél meghajtó legyen más fizikai eszköz a meghibásodás elleni védelem érdekében.
</div>
```
---
## Summary of all changes
| Fix | What | File(s) |
|-----|------|---------|
| 1 | Remove `len(mounts) == 0` error gate | `crossdrive.go` |
| 2a | Update `AppBackupRow` Tier2 comment | `handlers.go` |
| 2b | Rewrite status + Tier2 block (remove HasHDDData gate, new dot logic) | `handlers.go` |
| 2c | Remove HasHDDData gate from cross-drive summary | `handlers.go` |
| 3a | Remove `{{if .HasHDDData}}` around Tier 2 row | `backups.html` |
| 3b | Update meta badges ("Auto" → "Konfig + DB") | `backups.html` |
| 3c | Add Tier 3 placeholder row | `backups.html` |
| 3d | Rename "Összes HDD mentés" → "Összes 2. mentés" | `backups.html` |
| 4a | Remove `{{if .StorageInfo}}` gate from cross-drive section | `deploy.html` |
| 4b | Update cross-drive section text for generality | `deploy.html` |
## Files to modify (4)
1. `internal/backup/crossdrive.go` — Fix 1
2. `internal/web/handlers.go` — Fix 2a + 2b + 2c
3. `internal/web/templates/backups.html` — Fix 3a + 3b + 3c + 3d
4. `internal/web/templates/deploy.html` — Fix 4a + 4b
## Status dot logic after fix
| Dot color | Meaning |
|-----------|---------|
| Green | 2+ tiers with successful backups + destination healthy |
| Yellow | Only 1 tier, or Tier 2 failing, or Tier 2 configured but never run |
| Red | Tier 2 destination blocked/inaccessible |
**"auto" (gray) is removed.** Every app now shows yellow or better.
## Architecture after fix
```
Per-app Tier 2 availability:
┌──────────────────────────────────────────────────────────┐
│ App type │ Tier 1 │ Tier 2 (new) │
│───────────────────│───────────────────│──────────────────│
│ HDD + DB │ Config+DB+Data │ Config+DB+Data │
│ HDD, no DB │ Config+Data │ Config+Data │
│ DB, no HDD │ Config+DB │ Config+DB (new!) │
│ Config only │ Config │ Config (new!) │
└──────────────────────────────────────────────────────────┘
UI per-app display after fix:
┌─────────────────────────────────────────────────────────────┐
│ 🟢 Immich Külső tárhely (hdd_1) 63.9 MB │
│ 1. mentés Auto helyi 02-18 03:00 ✓ DB+Konfig+Adatok │
│ 2. mentés rsync → hdd_1 Naponta Sikeres 📁 │
│ 3. mentés Hamarosan távoli (offsite) │
├─────────────────────────────────────────────────────────────┤
│ 🟡 Mealie Konfig + DB │
│ 1. mentés Auto helyi 02-18 03:00 ✓ DB+Konfig │
│ 2. mentés ✓ 1. mentés auto ⚠ Nincs 2. másolat │
│ 3. mentés Hamarosan távoli (offsite) │
├─────────────────────────────────────────────────────────────┤
│ 🟡 Gokapi Konfig │
│ 1. mentés Auto helyi 02-18 03:00 ✓ Konfig │
│ 2. mentés ⚠ Nincs 2. másolat [Beállítás →] │
│ 3. mentés Hamarosan távoli (offsite) │
└─────────────────────────────────────────────────────────────┘
```
## Post-fix checklist
- [ ] `go build ./...` passes
- [ ] `go vet ./...` passes
- [ ] Verify no references to old "auto" status remain in handlers.go
- [ ] Verify no template references to removed `{{if .HasHDDData}}` gate on Tier 2
- [ ] Update `CHANGELOG.md` — session 46, version **v0.12.9**:
- Tier 2 cross-drive backup now configurable for ALL apps (not just HDD apps)
- Non-HDD apps back up config + DB dumps to secondary drive
- Status dot: removed "auto" (gray) — all apps start yellow, green requires 2+ tiers
- Tier 3 placeholder row shown in UI
- Deploy page: cross-drive config form visible for all deployed apps
- Updated button text "Összes 2. mentés futtatása most"
- [ ] Commit, build on 192.168.0.180, deploy on 192.168.0.162
- [ ] Verify with `docker ps` and `docker logs`
- [ ] After deploy, verify:
- Immich: green dot (Tier 2 configured + successful backup)
- Mealie: yellow dot with "Csak helyi mentés (1 szint)"
- Mealie: Tier 2 row shown with "⚠ Nincs 2. másolat" + "Beállítás →" link
- Mealie deploy page: cross-drive config form visible
- Configure Tier 2 for Mealie → run manual backup → verify dot turns green
- Tier 3 placeholder row shown for all apps (grayed out "Hamarosan")
- Gokapi: yellow dot, Tier 2 configurable