slice 8C Phase B.2 + C.1/C.2: retire disk subsystem + rewire disk mgmt to agent
Retired (~12.3k LOC): internal/storage/* (scan/format/attach/migrate/safety), backup restic/crossdrive/restore_drives/disk_layout/local_infra/restore_scan/ paths + restore_app, report/infra_backup*/infra_pull, setup/scanner, monitor/watchdog+pinger, web/storage_handlers+handler_restore. Surgically split backup.Manager to app-data only (DB dumps + volume tars + app restore; dropped restic + cross-drive + snapshot history). Fixed router/main/web wiring. Added agent-backed disk API (web/agent_disk_handlers.go): /api/disks list/ assign/eject/format proxying agentapi; data-bearing format refusal -> HTTP 409 'operator authorization required'. report/config_pull.go keeps the setup fresh-install config download. go build + go test green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,31 +17,28 @@ import (
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/integrations"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/monitor"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/notify"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/scheduler"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/selfupdate"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/storage"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
cfg *config.Config
|
||||
stackMgr *stacks.Manager
|
||||
cpuCollector *system.CPUCollector
|
||||
backupMgr *backup.Manager
|
||||
crossDriveRunner *backup.CrossDriveRunner
|
||||
scheduler *scheduler.Scheduler
|
||||
settings *settings.Settings
|
||||
alertManager *AlertManager
|
||||
notifier *notify.Notifier
|
||||
updater *selfupdate.Updater
|
||||
logger *log.Logger
|
||||
version string
|
||||
encKey []byte // AES-256 key for decrypting app.yaml values
|
||||
tmpl *template.Template
|
||||
cfg *config.Config
|
||||
stackMgr *stacks.Manager
|
||||
cpuCollector *system.CPUCollector
|
||||
backupMgr *backup.Manager
|
||||
scheduler *scheduler.Scheduler
|
||||
settings *settings.Settings
|
||||
alertManager *AlertManager
|
||||
notifier *notify.Notifier
|
||||
updater *selfupdate.Updater
|
||||
logger *log.Logger
|
||||
version string
|
||||
encKey []byte // AES-256 key for decrypting app.yaml values
|
||||
tmpl *template.Template
|
||||
|
||||
sessions map[string]*session
|
||||
sessionsMu sync.RWMutex
|
||||
@@ -50,26 +47,9 @@ type Server struct {
|
||||
done chan struct{}
|
||||
closeOnce sync.Once
|
||||
|
||||
// Disk operation state (format/migrate jobs)
|
||||
diskJobMu sync.Mutex
|
||||
diskJob *activeDiskJob
|
||||
|
||||
// Active raw mount for the attach wizard (empty when not in use)
|
||||
activeRawMount string
|
||||
|
||||
// Guard for FileBrowser sync — prevents concurrent file writes (H5 fix)
|
||||
fileBrowserMu sync.Mutex
|
||||
|
||||
// Drive migration
|
||||
driveMigrator *storage.DriveMigrator
|
||||
|
||||
// DR restore mode state
|
||||
restoreMu sync.RWMutex
|
||||
restorePlan *backup.RestorePlan
|
||||
|
||||
// Storage watchdog (set after construction to break init ordering)
|
||||
storageWatchdog *monitor.StorageWatchdog
|
||||
|
||||
// Hub push status callback — set via SetHubPushStatus for monitoring page
|
||||
hubPushStatusFn func() HubPushStatusData
|
||||
|
||||
@@ -88,29 +68,28 @@ type Server struct {
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func NewServer(cfg *config.Config, stackMgr *stacks.Manager, cpuCollector *system.CPUCollector, backupMgr *backup.Manager, crossDrive *backup.CrossDriveRunner, sched *scheduler.Scheduler, sett *settings.Settings, alertMgr *AlertManager, notif *notify.Notifier, updater *selfupdate.Updater, logger *log.Logger, version string) *Server {
|
||||
func NewServer(cfg *config.Config, stackMgr *stacks.Manager, cpuCollector *system.CPUCollector, backupMgr *backup.Manager, sched *scheduler.Scheduler, sett *settings.Settings, alertMgr *AlertManager, notif *notify.Notifier, updater *selfupdate.Updater, logger *log.Logger, version string) *Server {
|
||||
s := &Server{
|
||||
cfg: cfg,
|
||||
stackMgr: stackMgr,
|
||||
cpuCollector: cpuCollector,
|
||||
backupMgr: backupMgr,
|
||||
crossDriveRunner: crossDrive,
|
||||
scheduler: sched,
|
||||
settings: sett,
|
||||
alertManager: alertMgr,
|
||||
notifier: notif,
|
||||
updater: updater,
|
||||
logger: logger,
|
||||
version: version,
|
||||
sessions: make(map[string]*session),
|
||||
loginAttempts: make(map[string]*loginAttempt),
|
||||
done: make(chan struct{}),
|
||||
cfg: cfg,
|
||||
stackMgr: stackMgr,
|
||||
cpuCollector: cpuCollector,
|
||||
backupMgr: backupMgr,
|
||||
scheduler: sched,
|
||||
settings: sett,
|
||||
alertManager: alertMgr,
|
||||
notifier: notif,
|
||||
updater: updater,
|
||||
logger: logger,
|
||||
version: version,
|
||||
sessions: make(map[string]*session),
|
||||
loginAttempts: make(map[string]*loginAttempt),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
if cfg.Logging.Level == "debug" {
|
||||
logger.Printf("[DEBUG] [web] NewServer: initializing web server v%s", version)
|
||||
logger.Printf("[DEBUG] [web] NewServer: backup=%v crossDrive=%v scheduler=%v alertMgr=%v notifier=%v updater=%v",
|
||||
backupMgr != nil, crossDrive != nil, sched != nil, alertMgr != nil, notif != nil, updater != nil)
|
||||
logger.Printf("[DEBUG] [web] NewServer: backup=%v scheduler=%v alertMgr=%v notifier=%v updater=%v",
|
||||
backupMgr != nil, sched != nil, alertMgr != nil, notif != nil, updater != nil)
|
||||
}
|
||||
|
||||
s.loadTemplates()
|
||||
@@ -155,23 +134,6 @@ func (s *Server) loadTemplates() {
|
||||
}
|
||||
}
|
||||
|
||||
// SetRestoreState puts the server into DR restore mode with the given plan.
|
||||
func (s *Server) SetRestoreState(plan *backup.RestorePlan) {
|
||||
s.restoreMu.Lock()
|
||||
defer s.restoreMu.Unlock()
|
||||
s.restorePlan = plan
|
||||
}
|
||||
|
||||
// SetStorageWatchdog sets the storage watchdog for disconnect/reconnect operations.
|
||||
func (s *Server) SetStorageWatchdog(w *monitor.StorageWatchdog) {
|
||||
s.storageWatchdog = w
|
||||
}
|
||||
|
||||
// SetDriveMigrator sets the drive migration engine for full drive migration.
|
||||
func (s *Server) SetDriveMigrator(dm *storage.DriveMigrator) {
|
||||
s.driveMigrator = dm
|
||||
}
|
||||
|
||||
// HubPushStatusData holds hub push status for the monitoring page.
|
||||
type HubPushStatusData struct {
|
||||
LastAttempt time.Time
|
||||
@@ -230,13 +192,6 @@ func (s *Server) ServeDebugAPI(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleDebugAPI(w, r)
|
||||
}
|
||||
|
||||
// InRestoreMode returns true if the server is in DR restore mode.
|
||||
func (s *Server) InRestoreMode() bool {
|
||||
s.restoreMu.RLock()
|
||||
defer s.restoreMu.RUnlock()
|
||||
return s.restorePlan != nil
|
||||
}
|
||||
|
||||
// ServeHTTP handles all non-API web requests.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
@@ -245,30 +200,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.logger.Printf("[DEBUG] [web] ServeHTTP: %s %s from %s", r.Method, path, r.RemoteAddr)
|
||||
}
|
||||
|
||||
// DR restore mode: intercept all routes except restore page, static, and restore API
|
||||
if s.InRestoreMode() {
|
||||
switch {
|
||||
case path == "/restore":
|
||||
s.restorePageHandler(w, r)
|
||||
return
|
||||
case path == "/api/restore/status":
|
||||
s.apiRestoreStatus(w, r)
|
||||
return
|
||||
case path == "/api/restore/all" && r.Method == http.MethodPost:
|
||||
s.apiRestoreAll(w, r)
|
||||
return
|
||||
case path == "/api/restore/skip" && r.Method == http.MethodPost:
|
||||
s.apiRestoreSkip(w, r)
|
||||
return
|
||||
case strings.HasPrefix(path, "/static/"):
|
||||
// Allow static assets through
|
||||
default:
|
||||
// Redirect everything else to the restore page
|
||||
http.Redirect(w, r, "/restore", http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case path == "/" || path == "/dashboard":
|
||||
s.dashboardHandler(w, r)
|
||||
@@ -296,25 +227,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.settingsStorageSchedulableHandler(w, r)
|
||||
case path == "/settings/storage/label" && r.Method == http.MethodPost:
|
||||
s.settingsStorageLabelHandler(w, r)
|
||||
case strings.HasPrefix(path, "/settings/cross-backup/") && r.Method == http.MethodPost:
|
||||
name := strings.TrimPrefix(path, "/settings/cross-backup/")
|
||||
s.settingsCrossBackupHandler(w, r, name)
|
||||
case path == "/backup/restore" && r.Method == http.MethodPost:
|
||||
s.backupRestoreHandler(w, r)
|
||||
case path == "/settings/storage/init":
|
||||
s.storageInitHandler(w, r)
|
||||
case path == "/settings/storage/attach":
|
||||
s.storageAttachHandler(w, r)
|
||||
case path == "/settings/storage/migrate-drive":
|
||||
s.migrateDrivePageHandler(w, r)
|
||||
case strings.HasPrefix(path, "/stacks/") && strings.HasSuffix(path, "/export"):
|
||||
name := strings.TrimPrefix(path, "/stacks/")
|
||||
name = strings.TrimSuffix(name, "/export")
|
||||
s.exportPageHandler(w, r, name)
|
||||
case strings.HasPrefix(path, "/stacks/") && strings.HasSuffix(path, "/migrate"):
|
||||
name := strings.TrimPrefix(path, "/stacks/")
|
||||
name = strings.TrimSuffix(name, "/migrate")
|
||||
s.migratePageHandler(w, r, name)
|
||||
case strings.HasPrefix(path, "/stacks/") && strings.HasSuffix(path, "/logs"):
|
||||
name := strings.TrimPrefix(path, "/stacks/")
|
||||
name = strings.TrimSuffix(name, "/logs")
|
||||
@@ -440,14 +358,6 @@ func (s *Server) findStackBySubdomain(subdomain string) (*stacks.Stack, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ServeStorageAPI handles /api/storage/* routes (JSON API for disk operations).
|
||||
func (s *Server) ServeStorageAPI(w http.ResponseWriter, r *http.Request) {
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [web] ServeStorageAPI: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
|
||||
}
|
||||
s.storageAPIHandler(w, r)
|
||||
}
|
||||
|
||||
// primaryHDDPath returns the default storage path, or the legacy config value.
|
||||
func (s *Server) primaryHDDPath() string {
|
||||
if p := s.settings.GetDefaultStoragePath(); p != "" {
|
||||
|
||||
Reference in New Issue
Block a user