fix: deep bug hunt II — concurrency, security & optimization (25 files)

Critical: watchdog mutex panic safety, SetGeoAppOverride nil guard,
SSD-only app DB restore fallback.

High: double deploy race (atomic Deploying flag), delete/remove during
deploy guard, ScanStacks overwrite protection, FileBrowser mount mutex,
PushEvent history, PushOnce error handling, DB dump sync+close before
rename, restic retry fresh context, encrypt failure logging, cross-backup
path traversal validation, deepCopyStack completeness.

Security: constant-time API key comparison, login rate limiting (5/min),
git credential masking in logs, storage path prefix traversal fix.

Concurrency: MigrateEncryption lock ordering, SubdomainInUse I/O outside
lock, scheduler late-registered jobs, SQLite WAL verification, metrics
shutdown context, telemetry scan error logging, asset sync lock scope.

Optimization: streaming file copy for DB dumps, restic stats dedup,
atomic infra config copy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 14:21:09 +01:00
parent 72ab145b41
commit db83db383c
25 changed files with 930 additions and 626 deletions
+25 -2
View File
@@ -68,6 +68,7 @@ type DriveRepoInfo struct {
TotalSize string
TotalSizeBytes int64
SnapshotCount int
LatestSnapshot *SnapshotInfo `json:"-"` // used for aggregation, not serialized
}
// CrossDriveSummaryItem holds display data for one app's cross-drive backup.
@@ -860,11 +861,33 @@ func (m *Manager) perDriveRepoStats() []DriveRepoInfo {
TotalSize: stats.TotalSize,
TotalSizeBytes: stats.TotalSizeBytes,
SnapshotCount: stats.SnapshotCount,
LatestSnapshot: stats.LatestSnapshot,
})
}
return infos
}
// aggregateFromDriveStats derives aggregate stats from already-computed per-drive stats,
// avoiding a second round of restic subprocess calls.
func aggregateFromDriveStats(drives []DriveRepoInfo, m *Manager) *RepoStats {
agg := &RepoStats{}
var totalBytes int64
for _, d := range drives {
agg.SnapshotCount += d.SnapshotCount
totalBytes += d.TotalSizeBytes
if d.LatestSnapshot != nil {
if agg.LatestSnapshot == nil || d.LatestSnapshot.Time.After(agg.LatestSnapshot.Time) {
agg.LatestSnapshot = d.LatestSnapshot
}
}
}
agg.TotalSizeBytes = totalBytes
if totalBytes > 0 {
agg.TotalSize = humanizeBytes(totalBytes)
}
return agg
}
// aggregateRepoStats combines stats from all primary restic repos.
func (m *Manager) aggregateRepoStats() *RepoStats {
drives := m.activeDrives()
@@ -1066,9 +1089,9 @@ func (m *Manager) RefreshCache(nextDBDump, nextBackup time.Time) {
Retention: m.cfg.Backup.Retention,
}
// Expensive calls (outside lock)
status.RepoStats = m.aggregateRepoStats()
// Expensive calls (outside lock) — compute per-drive stats once, derive aggregate
status.PerDriveRepoStats = m.perDriveRepoStats()
status.RepoStats = aggregateFromDriveStats(status.PerDriveRepoStats, m)
// Scan dump files from per-drive per-stack paths
files := m.listAllDumpFiles()