On a Debian install the onboarding wizard offered the EFI system partition
(unlabeled vfat at /boot/efi) as the primary WROLPi drive; choosing it would
mount the boot partition over /media/wrolpi. Candidates were filtered by
the "EFI" label only, and the Debian installer leaves the ESP unlabeled.
/boot/efi is now an excluded mountpoint, and partitions with the EFI system
partition type (GPT GUID or MBR 0xef, from lsblk PARTTYPE) are excluded even
when unmounted, e.g. on a second internal disk.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
"Run repair without external drive" kept the library on the internal disk,
but the API's unmounted-media guard refuses to create or use a database
beneath /media/wrolpi when it is not a mount. After onboarding without a
drive the API restarted forever with no database, and downloads were
blocked as "media not mounted".
The Controller now records the choice as drives.library_on_internal_disk in
controller.yaml -- the only source of it. The database guard and the
media_mounted check both read that marker, so require_media_mounted keeps
its meaning and needs no change. Onboarding a drive later clears the
marker, so it is never copied onto the drive.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The onboarding wizard deleted /media/wrolpi/config before mounting a
drive, and the shadowed data check ignored config/. A library kept on
the internal disk keeps wrolpi.db and its configs there, so onboarding
a drive later deleted them without warning. Only a config/ holding
nothing but the ssl/ CA is now treated as transient.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
wrolpi-bootstrap.sh created controller.yaml on installed systems, which
hid the onboarding wizard. Installed systems now wait for the wizard,
whose repair.sh finishes setup.
"Run repair without external drive" now writes controller.yaml before
starting repair, so the wizard does not return after repair.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
Introduce an onboarding system in the Controller that guides users
through selecting and mounting their primary media drive. This is
the fallback UI flow used when the main React app is unavailable.
Key changes:
- Add onboarding API endpoints and schemas (controller/api/onboarding.py)
- Add onboarding logic to detect drives, handle already-mounted drives
(needed for RPi which auto-mounts), and save primary drive selection
to the controller config when no mounts are configured
- Extend the Controller's index.html template with the onboarding UI
- Skip root cert creation until onboarding is complete
- Update first_startup.sh to support the onboarding/repair flow
- Add comprehensive tests for onboarding logic and API