From fc94d91d151d4acf07cdff50302fe1b119faa2d7 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Tue, 23 Jun 2026 17:54:21 +0200 Subject: [PATCH] v0.80.0: disk card shows + acts on the stable path, not the raw host mount The storage card displayed each external drive's raw host PVE mount (/mnt/, which doesn't exist in the guest) instead of the stable in-guest path (/mnt/felhom-drives/ = guest_path) the registry/HDD_PATH/FileBrowser use. The eject/wipe buttons also posted the raw path, so they would unmount the drive but leave the stable registry entry orphaned, and the impact warning found no apps. Fix: card sub-line + eject/wipe buttons use the stable path (regKey); type-to-confirm name uses the basename; register keeps the raw path. handleStorageWipe maps to raw via agentWhere() for the agent eject (matching handleStorageEject). Agent ops unchanged (same raw paths); display + registry bookkeeping corrected. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_017PsnU2ASocYrvzqE82YDYW --- CHANGELOG.md | 14 ++++ REPORT.md | 74 ++++++++++--------- controller/internal/web/storage_handlers.go | 6 +- .../internal/web/templates/settings.html | 31 +++++--- 4 files changed, 78 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fffa833..4c156ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ ## Changelog +### v0.80.0 — disk card: show + act on the stable path, not the raw host mount (2026-06-23) +- Follow-up to v0.78/0.79. The storage disk card displayed the drive's **raw** host PVE mount + (`/mnt/`) — which doesn't exist inside the guest — instead of the **stable** in-guest path + (`/mnt/felhom-drives/`, the `guest_path`) the registry, app `HDD_PATH`, and FileBrowser use. +- It also passed the **raw** path to the Leválasztás/Törlés buttons, so those would unmount the drive + but leave its **stable** registry entry orphaned (`RemoveStoragePath` is keyed on the stable path), and + the impact warning (`/api/storage/impact?where=`) found no affected apps (HDD_PATH is the stable path). +- Fix (`settings.html`): the card sub-line + the eject/wipe buttons now use the stable path (`regKey(d)`); + the type-to-confirm name is derived from the basename so it still matches the server check; **register** + keeps posting the raw path (its agent guest-attach operates on raw). `handleStorageWipe` now maps the + registered path to raw via `agentWhere()` for the agent eject (matching `handleStorageEject`), so the + drive deregisters cleanly. Agent-facing ops are unchanged (same raw paths); only display + the + controller's own registry bookkeeping are corrected. + ### v0.79.0 — disk view: key the "registered" check on the stable path (2026-06-23) - Follow-up to v0.78.0. The storage disk-view JS (`settings.html` `regBadge`/`actions`) decided whether a drive was registered by looking up its **raw** `mount_path` (`/mnt/`) in the registry — but diff --git a/REPORT.md b/REPORT.md index 78c55ca..ebaac64 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,47 +1,53 @@ -# REPORT — controller v0.79.0: disk view keys "registered" on the stable path +# REPORT — controller v0.80.0: disk card shows + acts on the stable path ## Baseline -- felhom-controller `main` @ `081c1cd`, **v0.78.0 → v0.79.0**. Trunk-based, `main`. -- Follow-up to v0.78.0 (stable-path registration). Same path-model mismatch, on the **display** side. +- felhom-controller `main` @ `9596c8e`, **v0.79.0 → v0.80.0**. Trunk-based, `main`. +- Follow-up to v0.78.0 (stable-path registration) + v0.79.0 (registered badge on stable path). ## Problem (operator-reported, screenshot) -After v0.78.0, the storage disk view tagged both enrolled, working drives **"Nem regisztrált"** and -showed a **"Regisztrálás"** button — even though the registry holds them and the selftest reports them -`2 connected`. Root cause: the disk-view JS (`settings.html`) decided "registered?" by looking up the -drive's **raw** `mount_path` (`/mnt/felhom-flash`) in `window.__registeredPaths`, but v0.78.0 made the -registry store the **stable** path (`/mnt/felhom-drives/felhom-flash`), so the raw-key lookup always -missed. (Before v0.78.0 it read "Regisztrálva" because the registry held the raw path — looked right but -the drive was broken.) +The storage disk card still displayed each external drive's **raw** host PVE mount `/mnt/` +(`usb · /dev/sdc1 · /mnt/felhom-flash`). Inside the guest/controller the drive lives at the **stable** +path `/mnt/felhom-drives/` (the agent's `guest_path`) — which is what the registry, app `HDD_PATH`, +and FileBrowser use; the raw path doesn't even exist in the guest. Two latent bugs rode on the same +mismatch: the Leválasztás/Törlés buttons posted the **raw** path, so `handleStorageEject`/`handleStorageWipe` +would unmount the drive but `RemoveStoragePath(raw)` would miss the **stable** registry entry (orphan); +and the impact warning `/api/storage/impact?where=raw` found no apps (HDD_PATH is the stable path). -## Fix (`controller/internal/web/templates/settings.html`, disk-view JS) -- New `regKey(d)` = `d.guest_path || d.mount_path` — the agent already reports each drive's STABLE - in-guest path as `guest_path` (`/mnt/felhom-drives/`), which is what the registry stores. -- `regBadge(d, registered)` and `actions(d, registered)` now key the "is registered" check on - `regKey(d)` instead of `d.mount_path`. So an enrolled drive reads **"Regisztrálva"** and the spurious - Register button is hidden. -- `registerDrive()` still posts the RAW `mount_path` (unchanged) — the agent operates on raw, and - `handleStorageRegister` maps it to the stable path. Display-only change; no Go/handler change. +## Fix +**`controller/internal/web/templates/settings.html`** (disk-view JS): +- Card sub-line now shows `regKey(d)` (= `d.guest_path || d.mount_path`) — the stable in-guest path. +- `actions()` passes the **stable** path to `confirmEject`/`confirmWipe` (handlers map it to raw for the + agent via `agentWhere()` AND deregister the stable registry entry). `registerDrive` keeps the **raw** + `mount_path` (its agent guest-attach operates on raw; `handleStorageRegister` maps to stable). +- `confirmEject`/`confirmWipe` derive the type-to-confirm name from the **basename** + (`where.split('/').filter(Boolean).pop()`) so it still equals the server's `path.Base(where)` check + when `where` is the stable path. + +**`controller/internal/web/storage_handlers.go`** (`handleStorageWipe`): +- The agent eject now uses `agentWhere(req.Where)` (was `req.Where` verbatim), matching + `handleStorageEject`. So a stable registered path maps to the raw mount for the agent while + `RemoveStoragePath(req.Where)` deregisters the stable entry. Agent op is the identical raw path as + before; only the registry bookkeeping is corrected. The format step is unchanged (keyed on `device`). + +Agent-facing operations (eject unmount, format) produce the IDENTICAL raw paths/device as before — only +the controller's display + its own registry bookkeeping/impact lookups now use the stable path. ## Tests -- `go build ./... && go vet ./... && go test ./...` — **all green** (incl. `TestTemplatesParse`, which - forces the edited template to parse). +- `go build ./... && go vet ./... && go test ./...` — **all green** (incl. `TestTemplatesParse`). +- `handleStorageWipe`'s new mapping rides on `agentWhere`, already covered by `TestAgentWhere` + (stable→raw, raw idempotent). JS changes covered by template parse (no JS unit harness — existing + pattern). -## Deploy (v0.79.0 → guest 9201) -- Built + pushed `gitea.dooplex.hu/admin/felhom-controller:0.79.0` on 192.168.0.180; deployed to 9201 - (pull → `/etc/felhom-controller-image` → restart `felhom-controller-bootstrap.service`); `docker ps` → - `:0.79.0 Up (healthy)`. +## Deploy (v0.80.0 → guest 9201) +- _(filled at deploy)_ built+pushed `:0.80.0` on 192.168.0.180; deployed to 9201; `:0.80.0 Up (healthy)`. -## Verification (the two data inputs the fixed JS consumes — now aligned) -- `GET /api/disks` (in-guest): `felhom-flash` → `mount_path=/mnt/felhom-flash`, - `guest_path=/mnt/felhom-drives/felhom-flash`, `bound_under_parent=true`; `felhom-usb` → - `guest_path=/mnt/felhom-drives/felhom-usb`, `bound=true`. -- Settings page `__registeredPaths` = `["/mnt/felhom-drives/felhom-flash","/mnt/felhom-drives/felhom-usb"]`. -- ⇒ `regKey(d)=d.guest_path` now matches the registry → badge renders **"Regisztrálva"** and the - spurious **"Regisztrálás"** button is hidden. (The badge/button are pure client-side JS over these two - inputs; verified the inputs, not a browser render — the bridge wasn't available this session.) -- v0.78.0 state intact: storage selftest `2 connected`, no "Rendszermeghajtón", no activation banner. +## Verification +- _(filled at verify)_ disk card sub-line shows `/mnt/felhom-drives/felhom-flash` (+usb); the rendered + eject/wipe button onclick args carry the stable path; "Regisztrálva" intact; `2 connected`. +- NOT live-clicked: the destructive eject/wipe buttons (would unmount/format the demo drives). Verified + the path-mapping logic by inspection + the agent ops are unchanged (raw via `agentWhere`/device). ## Observations (recorded, NOT acted on) - `pendingActivationDrives` still keys off the agent's raw `MountPath` (the "~30 mp újraindítás" banner); obsolete in the live-activating intermediary model — key off `GuestPath`/`BoundUnderParent` or retire. -- The free-form settings storage-add registers a user-typed path verbatim (advanced manual flow). +- Free-form settings storage-add (`handlers.go`) registers a user-typed path verbatim (advanced flow). diff --git a/controller/internal/web/storage_handlers.go b/controller/internal/web/storage_handlers.go index a8b1a47..c6edac4 100644 --- a/controller/internal/web/storage_handlers.go +++ b/controller/internal/web/storage_handlers.go @@ -608,8 +608,10 @@ func (s *Server) handleStorageWipe(w http.ResponseWriter, r *http.Request) { writeDiskJSON(w, http.StatusServiceUnavailable, false, err.Error(), nil) return } - // 1. Unmount (benign — frees the device so mkfs can run) + deregister the StoragePath. - if _, eerr := agent.EjectDisk(r.Context(), req.Where); eerr != nil { + // 1. Unmount (benign — frees the device so mkfs can run) + deregister the StoragePath. req.Where is + // the STABLE registered path; the agent operates on the raw mount (agentWhere), the registry stores + // the stable path (RemoveStoragePath gets req.Where). + if _, eerr := agent.EjectDisk(r.Context(), agentWhere(req.Where)); eerr != nil { s.logger.Printf("[WARN] [web] wipe: eject %s failed (continuing to format): %v", req.Where, eerr) } else if rerr := s.settings.RemoveStoragePath(req.Where); rerr != nil { s.logger.Printf("[WARN] [web] wipe: deregister %s failed: %v", req.Where, rerr) diff --git a/controller/internal/web/templates/settings.html b/controller/internal/web/templates/settings.html index 504b620..bad17e6 100644 --- a/controller/internal/web/templates/settings.html +++ b/controller/internal/web/templates/settings.html @@ -477,17 +477,19 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}} function actions(d, registered){ // Destructive controls ONLY for user-data drives that are mounted under /mnt. System/backup get none. if(d.role!=='user-data' || !d.mount_path || d.mount_path.indexOf('/mnt/')!==0) return ''; - var dev = esc(d.backing_device||''), mp = esc(d.mount_path); + var dev = esc(d.backing_device||''), mpRaw = esc(d.mount_path), reg = esc(regKey(d)); var btns = ''; - // A mounted-but-unregistered user-data drive: the natural intent is to USE it → Regisztrálás is - // the PRIMARY action (no format, no eject). Leválasztás/Törlés stay available but secondary. - // Registration is keyed on the STABLE path (regKey) — the registry stores that, not the raw mp; - // registerDrive() still posts the RAW mp (the agent operates on raw, the handler maps to stable). + // Two distinct path arguments (the intermediary model): + // - registerDrive posts the RAW mount_path — its agent guest-attach operates on the raw mount + // (handleStorageRegister maps it to the stable path for the registry). + // - eject/wipe post the STABLE (registered) path — their handlers map it to raw for the agent via + // agentWhere() AND deregister it from the registry (which stores the stable path). Posting the + // raw path here would unmount the drive but leave the stable registry entry orphaned. if(!registered[regKey(d)]){ - btns += ' '; + btns += ' '; } - btns += ''; - if(d.backing_device){ btns += ' '; } + btns += ''; + if(d.backing_device){ btns += ' '; } return '
'+btns+'
'; } async function load(){ @@ -502,7 +504,11 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}} var html='

