v0.78.0: storage register uses the STABLE intermediary path, not the raw path
handleStorageRegister (the "Regisztrálás" action for an already-mounted, unregistered drive) registered the raw /mnt/<name> path verbatim, unlike runStorageInit/runStorageAttach which map to the stable /mnt/felhom-drives/<name> path the agent actually binds the drive at. The controller then watched an empty placeholder dir on the guest rootfs → "Rendszermeghajtón" + stuck "activation pending" banner after a re-provision. Fix: register stablePathForName(path.Base(req.Where)); attachIntoGuest still uses the raw path (the agent operates on raw). Test + red-proof added. Diagnosis: felhom.eu/documentation/audits/DIAGNOSE-drive-bind-after-reprovision-2026-06-23.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW
This commit is contained in:
@@ -679,18 +679,26 @@ func (s *Server) handleStorageRegister(w http.ResponseWriter, r *http.Request) {
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, "érvénytelen csatlakoztatási pont", nil)
|
||||
return
|
||||
}
|
||||
if err := s.registerStoragePath(req.Where, req.Label, req.SetDefault); err != nil {
|
||||
s.logger.Printf("[WARN] [web] storage register %s failed: %v", req.Where, err)
|
||||
// req.Where is the RAW /mnt/<name> host mount the agent reports; in the intermediary model the drive
|
||||
// is actually live in the guest at the STABLE path /mnt/felhom-drives/<name>, so REGISTER the stable
|
||||
// path (matching runStorageInit/runStorageAttach). Registering the raw path made the controller watch
|
||||
// an empty rootfs placeholder → "Rendszermeghajtón" + stuck "activation pending" banner
|
||||
// (DIAGNOSE-drive-bind-after-reprovision-2026-06-23.md). attachIntoGuest still uses the RAW path —
|
||||
// the agent operates on raw.
|
||||
stable := stablePathForName(path.Base(req.Where))
|
||||
if err := s.registerStoragePath(stable, req.Label, req.SetDefault); err != nil {
|
||||
s.logger.Printf("[WARN] [web] storage register %s (stable %s) failed: %v", req.Where, stable, err)
|
||||
writeDiskJSON(w, http.StatusBadGateway, false, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] [web] storage path registered (existing mount): %s", req.Where)
|
||||
s.logger.Printf("[INFO] [web] storage path registered (existing mount): %s → %s", req.Where, stable)
|
||||
// Pass the drive into the guest too (slice 10 P2) — registering a host-only mount otherwise leaves
|
||||
// it guest-invisible (the exact gap that produced the "nem elérhető" banner).
|
||||
// it guest-invisible (the exact gap that produced the "nem elérhető" banner). The agent operates on
|
||||
// the RAW path, so attach with req.Where (NOT the stable path).
|
||||
if agent, aerr := s.agentClient(); aerr == nil {
|
||||
s.attachIntoGuest(r.Context(), agent, req.Where)
|
||||
}
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{"registered": true, "where": req.Where})
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{"registered": true, "where": stable, "raw": req.Where})
|
||||
}
|
||||
|
||||
func (s *Server) handleStorageAttach(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -209,6 +213,48 @@ func TestRunStorageAttach_Success(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// handleStorageRegister (the "Regisztrálás" action for an already-mounted, unregistered drive) must
|
||||
// register the STABLE intermediary path /mnt/felhom-drives/<name> — the same path runStorageInit/
|
||||
// runStorageAttach register — NOT the raw /mnt/<name> it receives. Registering the raw path made the
|
||||
// controller watch an empty rootfs placeholder ("Rendszermeghajtón" + stuck activation banner;
|
||||
// DIAGNOSE-drive-bind-after-reprovision-2026-06-23.md). RED-PROOF: reverting the handler to
|
||||
// registerStoragePath(req.Where, …) makes this fail (registry holds the raw /mnt/felhom-flash).
|
||||
// (The agent still operates on the RAW path via attachIntoGuest — that mapping is unchanged and is
|
||||
// asserted by TestRunStorageInit_Success's guestAttachCalls check; agentClient is unconfigured here so
|
||||
// the attach is skipped, which does not affect the registration under test.)
|
||||
func TestHandleStorageRegister_RegistersStablePath(t *testing.T) {
|
||||
s := testServer(t)
|
||||
body, _ := json.Marshal(map[string]any{"where": "/mnt/felhom-flash"})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/storage/register", bytes.NewReader(body))
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
s.handleStorageRegister(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d (body %s)", rr.Code, rr.Body.String())
|
||||
}
|
||||
paths := s.settings.GetStoragePaths()
|
||||
if len(paths) != 1 {
|
||||
t.Fatalf("expected exactly one registered path, got %+v", paths)
|
||||
}
|
||||
if paths[0].Path != "/mnt/felhom-drives/felhom-flash" {
|
||||
t.Fatalf("must register the STABLE path /mnt/felhom-drives/felhom-flash (raw-path bug if /mnt/felhom-flash), got %q", paths[0].Path)
|
||||
}
|
||||
var resp struct {
|
||||
OK bool `json:"ok"`
|
||||
Data struct {
|
||||
Where string `json:"where"`
|
||||
Raw string `json:"raw"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if !resp.OK || resp.Data.Where != "/mnt/felhom-drives/felhom-flash" || resp.Data.Raw != "/mnt/felhom-flash" {
|
||||
t.Fatalf("response must report the stable where + raw, got %+v", resp.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFSUUIDForDevice(t *testing.T) {
|
||||
disks := agentapi.DisksResponse{Disks: []agentapi.DiskInfo{
|
||||
{BackingDevice: "/dev/sda1", DurableID: "uuid:AAAA"},
|
||||
|
||||
Reference in New Issue
Block a user