- backup_manager: read plugin state from "states" key (not "plugins") to
match the actual plugin_state.json format written by state_manager
- backup_manager: stream ZIP directly to a temp file instead of building
it in an io.BytesIO buffer to avoid OOM on Raspberry Pi
- backup_manager: tighten plugin-uploads path validation in validate_backup
and restore_backup to require "/uploads/" in the path, rejecting any
non-uploads files smuggled under assets/plugins/
- api_v3: enforce 200 MB upload limit by streaming in chunks rather than
relying on validate_file_upload (which only checks the filename)
- api_v3: replace bool() with _coerce_to_bool() for RestoreOptions fields
so string "false" is not treated as truthy
- api_v3: capture and log _save_config_atomic return value instead of
discarding it; log rather than silence font-cache and config-reload errors
- backup_restore.html: track inspectedFile so runRestore always applies to
the file the user inspected, not a subsequently selected file; clear on
input change or clearRestore()
- backup_restore.html: throw on non-success restore payload so errors are
surfaced via the error notification path instead of yellow "warnings"
- test: update fixture to use correct "states" key structure; import
SCHEMA_VERSION constant instead of hardcoding 1; rename unused err -> _err
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Backup & Restore tab to the v3 web UI that packages user config,
secrets, WiFi, user-uploaded fonts, plugin image uploads, and the installed
plugin list into a single ZIP for safe reinstall recovery. Restore extracts
the bundle, snapshots current state via the existing atomic config manager
(so rollback stays available), reapplies the selected sections, and
optionally reinstalls missing plugins from the store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>