v0.11.8 — Per-App Cross-Drive Backup (3-2-1 rule)

New feature: backup app data to a secondary storage drive to satisfy
the "different media" requirement of the 3-2-1 backup rule.

- settings.go: CrossDriveBackup struct, AppBackupPrefs.CrossDrive field,
  getter/setter methods, GetOrCreateCrossDrivePassword, preserves
  cross-drive config when toggling nightly backup

- crossdrive.go (new): CrossDriveRunner with rsync and restic backends.
  Validates destination (mount point, writable), prevents source/dest
  overlap, per-app concurrency lock, persists last_run/status/size.

- main.go: wire CrossDriveRunner, register cross-drive-daily (03:30)
  and cross-drive-weekly (04:30 Sundays) scheduler jobs

- router.go: 4 new API endpoints — save config, trigger run, get status,
  run-all. Router now accepts Settings and CrossDriveRunner.

- server.go: Server struct accepts CrossDriveRunner, new web route
  POST /settings/cross-backup/{name}

- handlers.go: deployHandler populates CrossDriveConfig, BackupDestPaths,
  BackupDestWarning, AppBackupEnabled. settingsCrossBackupHandler saves
  config. backupsHandler builds CrossDriveSummary, UnconfiguredApps,
  CrossDriveWarnings for backup page.

- deploy.html: "Biztonsági mentés" card with destination/method/schedule
  dropdowns, last-run status, manual trigger button, flash messages.

- backups.html: "Másolatok másik meghajtóra" section with per-app
  status rows, unconfigured app warnings, "Összes futtatása most" button.

- style.css: margin-bottom fix for .deploy-stale-data, new cross-drive
  card and list styles.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 15:45:31 +01:00
parent d4da3e6ea2
commit 1a8036d055
12 changed files with 1126 additions and 44 deletions
@@ -2238,6 +2238,7 @@ a.stat-card:hover {
border-radius: var(--radius);
padding: 1.5rem;
margin-top: 1rem;
margin-bottom: 1.5rem;
}
.deploy-stale-data h4 {
@@ -2265,3 +2266,67 @@ a.stat-card:hover {
.btn-danger:hover {
opacity: 0.85;
}
/* Cross-drive backup card on deploy page */
.deploy-cross-drive {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.5rem;
margin-top: 1rem;
margin-bottom: 1rem;
}
.deploy-cross-drive h4 {
margin: 0 0 1rem 0;
color: var(--text-primary);
}
.cross-drive-nightly {
margin-bottom: .5rem;
}
/* Cross-drive list on backup page */
.cross-drive-list {
display: flex;
flex-direction: column;
gap: .5rem;
}
.cross-drive-item {
background: var(--bg-secondary);
border-radius: var(--radius);
padding: .75rem 1rem;
}
.cross-drive-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: .5rem;
}
.cross-drive-name {
font-weight: 500;
color: var(--text-primary);
text-decoration: none;
}
.cross-drive-name:hover {
text-decoration: underline;
color: var(--accent-blue);
}
.cross-drive-meta {
display: flex;
align-items: center;
gap: .4rem;
flex-wrap: wrap;
}
.cross-drive-actions {
display: flex;
gap: .5rem;
flex-wrap: wrap;
}