* Warn in the navbar when a hard drive runs hot.
The navbar already warns about high CPU temperature; add the equivalent
for drive temperature, since an overheating disk risks data loss and a
user running an off-grid library won't be watching for it.
- controller/lib/smart.py: build_smart_stats() returns the per-drive list
plus warning thresholds (55C warn / 65C critical) so the frontend stays
dumb. Reading SMART wakes a spun-down drive, so drives in standby are
detected with `smartctl -n standby` (which works through the USB-SATA
bridges where `hdparm -C` only reports "unknown") and NOT read; their
last-known reading is reused instead.
- controller/lib/status_worker.py: collect SMART on a slow 60s cadence
(cached on app.state) rather than the 5s status loop, so the poll does
not keep USB drives awake. The result flows into the same cached payload
via a new collect_all_status(smart_stats=...) kwarg.
- Frontend: useDriveTemperature() reports the hottest drive; Nav.js shows
a yellow/red `hdd` icon with a popup, mirroring the CPU warning.
Verified on hardware: smart_stats reports real temps and SMART runs once
a minute, not every 5s.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address Greptile review on HDD temperature warning.
- _drive_is_asleep also treats SLEEP mode as asleep, not just STANDBY.
smartctl -n standby exits "Device is in SLEEP mode, exit(2)" for the
deeper power mode; matching only "STANDBY" meant a SLEEP drive was read
and woken — the exact failure the check prevents. (TDD: added a failing
SLEEP test, then fixed.)
- useDriveTemperature uses ?? instead of || for the temperature fallback
to express "fall back only on null/undefined".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Applied directly since the dependabot PR could not auto-rebase after the
grouped merges.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real drives report attribute 194 raw values like "52 (Min/Max 16/57)"
which int() cannot parse, so every drive's temperature was null.
Prefer pySMART's `temperature` property, which parses these formats
and also works for NVMe devices that have no ATA attribute table.
Verified on real hardware (WD100EMAZ over USB-SAT): 51-53C now
reported where null was returned before.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /api/disks/unmount endpoint only removed entries from fstab.yaml and
asked the reconciler to unmount, but the reconciler deliberately never
unmounts paths it has not claimed. Foreign mounts (udisks2 desktop
automounts, manual mounts) therefore could not be unmounted from the UI,
failing with a confusing "check journalctl" error.
- Fall back to a direct umount(8) when a reconcile leaves the requested
mount point live.
- Reject unmounting the primary WROLPi drive (/media/wrolpi) with a
clear 400, normalized to catch path traversal.
- Require the normalized mount point to be strictly under /media/
before any direct umount, since the fallback runs as root.
- Show "System" instead of an Unmount button for /media/wrolpi in both
the React UI and the Controller's simplified UI.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
Map viewer has simple search.
Map search databases can be downloaded from CDN, or built locally. Map searches can be built offline, but will not have enriched things like state names downloaded from Wikidata.
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
- Replace nginx with Caddy as the web server for both Docker and native
(RPi/Debian) deployments. Caddy handles HTTPS termination, reverse
proxying to all services (API, React app, Controller, Kiwix, Help, Map),
and serves the media/download file browsers.
- Generate a Root CA and signed leaf certificates for HTTPS. The Root CA
uses Name Constraints to restrict signing to local addresses only
(.local, localhost, and RFC 1918 private IP ranges), mitigating MITM
risk if the CA key is compromised.
- Add an HTTP landing page that guides users through one-time CA
certificate installation on iOS, Android, macOS, Windows, Linux, and
Firefox, with auto-redirect when the cert is already trusted.
- Add trailing-slash redirects for /controller, /docs, /media, and
/download so bare paths don't fall through to the React catch-all.
- Add a Root CA download link on the Settings page under Configs.
- Update help.sh diagnostics for IPv6 listener detection and HTTP/2
status codes.
- Remove all nginx config files and the https_proxy Docker container.
- Bump wrolpi-help submodule with certificate documentation.