Moving WROLPi-managed mounts off /etc/fstab onto a drive-resident fstab.yaml.

A WROLPi drive can move between hosts and across deployment styles
(Portable, RPi, Debian).  /etc/fstab is host-specific and on the Portable
image it's part of a read-only squashfs, so persisting secondary mounts
there was both wrong-host and lost-on-reboot.  This change makes
/media/wrolpi/config/fstab.yaml the single source of truth for WROLPi-
managed mounts and introduces a new wrolpi-mounts.service that reconciles
live state to match.

Architecture:

- The Controller writes intent (fstab.yaml) but never calls mount(8) or
  umount(8) itself.  Every mutation triggers `systemctl restart
  wrolpi-mounts.service`; the Controller then verifies the outcome via
  the same MountExecutor abstraction the service uses.

- wrolpi-mounts.service runs a thin entry point that constructs a real
  Reconciler with a SubprocessMountExecutor and calls apply().  The
  reconciler mounts what's in fstab.yaml but missing from the live state,
  and unmounts paths it previously claimed that are no longer in
  fstab.yaml.  A managed-set file in /run/wrolpi-mounts/managed prevents
  the reconciler from ever touching user-mounted paths it didn't own.

- One-time migration (marker-gated) lifts both /etc/fstab WROLPi entries
  and any controller.yaml drives.mounts entries into fstab.yaml.
  /etc/fstab is backed up to /etc/fstab.wrolpi-migration.backup;
  migrated lines are commented in place with a "migrated to fstab.yaml:"
  prefix so a host admin can hand-recover.

Test approach: dependency injection in place of patching.  The
MountExecutor protocol has SubprocessMountExecutor (production) and
FakeMountExecutor (in-memory, records calls).  The Reconciler accepts an
executor in its constructor, so the 20 reconciler tests + 13 migration
tests + 18 fstab_yaml tests use real files in tmp_path with zero
mock.patch calls.  test_disks_api.py drops to mocks only for the
host-touching read helpers; the mount/unmount paths now run the real
Reconciler against the fake executor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Roland 2026-05-22 22:02:30 -06:00 • committed by lrnselfreliance
parent 94d56f313a
commit 719905cb77
15 changed files with 2257 additions and 320 deletions

View file

@ -0,0 +1,22 @@
[Unit]
Description=WROLPi secondary-drive mounts
Documentation=https://github.com/lrnselfreliance/wrolpi
# Read drives.mounts from controller.yaml and apply each entry that isn't
# already mounted. Replaces /etc/fstab entries for secondary drives so the
# mount table travels with the drive itself.
After=local-fs.target wrolpi-portable-bootstrap.service
RequiresMountsFor=/media/wrolpi
# Run before WROLPi services so secondary drives are ready when they start.
Before=wrolpi-api.service wrolpi-app.service wrolpi-kiwix.service wrolpi.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/wrolpi
ExecStart=/opt/wrolpi/controller/venv/bin/python -m controller.lib.mount_runner
StandardOutput=journal
StandardError=journal
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target