v0.172.0 — R-75: canonical import root, catalog-derived skeleton, import surfaces

${IMPORT_PATH} = <system namespace root>/userdata/import — ONE drop-zone per box,
on the system drive, injected at BOTH compose-env builders with NO per-drive
fallback (unresolvable leaves it unset so compose fails loudly rather than
quietly building a second, dead drop-zone).

Third BindRoot (RootImport) + Import list in BackupSpec, extended through
ValidateBackupSpec/ClassifyBinds. Load-bearing: a stale `userdata: import/<app>`
entry against the moved bind would be a WHOLE-BLOCK reject, taking the app's
mandatory hdd classification with it.

Exhaustive-root audit: resolveAbs/structuralGuard/ComputeCaptureSet/
ComputeFabBuckets now take importRoot explicitly (an import bind resolved
against hddPath would name a directory on the wrong drive); unresolvable is
refused loudly into Skipped. GetImportRoot added to both provider interfaces.

Catalog-derived skeleton: UserdataSkeleton() -> UserdataSkeletonCarry() +
BuildUserdataSkeleton(), SORTED. The carry-list makes zero-removals true by
construction (`documents` is in no catalog app but on both boxes) and is the
fresh-box floor. The sort is not tidiness: the naive map-order derivation
measured 20 distinct outputs from 20 identical runs, which with fbNeedsRecreate
is a fleet-wide FileBrowser restart loop.

One authoritative compose parser: ParseComposeUserdataMounts now delegates to
ParseComposeClassifiableBinds. Import root excluded from per-app migration.

Surfaces: FileBrowser /srv/beolvasas source; app-page "Hova tegyem a fajlokat?"
with PathEscape deep links (never QueryEscape) and class-driven copy;
data_paths: annotation with the Fork-3 asymmetry; system-owned beolvasas SMB
share refused server-side at handler AND store, button omitted in template.

Caught on the way: the sharing template's row struct was function-local, so
adding {{if .System}} would have 500'd every share row. ShareRow is now
package-level and the render test uses the handler's own type.

Tests 915 -> 949, all green. MinAgent unchanged.
This commit is contained in:
2026-07-26 08:12:57 +02:00
parent 3b672ba74c
commit 2958946517
57 changed files with 2228 additions and 151 deletions
@@ -71,6 +71,24 @@
onerror="this.style.display='none'">
</div>
{{if .DataPathCards}}
<div class="app-info-card" style="margin-top:1rem">
<h3>Hova tegyem a fájlokat?</h3>
<p class="form-hint">Ezeket a mappákat a Fájlkezelőben éred el. Előfordulhat, hogy először be kell jelentkezned a Fájlkezelőbe.</p>
<div class="datapath-list">
{{range .DataPathCards}}
<div class="datapath-row">
<div class="datapath-head">
<a href="{{.Link}}" target="_blank" rel="noopener" class="datapath-link">{{.Label}} ↗</a>
{{if .FreeSpace}}<span class="datapath-space">{{.FreeSpace}}</span>{{end}}
</div>
{{if .Consequence}}<p class="datapath-note">{{.Consequence}}</p>{{end}}
</div>
{{end}}
</div>
</div>
{{end}}
{{if and .Stack.Deployed .MigrateTargets}}
<div class="app-info-card" style="margin-top:1rem">
<h3>Áthelyezés másik tárhelyre</h3>
@@ -146,11 +146,15 @@
</form>
</td>
<td>
{{if .System}}
<span class="form-hint">rendszer</span>
{{else}}
<form method="POST" action="/sharing/shares/delete">
{{$.CSRFField}}
<input type="hidden" name="name" value="{{.Name}}">
<button type="submit" class="btn btn-xs btn-danger-outline">Törlés</button>
</form>
{{end}}
</td>
</tr>
{{end}}
@@ -1317,6 +1317,44 @@ a.stat-card:hover {
font-size: .95rem;
color: var(--text-1);
}
/* „Hova tegyem a fájlokat?" — R-75 folder rows on the app page. */
.datapath-list {
display: flex;
flex-direction: column;
gap: .75rem;
}
.datapath-row {
border-left: 3px solid var(--blue);
background: var(--blue-dim);
padding: .6rem .75rem;
border-radius: var(--radius);
}
.datapath-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: .75rem;
flex-wrap: wrap;
}
.datapath-link {
color: var(--blue-bright);
font-weight: 600;
text-decoration: none;
}
.datapath-link:hover {
text-decoration: underline;
}
.datapath-space {
color: var(--text-2);
font-size: .8rem;
white-space: nowrap;
}
.datapath-note {
margin: .35rem 0 0 0;
color: var(--text-2);
font-size: .85rem;
line-height: 1.45;
}
.app-info-list {
margin: 0;
padding-left: 1.25rem;