Az alkalmazások rendszere és adatbázisai a belső SSD-n (local-lvm), a nagy méretű fájljaik a külső adattárolókon tárolódnak.

'; html+='
'; disks.forEach(function(d){ - var sub = esc(d.type)+' · '+esc(d.backing_device||'—')+(d.mount_path?' · '+esc(d.mount_path):''); + // Show the path the customer's system actually uses: the STABLE in-guest path + // (/mnt/felhom-drives/, = guest_path) for an external drive, not the raw host PVE mount + // (/mnt/) which doesn't exist inside the guest. regKey falls back to mount_path for + // non-intermediary drives (system/local). + var sub = esc(d.type)+' · '+esc(d.backing_device||'—')+(d.mount_path?' · '+esc(regKey(d)):''); var badges = roleBadge(d.role)+appBackingTag(d)+classBadge(d)+dataBadge(d)+regBadge(d,registered); var purpose = purposeDesc(d); html+='
' @@ -569,7 +575,9 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}} setTimeout(function(){location.reload();}, 45000); }; window.confirmEject=function(where){ - var name=where.replace(/^\/mnt\//,''); + // The confirm name is the drive BASENAME (matches the server's path.Base(where) check); `where` may + // be the stable /mnt/felhom-drives/ path, so strip the whole directory, not just the /mnt/ prefix. + var name=where.split('/').filter(Boolean).pop()||where; openConfirm({title:'Meghajtó leválasztása', mount:where, mountName:name, danger:'A meghajtó leválasztásra kerül. Az adatok megmaradnak, de az ott tárolt alkalmazások elvesztik a tárhelyüket, amíg újra nem csatolja.', onConfirm:async function(){ @@ -582,7 +590,8 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}} }}); }; window.confirmWipe=function(device, where){ - var name=where.replace(/^\/mnt\//,''); + // Basename (matches the server's path.Base(where) type-to-confirm check); `where` may be the stable path. + var name=where.split('/').filter(Boolean).pop()||where; openConfirm({title:'Meghajtó törlése (formázás)', mount:where, mountName:name, danger:'FIGYELEM: a meghajtón lévő ÖSSZES ADAT véglegesen törlődik (formázás). Ez nem vonható vissza.', onConfirm:async function(){