v0.141.0: F6 initialize-to-usable (detached crash-safe init job + status poll) + F7 Vissza back-routes
- F6: POST /api/storage/init runs format→mount→register as a DETACHED single-flight job (context.Background, netAddState shape) the wizard polls via GET /api/storage/init/status; 3-step progress; register-last marker-last crash-safety. Fixes the client-disconnect-aborts-mount bug. No agent change (chain reaches FileBrowser sync = controller-only). Red-proof TestStorageInit_DetachedSurvivesClientDisconnect. - F7: storage_init/attach Vissza → /storage (was /settings). Test TestStorageWizardBackAnchors_PointToStorage.
This commit is contained in:
@@ -108,13 +108,19 @@ type storageInitResult struct {
|
||||
// UUID → assign → register. A USER-DATA data-bearing device requires the customer's confirmation
|
||||
// (NeedsConfirmation); a SYSTEM/BACKUP device requires an operator signature (Refused+Opsign). In
|
||||
// either refusal it performs NO further (destructive or mount) action.
|
||||
func (s *Server) runStorageInit(ctx context.Context, agent diskAgent, device, fstype, where, label string, setDefault, confirmed bool, durableID string) (storageInitResult, error) {
|
||||
func (s *Server) runStorageInit(ctx context.Context, agent diskAgent, device, fstype, where, label string, setDefault, confirmed bool, durableID string, progress func(string)) (storageInitResult, error) {
|
||||
setP := func(phase string) {
|
||||
if progress != nil {
|
||||
progress(phase)
|
||||
}
|
||||
}
|
||||
if !validFSTypes[fstype] {
|
||||
return storageInitResult{}, fmt.Errorf("nem támogatott fájlrendszer: %q (ext4 vagy xfs)", fstype)
|
||||
}
|
||||
// 1. Format — the AGENT inspects the device and tiers it by role. A data-bearing user-data device
|
||||
// is allowed only with the customer's confirmation bound to its durable id; system/backup needs
|
||||
// an operator signature.
|
||||
setP(storageInitPhaseFormatting)
|
||||
fr, err := agent.FormatDisk(ctx, device, fstype, confirmed, durableID)
|
||||
if errors.Is(err, agentapi.ErrNeedsConfirmation) {
|
||||
// USER-DATA: surface the type-to-confirm requirement + the durable id to confirm against.
|
||||
@@ -135,6 +141,7 @@ func (s *Server) runStorageInit(ctx context.Context, agent diskAgent, device, fs
|
||||
}
|
||||
// 2. Resolve the NEW fs UUID. A freshly-formatted RAW device isn't in /disks (not enrolled yet), so
|
||||
// resolve via the raw-device scan too (Impl-2b) — the device now appears there with its new durable_id.
|
||||
setP(storageInitPhaseMounting)
|
||||
uuid := resolveEnrollUUID(ctx, agent, device)
|
||||
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")
|
||||
@@ -147,6 +154,10 @@ func (s *Server) runStorageInit(ctx context.Context, agent diskAgent, device, fs
|
||||
return storageInitResult{}, fmt.Errorf("csatlakoztatás sikertelen: %w", err)
|
||||
}
|
||||
s.attachIntoGuest(ctx, agent, where)
|
||||
// 5. Register the stable path — THE LAST step (marker-last, F6/Scenario B): a crash before this
|
||||
// leaves at most a mounted-but-unregistered drive (surfaced by the list), never a broken
|
||||
// registration, and AddStoragePath dedups a re-run so there is never a duplicate entry.
|
||||
setP(storageInitPhaseRegistering)
|
||||
stable := stablePathForName(path.Base(where))
|
||||
if err := s.registerStoragePath(stable, label, setDefault); err != nil {
|
||||
return storageInitResult{}, err
|
||||
@@ -273,6 +284,8 @@ func (s *Server) ServeStorageAPI(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.URL.Path == "/api/storage/init" && r.Method == http.MethodPost:
|
||||
s.handleStorageInit(w, r)
|
||||
case r.URL.Path == "/api/storage/init/status" && r.Method == http.MethodGet:
|
||||
s.handleStorageInitStatus(w, r)
|
||||
case r.URL.Path == "/api/storage/attach" && r.Method == http.MethodPost:
|
||||
s.handleStorageAttach(w, r)
|
||||
case r.URL.Path == "/api/storage/eject" && r.Method == http.MethodPost:
|
||||
@@ -527,20 +540,33 @@ func (s *Server) handleStorageInit(w http.ResponseWriter, r *http.Request) {
|
||||
writeDiskJSON(w, http.StatusServiceUnavailable, false, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
res, err := s.runStorageInit(r.Context(), agent, req.Device, req.FSType, where, req.Label, req.SetDefault, req.Confirmed, req.DurableID)
|
||||
if err != nil {
|
||||
writeDiskJSON(w, http.StatusBadGateway, false, err.Error(), nil)
|
||||
// F6: run format→mount→register as a DETACHED job (off the request context) the wizard polls via
|
||||
// /api/storage/init/status. A closed tab / lost connection can no longer abort the post-mkfs
|
||||
// mount+register leg. The confirm/refuse verdicts (fast, pre-mkfs) surface through the same poll.
|
||||
started := s.startStorageInit(agent, storageInitParams{
|
||||
device: req.Device, fstype: req.FSType, where: where, label: req.Label,
|
||||
setDefault: req.SetDefault, confirmed: req.Confirmed, durableID: req.DurableID,
|
||||
})
|
||||
if !started {
|
||||
writeDiskJSON(w, http.StatusConflict, false, "már folyamatban van egy meghajtó-inicializálás", nil)
|
||||
return
|
||||
}
|
||||
if res.NeedsConfirmation {
|
||||
writeDiskJSON(w, http.StatusConflict, false, "ügyfél-megerősítés szükséges", res)
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{"started": true, "phase": storageInitPhaseFormatting})
|
||||
}
|
||||
|
||||
// handleStorageInitStatus reports the last / in-flight drive-init job (deep copy; "idle" when never
|
||||
// ran). The wizard polls this for the 3-step progress + the confirm/refuse verdicts (F6).
|
||||
func (s *Server) handleStorageInitStatus(w http.ResponseWriter, r *http.Request) {
|
||||
job := s.storageInit.snapshot()
|
||||
if job == nil {
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{"phase": "idle"})
|
||||
return
|
||||
}
|
||||
if res.Refused {
|
||||
writeDiskJSON(w, http.StatusConflict, false, "operátori aláírás szükséges", res)
|
||||
return
|
||||
}
|
||||
writeDiskJSON(w, http.StatusOK, true, "", res)
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{
|
||||
"phase": job.Phase, "device": job.Device, "where": job.Where, "error": job.Error,
|
||||
"reason": job.Reason, "durable_id": job.DurableID, "opsign": job.Opsign,
|
||||
"started_at": job.StartedAt, "updated_at": job.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// storageImpactReq / handleStorageImpact return the deployed apps whose data lives on a given mount —
|
||||
|
||||
Reference in New Issue
Block a user