Files
AetherForge/server/internal/api/backup_handler.go
AetherForge d52479c9a6 feat: Telegram fleet alerts, forge sigil scramble, UI polish, agent ops
- Calibrate: per-event Telegram/SMTP toggles, test notification, chat ID help
- Notify on agent connect/reconnect, offline/hashrate/rejection, forge complete
- Sigil scramble post-forge uniquification and Dispense Reveal ceremony
- Full system check, desktop push, BITS/host-binary persistence, Path Tracer
- Dashboard/Crucible visual polish, haptics, sacred geometry, mobile nav
- README documents alerts, sigil scramble, and pack-usb workflow
- USB bundle repacked via pack-usb.bat (AetherForge.exe + synced agent source)
2026-06-03 20:32:59 -07:00

75 lines
2.1 KiB
Go

package api
import (
"archive/zip"
"bytes"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
)
// BackupHandler serves GET /api/v1/backup.
// It returns a zip containing config.json, users.json, miner.db, and a
// backup-info.txt with the timestamp and server version. The caller must
// already be authenticated via basicAuthMiddleware (registered in router.go).
type BackupHandler struct {
dataDir string
serverVersion string
}
// NewBackupHandler creates a BackupHandler for the given data directory.
func NewBackupHandler(dataDir, serverVersion string) *BackupHandler {
return &BackupHandler{dataDir: dataDir, serverVersion: serverVersion}
}
func (h *BackupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
now := time.Now().UTC()
dateTag := now.Format("2006-01-02")
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
// backup-info.txt
info := fmt.Sprintf("AetherForge Deck Backup\nTimestamp: %s\nVersion: %s\n",
now.Format(time.RFC3339), h.serverVersion)
if fw, err := zw.Create("backup-info.txt"); err == nil {
_, _ = fw.Write([]byte(info))
}
// Helper: add a file from disk into the zip, skip gracefully if missing.
addFile := func(name, diskPath string) {
data, err := os.ReadFile(diskPath)
if err != nil {
return
}
fw, err := zw.Create(name)
if err != nil {
return
}
_, _ = fw.Write(data)
}
addFile("config.json", filepath.Join(h.dataDir, "config.json"))
addFile("users.json", filepath.Join(h.dataDir, "users.json"))
addFile("miner.db", filepath.Join(h.dataDir, "miner.db"))
if err := zw.Close(); err != nil {
http.Error(w, "Failed to create backup zip", http.StatusInternalServerError)
return
}
filename := fmt.Sprintf("aetherforge-backup-%s.zip", dateTag)
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
w.Header().Set("Content-Length", fmt.Sprintf("%d", buf.Len()))
w.WriteHeader(http.StatusOK)
_, _ = w.Write(buf.Bytes())
}