controller v0.67.0: intermediary-mount — HDD_PATH repoint + drive-absent gate + H1 routes

Drives are visible in-guest only at the STABLE /mnt/felhom-drives/<name>; the
registered path + HDD_PATH + FileBrowser source repoint there while agent calls
map back to raw /mnt/<name> (agentWhere). Enroll binds-under-parent before
register. Drive-absent GATE (planDriveGates + 30s driveGateLoop) stops/blocks
apps when a drive vanishes and auto-restarts on return; start-gate refuses start
when the drive is absent. H1 endpoints (disconnect/reconnect/restart-apps) routed
onto host-side ops. Non-hollow tests + companions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 16:45:13 +02:00
parent 2687843a83
commit 55c896624f
9 changed files with 467 additions and 17 deletions
+22 -9
View File
@@ -116,15 +116,19 @@ func (s *Server) runStorageInit(ctx context.Context, agent diskAgent, device, fs
if uuid == "" {
return storageInitResult{}, fmt.Errorf("formázás kész, de az új fájlrendszer-azonosító nem feloldható — frissítsen és használja a Csatolás funkciót")
}
// 3. Mount (benign assign) + 4. register.
// 3. Mount (benign assign) at the raw /mnt/<name>. 4. Bind felhom-data under the shared parent FIRST
// (intermediary model) so the drive's STABLE path is live in the guest, THEN register + skeleton there
// (the controller can only see/write the drive at the stable path post-attach). 5. Register the stable
// path — that is what apps' HDD_PATH / FileBrowser / monitoring use.
if err := agent.AssignDisk(ctx, uuid, where, fstype, ""); err != nil {
return storageInitResult{}, fmt.Errorf("csatlakoztatás sikertelen: %w", err)
}
if err := s.registerStoragePath(where, label, setDefault); err != nil {
s.attachIntoGuest(ctx, agent, where)
stable := stablePathForName(path.Base(where))
if err := s.registerStoragePath(stable, label, setDefault); err != nil {
return storageInitResult{}, err
}
s.attachIntoGuest(ctx, agent, where)
return storageInitResult{Registered: true, Where: where}, nil
return storageInitResult{Registered: true, Where: stable}, nil
}
// attachIntoGuest passes an enrolled drive INTO the guest (slice 10 P2) so the controller + apps can
@@ -152,11 +156,12 @@ func (s *Server) runStorageAttach(ctx context.Context, agent diskAgent, device,
if err := agent.AssignDisk(ctx, uuid, where, fstype, ""); err != nil {
return storageInitResult{}, fmt.Errorf("csatlakoztatás sikertelen: %w", err)
}
if err := s.registerStoragePath(where, label, setDefault); err != nil {
s.attachIntoGuest(ctx, agent, where)
stable := stablePathForName(path.Base(where))
if err := s.registerStoragePath(stable, label, setDefault); err != nil {
return storageInitResult{}, err
}
s.attachIntoGuest(ctx, agent, where)
return storageInitResult{Registered: true, Where: where}, nil
return storageInitResult{Registered: true, Where: stable}, nil
}
// pendingActivationDrives returns registered storage paths that are NOT yet live-mounted in this
@@ -288,6 +293,12 @@ func (s *Server) ServeStorageAPI(w http.ResponseWriter, r *http.Request) {
s.handleStorageMigrateStatus(w, r)
case r.URL.Path == "/api/storage/decommission" && r.Method == http.MethodPost:
s.handleStorageDecommission(w, r)
case r.URL.Path == "/api/storage/disconnect" && r.Method == http.MethodPost:
s.handleStorageDisconnect(w, r)
case r.URL.Path == "/api/storage/reconnect" && r.Method == http.MethodPost:
s.handleStorageReconnect(w, r)
case r.URL.Path == "/api/storage/restart-apps" && r.Method == http.MethodPost:
s.handleStorageRestartApps(w, r)
default:
http.NotFound(w, r)
}
@@ -421,7 +432,8 @@ func (s *Server) finalizeDecommissionWith(ctx context.Context, agent diskAgent,
if err := s.settings.SetDecommissioned(where, migratedTo); err != nil {
return fmt.Errorf("nyilvántartás frissítése sikertelen: %w", err)
}
if _, err := agent.Decommission(ctx, where); err != nil {
// Registered path is the STABLE /mnt/felhom-drives/<name>; the agent decommissions the raw mount.
if _, err := agent.Decommission(ctx, agentWhere(where)); err != nil {
return fmt.Errorf("a meghajtó leszerelése sikertelen: %w", err)
}
if s.stackMgr != nil {
@@ -715,7 +727,8 @@ func (s *Server) handleStorageEject(w http.ResponseWriter, r *http.Request) {
writeDiskJSON(w, http.StatusServiceUnavailable, false, err.Error(), nil)
return
}
res, err := agent.EjectDisk(r.Context(), req.Where)
// The registered path is the STABLE /mnt/felhom-drives/<name>; the agent operates on the raw mount.
res, err := agent.EjectDisk(r.Context(), agentWhere(req.Where))
if err != nil {
writeDiskJSON(w, http.StatusBadGateway, false, err.Error(), nil)
return