W13 new-drive pages pre-fill the next free hdd_N and answer a bad name visibly; W14 drive operator detail behind Részletek; W29 plain decommission names no move; unreleased
gates / gates (push) Successful in 1m37s

This commit is contained in:
2026-10-21 22:51:08 +02:00
parent 8c1e290f63
commit 28b05d6d68
16 changed files with 1349 additions and 32 deletions
@@ -10,6 +10,7 @@ import (
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
@@ -54,6 +55,22 @@ func mountWhere(name string) (string, error) {
return "/mnt/" + name, nil
}
// nextFreeMountName is the first hdd_N (N from 1) whose /mnt/hdd_N is not a registered storage path — a
// decommissioned one counts as taken, so a new drive never reuses an old drive's name (W13). Pinned by
// TestW13_StorageInitSuggestsTheNextFreeNameAndNeverFailsSilently.
func nextFreeMountName(registered []settings.StoragePath) string {
taken := map[string]bool{}
for _, sp := range registered {
taken[path.Clean(sp.Path)] = true
}
for n := 1; ; n++ {
name := "hdd_" + strconv.Itoa(n)
if !taken["/mnt/"+name] {
return name
}
}
}
// fsUUIDForDevice re-lists the agent's disks and returns the fs UUID of the storage backed by `device`
// (the only way the de-privileged controller learns the UUID it must pass to assign). "" if not found.
func fsUUIDForDevice(disks agentapi.DisksResponse, device string) string {
@@ -373,6 +390,12 @@ func (s *Server) storageWizardPageHandler(w http.ResponseWriter, r *http.Request
}
data := s.baseData(tmpl, title)
data["TitleKey"] = titleKey // i18n: the Hungarian title above is what hu renders
// W13 (rehearsal 2026-10-20): the mount-name box was empty and a household did not know what to type.
var registered []settings.StoragePath
if s.settings != nil {
registered = s.settings.GetStoragePaths()
}
data["SuggestedMountName"] = nextFreeMountName(registered)
// executeTemplate, NOT render(): the wizard POSTs /api/storage/* with the csrf-meta token,
// and render() leaves {{.CSRFToken}} empty. Latent until the claim arc — on an unclaimed
// (auth-off) box CsrfProtect is skipped entirely, so the wizard had never run against a