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:
@@ -14,7 +14,6 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appexport"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/monitor"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/report"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
@@ -23,13 +22,11 @@ import (
|
||||
|
||||
// DebugCallbacks holds functions that need main.go wiring (modules not directly on Server).
|
||||
type DebugCallbacks struct {
|
||||
TriggerHubReportPush func() error
|
||||
TriggerHubInfraPush func() error
|
||||
TriggerLocalInfraWrite func() error
|
||||
TriggerSetupMode func() error
|
||||
HubConnectivityTest func() (statusCode int, latencyMs int64, err error)
|
||||
GiteaConnectivityTest func() (statusCode int, latencyMs int64, err error)
|
||||
GetTelemetryPreview func() ([]report.AppTelemetry, error)
|
||||
TriggerHubReportPush func() error
|
||||
TriggerSetupMode func() error
|
||||
HubConnectivityTest func() (statusCode int, latencyMs int64, err error)
|
||||
GiteaConnectivityTest func() (statusCode int, latencyMs int64, err error)
|
||||
GetTelemetryPreview func() ([]report.AppTelemetry, error)
|
||||
}
|
||||
|
||||
// debugPageHandler renders the debug dashboard page.
|
||||
@@ -53,29 +50,13 @@ func (s *Server) handleDebugAPI(w http.ResponseWriter, r *http.Request) {
|
||||
case subpath == "event/history" && r.Method == http.MethodGet:
|
||||
s.debugEventHistory(w, r)
|
||||
|
||||
// Section 3: Backup testing
|
||||
// Section 3: Backup testing (app-data only; disk-tier moved to host agent)
|
||||
case subpath == "backup/dbdump" && r.Method == http.MethodPost:
|
||||
s.debugTriggerDBDump(w, r)
|
||||
case subpath == "backup/crossdrive" && r.Method == http.MethodPost:
|
||||
s.debugTriggerCrossDrive(w, r)
|
||||
case subpath == "backup/integrity" && r.Method == http.MethodPost:
|
||||
s.debugTriggerIntegrity(w, r)
|
||||
case subpath == "backup/infra" && r.Method == http.MethodPost:
|
||||
s.debugTriggerInfraBackup(w, r)
|
||||
|
||||
// Section 4: Storage simulation
|
||||
case subpath == "storage/simulate-disconnect" && r.Method == http.MethodPost:
|
||||
s.debugSimulateDisconnect(w, r)
|
||||
case subpath == "storage/simulate-reconnect" && r.Method == http.MethodPost:
|
||||
s.debugSimulateReconnect(w, r)
|
||||
case subpath == "storage/watchdog-status" && r.Method == http.MethodGet:
|
||||
s.debugWatchdogStatus(w, r)
|
||||
|
||||
// Section 5: Hub & connectivity
|
||||
case subpath == "hub/push" && r.Method == http.MethodPost:
|
||||
s.debugHubPush(w, r)
|
||||
case subpath == "hub/infra-push" && r.Method == http.MethodPost:
|
||||
s.debugHubInfraPush(w, r)
|
||||
case subpath == "hub/test-connectivity" && r.Method == http.MethodPost:
|
||||
s.debugHubConnectivity(w, r)
|
||||
case subpath == "hub/preferences-sync" && r.Method == http.MethodPost:
|
||||
@@ -94,8 +75,6 @@ func (s *Server) handleDebugAPI(w http.ResponseWriter, r *http.Request) {
|
||||
// Section 7: DR / Setup
|
||||
case subpath == "dr/trigger-setup" && r.Method == http.MethodPost:
|
||||
s.debugTriggerSetupWizard(w, r)
|
||||
case subpath == "dr/infra-status" && r.Method == http.MethodGet:
|
||||
s.debugInfraBackupStatus(w, r)
|
||||
|
||||
// Section 8: Log viewer
|
||||
case subpath == "logs" && r.Method == http.MethodGet:
|
||||
@@ -219,23 +198,13 @@ func (s *Server) debugDump(w http.ResponseWriter, r *http.Request) {
|
||||
"enabled": true,
|
||||
"running": s.backupMgr.IsRunning(),
|
||||
}
|
||||
dbDump, backupSt := s.backupMgr.GetStatus()
|
||||
dbDump := s.backupMgr.GetStatus()
|
||||
if dbDump != nil {
|
||||
backupInfo["last_db_dump"] = map[string]interface{}{
|
||||
"time": dbDump.LastRun,
|
||||
"success": dbDump.Success,
|
||||
}
|
||||
}
|
||||
if backupSt != nil {
|
||||
backupInfo["last_backup"] = map[string]interface{}{
|
||||
"time": backupSt.LastRun,
|
||||
"success": backupSt.Success,
|
||||
}
|
||||
if backupSt.RepoStats != nil {
|
||||
backupInfo["repo_size"] = backupSt.RepoStats.TotalSize
|
||||
backupInfo["snapshot_count"] = backupSt.RepoStats.SnapshotCount
|
||||
}
|
||||
}
|
||||
dump["backup"] = backupInfo
|
||||
} else {
|
||||
dump["backup"] = map[string]interface{}{"enabled": false}
|
||||
@@ -378,98 +347,6 @@ func (s *Server) debugTriggerDBDump(w http.ResponseWriter, r *http.Request) {
|
||||
writeDebugJSON(w, http.StatusOK, true, "DB dump elindítva", nil)
|
||||
}
|
||||
|
||||
func (s *Server) debugTriggerCrossDrive(w http.ResponseWriter, r *http.Request) {
|
||||
if s.crossDriveRunner == nil {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false, "Cross-drive runner nincs konfigurálva", nil)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := s.crossDriveRunner.RunAllConfigured(context.Background()); err != nil {
|
||||
s.logger.Printf("[WARN] Debug cross-drive failed: %v", err)
|
||||
}
|
||||
}()
|
||||
writeDebugJSON(w, http.StatusOK, true, "Cross-drive mentés elindítva", nil)
|
||||
}
|
||||
|
||||
func (s *Server) debugTriggerIntegrity(w http.ResponseWriter, r *http.Request) {
|
||||
if s.backupMgr == nil {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false, "Backup manager nincs konfigurálva", nil)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := s.backupMgr.RunIntegrityCheck(context.Background()); err != nil {
|
||||
s.logger.Printf("[WARN] Debug integrity check failed: %v", err)
|
||||
}
|
||||
}()
|
||||
writeDebugJSON(w, http.StatusOK, true, "Integritás ellenőrzés elindítva", nil)
|
||||
}
|
||||
|
||||
func (s *Server) debugTriggerInfraBackup(w http.ResponseWriter, r *http.Request) {
|
||||
if s.debugCallbacks == nil || s.debugCallbacks.TriggerLocalInfraWrite == nil {
|
||||
writeDebugJSON(w, http.StatusNotImplemented, false, "Nem bekötött", nil)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := s.debugCallbacks.TriggerLocalInfraWrite(); err != nil {
|
||||
s.logger.Printf("[WARN] Debug infra backup failed: %v", err)
|
||||
}
|
||||
}()
|
||||
writeDebugJSON(w, http.StatusOK, true, "Infra mentés elindítva", nil)
|
||||
}
|
||||
|
||||
// ── Section 4: Storage simulation ───────────────────────────────────
|
||||
|
||||
func (s *Server) debugSimulateDisconnect(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Path == "" {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false, "Érvénytelen kérés: path szükséges", nil)
|
||||
return
|
||||
}
|
||||
if s.storageWatchdog == nil {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false, "Storage watchdog nincs konfigurálva", nil)
|
||||
return
|
||||
}
|
||||
stopped, err := s.storageWatchdog.SimulateDisconnect(r.Context(), req.Path)
|
||||
if err != nil {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
writeDebugJSON(w, http.StatusOK, true,
|
||||
fmt.Sprintf("Leválasztás szimulálva: %s (%d app leállítva)", req.Path, len(stopped)),
|
||||
map[string]interface{}{"stopped_stacks": stopped})
|
||||
}
|
||||
|
||||
func (s *Server) debugSimulateReconnect(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Path == "" {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false, "Érvénytelen kérés: path szükséges", nil)
|
||||
return
|
||||
}
|
||||
if s.storageWatchdog == nil {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false, "Storage watchdog nincs konfigurálva", nil)
|
||||
return
|
||||
}
|
||||
if err := s.storageWatchdog.SimulateReconnect(r.Context(), req.Path); err != nil {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
writeDebugJSON(w, http.StatusOK, true,
|
||||
fmt.Sprintf("Visszacsatlakozás szimulálva: %s", req.Path), nil)
|
||||
}
|
||||
|
||||
func (s *Server) debugWatchdogStatus(w http.ResponseWriter, r *http.Request) {
|
||||
if s.storageWatchdog == nil {
|
||||
writeDebugJSON(w, http.StatusOK, true, "", []interface{}{})
|
||||
return
|
||||
}
|
||||
status := s.storageWatchdog.GetDebugStatus()
|
||||
writeDebugJSON(w, http.StatusOK, true, "", status)
|
||||
}
|
||||
|
||||
// ── Section 5: Hub & connectivity ───────────────────────────────────
|
||||
|
||||
func (s *Server) debugHubPush(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -488,22 +365,6 @@ func (s *Server) debugHubPush(w http.ResponseWriter, r *http.Request) {
|
||||
map[string]interface{}{"latency_ms": latency})
|
||||
}
|
||||
|
||||
func (s *Server) debugHubInfraPush(w http.ResponseWriter, r *http.Request) {
|
||||
if s.debugCallbacks == nil || s.debugCallbacks.TriggerHubInfraPush == nil {
|
||||
writeDebugJSON(w, http.StatusNotImplemented, false, "Nem bekötött", nil)
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
err := s.debugCallbacks.TriggerHubInfraPush()
|
||||
latency := time.Since(start).Milliseconds()
|
||||
if err != nil {
|
||||
writeDebugJSON(w, http.StatusOK, false, err.Error(), map[string]interface{}{"latency_ms": latency})
|
||||
return
|
||||
}
|
||||
writeDebugJSON(w, http.StatusOK, true, "Infra backup elküldve a Hubra",
|
||||
map[string]interface{}{"latency_ms": latency})
|
||||
}
|
||||
|
||||
func (s *Server) debugHubConnectivity(w http.ResponseWriter, r *http.Request) {
|
||||
if s.debugCallbacks == nil || s.debugCallbacks.HubConnectivityTest == nil {
|
||||
writeDebugJSON(w, http.StatusNotImplemented, false, "Nem bekötött", nil)
|
||||
@@ -613,13 +474,6 @@ func (s *Server) debugTriggerSetupWizard(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// Pre-check: verify infra backup exists on at least one drive
|
||||
if !s.hasInfraBackupOnDrive() {
|
||||
writeDebugJSON(w, http.StatusBadRequest, false,
|
||||
"Nincs infra backup egyetlen meghajtón sem! Először készítsen infra backupot.", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Write marker file
|
||||
markerPath := filepath.Join(s.cfg.Paths.DataDir, ".needs-setup")
|
||||
if err := os.WriteFile(markerPath, []byte("debug-triggered\n"), 0644); err != nil {
|
||||
@@ -637,67 +491,6 @@ func (s *Server) debugTriggerSetupWizard(w http.ResponseWriter, r *http.Request)
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Server) debugInfraBackupStatus(w http.ResponseWriter, r *http.Request) {
|
||||
storagePaths := s.settings.GetStoragePaths()
|
||||
drives := make([]map[string]interface{}, 0, len(storagePaths))
|
||||
|
||||
for _, sp := range storagePaths {
|
||||
if sp.Decommissioned || sp.Disconnected {
|
||||
continue
|
||||
}
|
||||
driveInfo := map[string]interface{}{
|
||||
"path": sp.Path,
|
||||
"label": sp.Label,
|
||||
"has_backup": false,
|
||||
}
|
||||
|
||||
infraDir := backup.InfraBackupDir(sp.Path)
|
||||
info, err := os.Stat(infraDir)
|
||||
if err == nil && info.IsDir() {
|
||||
driveInfo["has_backup"] = true
|
||||
driveInfo["last_modified"] = info.ModTime()
|
||||
|
||||
// List files
|
||||
entries, _ := os.ReadDir(infraDir)
|
||||
files := make([]string, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
files = append(files, e.Name())
|
||||
}
|
||||
driveInfo["files"] = files
|
||||
}
|
||||
|
||||
drives = append(drives, driveInfo)
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"drives": drives,
|
||||
}
|
||||
if s.hubPushStatusFn != nil {
|
||||
st := s.hubPushStatusFn()
|
||||
data["hub_infra_push"] = map[string]interface{}{
|
||||
"last_attempt": st.LastAttempt,
|
||||
"last_success": st.LastSuccess,
|
||||
"last_error": st.LastError,
|
||||
}
|
||||
}
|
||||
|
||||
writeDebugJSON(w, http.StatusOK, true, "", data)
|
||||
}
|
||||
|
||||
// hasInfraBackupOnDrive checks if any connected storage drive has an infra backup.
|
||||
func (s *Server) hasInfraBackupOnDrive() bool {
|
||||
for _, sp := range s.settings.GetStoragePaths() {
|
||||
if sp.Decommissioned || sp.Disconnected {
|
||||
continue
|
||||
}
|
||||
infraDir := backup.InfraBackupDir(sp.Path)
|
||||
if info, err := os.Stat(infraDir); err == nil && info.IsDir() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ── Section 8: Log viewer ───────────────────────────────────────────
|
||||
|
||||
func (s *Server) debugLogBuffer(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user