controller v0.93.0: NAS Part B off-box backup target (restic-over-SFTP)

Encrypted restic repo over SFTP for the app-data tier (the off-site 3-2-1 leg). A dead
NAS fails fast via -oConnectTimeout (spike Q8), never hangs the runner; secrets are 0600
files (ride DR via PBS whole-CT); init-if-absent, retention forget --prune, restore,
single-flight, per-app toggle + UI. restic re-added to the image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
2026-06-30 15:26:38 +02:00
parent ddeb509d1d
commit 2a7deadc93
11 changed files with 1059 additions and 0 deletions
+9
View File
@@ -286,6 +286,15 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.storageWizardPageHandler(w, r, "storage_attach")
case path == "/backup/restore" && r.Method == http.MethodPost:
s.backupRestoreHandler(w, r)
// Off-box (NAS) restic-SFTP backup (Part B)
case path == "/backup/offbox/config" && r.Method == http.MethodPost:
s.offboxConfigHandler(w, r)
case path == "/backup/offbox/toggle" && r.Method == http.MethodPost:
s.offboxToggleHandler(w, r)
case path == "/backup/offbox/run" && r.Method == http.MethodPost:
s.offboxRunHandler(w, r)
case path == "/backup/offbox/restore" && r.Method == http.MethodPost:
s.offboxRestoreHandler(w, r)
case strings.HasPrefix(path, "/stacks/") && strings.HasSuffix(path, "/export"):
name := strings.TrimPrefix(path, "/stacks/")
name = strings.TrimSuffix(name, "/export")