controller v0.49.0: slice 10 P2 activation — pending-drive detection + restart button
pendingActivationDrives() flags registered drives the agent shows attached but not live-mounted in the container; settings banner + "Újraindítás most" button → /api/storage/activate → agentapi.GuestReboot. Batches all pending into one restart. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
|
||||
)
|
||||
|
||||
// Guided storage provisioning (rebuilt on the agent-delegated disk model). The controller is a thin
|
||||
@@ -153,6 +154,44 @@ func (s *Server) runStorageAttach(ctx context.Context, agent diskAgent, device,
|
||||
return storageInitResult{Registered: true, Where: where}, nil
|
||||
}
|
||||
|
||||
// pendingActivationDrives returns registered storage paths that are NOT yet live-mounted in this
|
||||
// container but whose backing drive the agent reports present+attached — enrolled drives waiting for
|
||||
// the guest restart that activates their bind (slice 10 P2; the host-side live inject is blocked on an
|
||||
// unprivileged guest). The customer activates them with the "Újraindítás most" button (one restart
|
||||
// batches all). Best-effort: agent unreachable → none.
|
||||
func (s *Server) pendingActivationDrives() []string {
|
||||
paths := s.settings.GetStoragePaths()
|
||||
if len(paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
agent, err := s.agentClient()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
resp, err := agent.Disks(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
attached := map[string]bool{}
|
||||
for _, d := range resp.Disks {
|
||||
if d.MountPath != "" && d.State == "attached" {
|
||||
attached[d.MountPath] = true
|
||||
}
|
||||
}
|
||||
var pending []string
|
||||
for _, sp := range paths {
|
||||
if sp.Decommissioned {
|
||||
continue
|
||||
}
|
||||
if attached[sp.Path] && !system.IsMountPoint(sp.Path) {
|
||||
pending = append(pending, sp.Path)
|
||||
}
|
||||
}
|
||||
return pending
|
||||
}
|
||||
|
||||
// registerStoragePath records a freshly-mounted path in the StoragePath registry (schedulable by
|
||||
// default) and refreshes the FileBrowser mounts so it's usable immediately.
|
||||
func (s *Server) registerStoragePath(where, label string, setDefault bool) error {
|
||||
@@ -201,6 +240,8 @@ func (s *Server) ServeStorageAPI(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleStorageImpact(w, r)
|
||||
case r.URL.Path == "/api/storage/register" && r.Method == http.MethodPost:
|
||||
s.handleStorageRegister(w, r)
|
||||
case r.URL.Path == "/api/storage/activate" && r.Method == http.MethodPost:
|
||||
s.handleStorageActivate(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
@@ -341,6 +382,24 @@ func (s *Server) handleStorageWipe(w http.ResponseWriter, r *http.Request) {
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{"device": req.Device, "wiped": fr.Formatted, "durable_id": fr.DurableID})
|
||||
}
|
||||
|
||||
// handleStorageActivate reboots the guest to activate pending drive binds (slice 10 P2). The agent
|
||||
// reboots detached + returns 202; this controller restarts with the guest, so the caller's response
|
||||
// may be cut short — the UI handles that and reloads after the restart window.
|
||||
func (s *Server) handleStorageActivate(w http.ResponseWriter, r *http.Request) {
|
||||
agent, err := s.agentClient()
|
||||
if err != nil {
|
||||
writeDiskJSON(w, http.StatusServiceUnavailable, false, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
if err := agent.GuestReboot(r.Context()); err != nil {
|
||||
s.logger.Printf("[ERROR] [web] guest reboot (activate pending drives) failed: %v", err)
|
||||
writeDiskJSON(w, http.StatusBadGateway, false, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[WARN] [web] guest restart requested to activate pending drive binds")
|
||||
writeDiskJSON(w, http.StatusAccepted, true, "", map[string]any{"rebooting": true})
|
||||
}
|
||||
|
||||
// handleStorageRegister records an ALREADY-mounted, unregistered user-data drive into the StoragePath
|
||||
// registry — no format, no eject. It is the natural primary action for a mounted-but-unregistered data
|
||||
// drive (e.g. felhom-usb): the customer's intent is to USE the existing data, not wipe it. It reuses
|
||||
|
||||
Reference in New Issue
Block a user