From 95edf645586030a12644c113996e4a226af02311 Mon Sep 17 00:00:00 2001 From: librelad Date: Fri, 28 Aug 2026 06:46:52 +0100 Subject: [PATCH] docs: the multi-disk test matrix, and what it found The failure shape worth naming: anything resolving a root at runtime works on a default install and points at the wrong disk on a relocated one. Testing only all-default or all-moved misses it, because in both the wrong answer is often accidentally right. Co-Authored-By: Claude Opus 5 --- docs/roadmap/storage-locations.md | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/roadmap/storage-locations.md b/docs/roadmap/storage-locations.md index 33d20c5..e24da05 100644 --- a/docs/roadmap/storage-locations.md +++ b/docs/roadmap/storage-locations.md @@ -490,6 +490,56 @@ Each phase is independently shippable and independently verifiable. Phase 0 carr | **5** ◐ | Manifest `storage` block and the staged restore + path rewrite are done (§9's latent bug is fixed). The "unknown location" **prompt** is not — an unresolvable location currently falls back to this host's default rather than asking. | Yes | | **6** ◐ | The Disks **data layer** is built and `libreportal storage disks` renders it (including not-attached rows). The WebUI page at `/admin/system/storage` and the periodic `storage check` cron are **not**. | Yes | +## 12.5 — Testing this, and what it found + +Multi-disk support has a specific failure shape: **anything that resolves a root +at runtime works on a default install and points at the wrong disk on a +relocated one.** Paths are baked into the root-owned helpers, the systemd unit +and the CLI wrapper at install time, so a code path that reads them from +somewhere else is invisible until the roots actually differ. Testing only "all +default" or only "all moved" misses it, because in both of those the wrong +answer often happens to be the right one. + +So the matrix is the point, not the individual runs: + +| | system | apps | backups | +|---|---|---|---| +| 1 | `/` | `/` | `/` | +| 2 | `/` | disk 2 | `/` | +| 3 | disk 1 | `/` | `/` | +| 4 | disk 1 | disk 2 | disk 2 | + + scripts/dev/lp-testdisk up 2 30G # loopback ext4: real superblock, + # own st_dev, own free space, disposable + sudo scripts/dev/lp-install-matrix all + +Each case checks the roots landed on the intended **device** (not merely the +intended path), the owners, that every helper was baked with no `__PLACEHOLDER__` +left, and that the WebUI answers. + +What the matrix turned up that review had not: + +- **`lp-shot` hardcoded `/libreportal-containers`** for both the compose file it + reads the port from and the `.auth.json` it signs a session with. On a + relocated install it fell back to a default port and a missing auth file — + which looks exactly like a WebUI that failed to boot. +- **The `@reboot` boot-reconcile resolved the default roots.** `paths.sh` takes + them from the environment, and a crontab entry is started by neither the CLI + wrapper nor the unit. That job brings every app up; pointed at the wrong root + it does not fail, docker just creates the missing bind-mount directories and + every app comes back empty (§10.1). Fixed by recovering them from the unit. +- **`libreportal-relocate` never rewrote the crontab**, whose entry embeds an + absolute path under the system dir — so after moving that root the job silently + did not exist. +- **The setup wizard called the app-data drive "System disk"** when it was not, + showing the data drive's size under the system disk's name. +- **The installer said nothing at all** when the only other drive was unmounted. + +Case 4 is also the honest test of §9: restoring a repository taken on a +default-layout host onto it means *every* app takes the stage-and-move branch, +and staging (disk 1) and destination (disk 2) are different devices, so +`app-adopt` exercises its copy path rather than `mv`. + ## 13. Open questions 1. ~~**Naming.** "Storage location" vs "backup location" in the same UI~~ — **resolved (2026-08-24):** build the Disks view (§7.1). The device becomes the organising concept and the two registries become *roles* on it, so the user never has to hold the distinction to understand their own hardware. Registries stay separate underneath.