fix(config): preflight /config writability and surface it clearly (#278)#279
Merged
Conversation
A read-only /config (host permission mismatch — common on TrueNAS where the dataset isn't owned by the container's PUID/PGID) surfaced as SQLite's opaque "attempt to write a readonly database" in two confusing ways: - Scans ran but every job-state persist to jobs.db failed, flooding the log once per item while the UI looked frozen (job state never updated). - Schedule creation failed writing scheduler.db, and the catch-all blamed a "malformed cron expression" — the wrong cause entirely. Add a config-directory health preflight: - config_health.probe_config_health(): authoritative create+delete probe plus /proc/mounts (ownership vs :ro mount), statvfs (low space), and network-fs (NFS/SMB) detection, each with an actionable fix. Probe file is uniquely named per attempt so concurrent gthread probes can't race. - Startup logs ONE clear error instead of a per-item flood. - GET /api/system/config-health (cheap, no GPU) feeds a global banner on every page; scan + schedule creation refuse with a 503 + chown hint instead of starting work that can't persist / a misleading 500. - Media-mount health folded into the same banner. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## dev #279 +/- ##
==========================================
+ Coverage 84.00% 84.06% +0.06%
==========================================
Files 89 90 +1
Lines 19669 19807 +138
==========================================
+ Hits 16523 16651 +128
- Misses 3146 3156 +10
🚀 New features to boost your workflow:
|
checkConfigHealth() used apiGet(), which redirects to /login on a 401. That let the passive background health poll bounce any auth-gated page to login — e.g. the setup wizard, whose /api/system/config-health call 401s when setup is already complete but the page isn't authenticated. This broke the TestSetupWizardStep5 e2e suite (wizard redirected to login before its steps could render). Use a raw fetch and silently bail on any non-OK response. A health check must never move the user; the banner just doesn't render when the endpoint is unavailable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #278. A read-only
/config(host permission mismatch — common on TrueNAS where the dataset isn't owned by the container's PUID/PGID) surfaced as SQLite's opaqueattempt to write a readonly databasein two confusing ways:jobs.dbfailed, flooding the log once per item while the UI never updated.scheduler.db, and the catch-all blamed "a malformed cron expression or a clash with an existing schedule" — the wrong cause entirely.Changes
web/config_health.py(new) —probe_config_health(): authoritative create+delete probe, plus/proc/mounts(tells ownership vs:romount apart),statvfs(low-space), and network-FS (NFS/SMB) detection — each with an actionable fix. Probe file is uniquely named per attempt so concurrent gthread probes (shared PID) can't race into a false "unwritable".web/app.py— startup preflight logs one clear error at boot instead of a per-item flood.routes/api_system.py—GET /api/system/config-health(cheap, no GPU) feeding the banner; folds in live media-mount health.routes/api_jobs.py— 503 guard oncreate_job/create_manual_job: refuses to start a scan that can't persist, with the fix.routes/api_schedules.py— detects the readonly-DB case → 503 + chown hint instead of the misleading cron 500.templates/base.html+static/js/app.js— global banner on every page: red when/configisn't writable, yellow for network-FS / low-space / missing media mount.Verification
tests/test_config_health.py— 12 tests (probe matrix incl.:romount, low-space, network-fs, cleanup-race regression; the two schedule error cells; both 503 guards)./config): the red banner renders with the exactchownhint;POST /api/jobsandPOST /api/schedulesboth return 503 with the fix; banner clears on the next poll once perms are fixed (no restart). Architecture Review run pre-commit (1 MED + 2 LOW, all fixed).🤖 Generated with Claude Code