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 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-28 06:46:52 +01:00
parent d13138398c
commit 95edf64558

View File

@ -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.