diff --git a/controller/internal/storage/attach_linux.go b/controller/internal/storage/attach_linux.go index 16a5e87..3e54a25 100644 --- a/controller/internal/storage/attach_linux.go +++ b/controller/internal/storage/attach_linux.go @@ -323,6 +323,54 @@ func CleanupRawMount(rawPath string) error { return nil } +// CleanupStaleRawMounts finds and removes raw mounts that have no corresponding +// bind mount — i.e., leftovers from an interrupted attach wizard. +// A raw mount is considered "in use" if fstab has a bind entry sourcing from it. +func CleanupStaleRawMounts() { + data, err := os.ReadFile("/proc/mounts") + if err != nil { + return + } + + // Read fstab to check for bind mount entries + fstabData, _ := os.ReadFile(FstabPath) + fstabLines := strings.Split(string(fstabData), "\n") + + for _, line := range strings.Split(string(data), "\n") { + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + mountPoint := fields[1] + if !strings.HasPrefix(mountPoint, RawMountBase+"/") { + continue + } + // Only consider direct children of RawMountBase (e.g., /mnt/.felhom-raw/hdd_1) + rel := strings.TrimPrefix(mountPoint, RawMountBase+"/") + if strings.Contains(rel, "/") { + continue + } + + // Check if any fstab bind entry sources from this raw mount path + inUse := false + for _, fl := range fstabLines { + fl = strings.TrimSpace(fl) + if fl == "" || strings.HasPrefix(fl, "#") { + continue + } + if strings.Contains(fl, "bind") && strings.HasPrefix(fl, mountPoint) { + inUse = true + break + } + } + + if !inUse { + _ = exec.Command("umount", mountPoint).Run() + _ = os.Remove(mountPoint) + } + } +} + // --- helpers --- // getBlkidValue runs blkid to get a single value (TYPE, UUID, LABEL) for a device. diff --git a/controller/internal/storage/attach_other.go b/controller/internal/storage/attach_other.go index c87cd22..30b019b 100644 --- a/controller/internal/storage/attach_other.go +++ b/controller/internal/storage/attach_other.go @@ -28,3 +28,6 @@ func FinalizeAttach(req AttachRequest, progress chan<- FormatProgress) (string, func CleanupRawMount(rawPath string) error { return fmt.Errorf("storage attach is only supported on Linux") } + +// CleanupStaleRawMounts is a no-op on non-Linux platforms. +func CleanupStaleRawMounts() {} diff --git a/controller/internal/web/storage_handlers.go b/controller/internal/web/storage_handlers.go index bb26d27..79c7ca8 100644 --- a/controller/internal/web/storage_handlers.go +++ b/controller/internal/web/storage_handlers.go @@ -1071,22 +1071,23 @@ func (s *Server) storageAttachStatusAPIHandler(w http.ResponseWriter, r *http.Re // storageAttachCancelHandler handles POST /api/storage/attach/cancel. // Cleans up the temporary raw mount when the user cancels the wizard. +// Also cleans up any stale raw mounts from interrupted previous sessions. func (s *Server) storageAttachCancelHandler(w http.ResponseWriter, r *http.Request) { s.diskJobMu.Lock() rawMount := s.activeRawMount s.activeRawMount = "" s.diskJobMu.Unlock() - if rawMount == "" { - jsonResponse(w, map[string]interface{}{"ok": true, "msg": "Nincs aktív raw mount"}) - return + if rawMount != "" { + if err := storage.CleanupRawMount(rawMount); err != nil { + s.logger.Printf("[WARN] Failed to cleanup raw mount %s: %v", rawMount, err) + } else { + s.logger.Printf("[INFO] Cleaned up raw mount: %s", rawMount) + } } - if err := storage.CleanupRawMount(rawMount); err != nil { - s.logger.Printf("[WARN] Failed to cleanup raw mount %s: %v", rawMount, err) - } else { - s.logger.Printf("[INFO] Cleaned up raw mount: %s", rawMount) - } + // Also clean up any stale raw mounts from previous interrupted sessions + storage.CleanupStaleRawMounts() - jsonResponse(w, map[string]interface{}{"ok": true, "msg": "Raw mount eltávolítva"}) + jsonResponse(w, map[string]interface{}{"ok": true}) } diff --git a/controller/internal/web/templates/storage_attach.html b/controller/internal/web/templates/storage_attach.html index 35d25dd..4785de6 100644 --- a/controller/internal/web/templates/storage_attach.html +++ b/controller/internal/web/templates/storage_attach.html @@ -167,7 +167,11 @@ function scanDisks() { errEl.style.display = 'none'; resultEl.style.display = 'none'; - fetch('/api/storage/scan', {method:'POST'}) + // Clean up any stale raw mounts from interrupted previous sessions first, + // so the device appears as available in the scan results. + fetch('/api/storage/attach/cancel', {method:'POST'}) + .catch(function(){}) // ignore cancel errors + .then(function() { return fetch('/api/storage/scan', {method:'POST'}); }) .then(function(r){ return r.json(); }) .then(function(data) { btn.textContent = '🔍 Meghajtók keresése';