#5927 "Adopt the family idea secret.Box": the SMTP password and the backup
bucket's secret key were kept as plain text in the settings table, so a copy
of the database alone could read them.
- New sealing.py: AES-GCM under an HKDF-derived key, each value bound to its
setting's name. The key is a random file, /var/inkwell/sealing.key (0600),
made on first use. It lives on the data volume, not in the database, which
is where the signing secret sits by default.
- set_settings seals a secret before storing it; get_setting opens it. A value
that no longer opens (key file lost) reads as not set, so Settings asks
for it again.
- At startup, credentials stored before this are sealed in place, so nothing
has to be re-entered. A data folder that can't be written never stops the
server; saving a password then answers with the reason.
- New dependency: cryptography (approved by the operator).
Also #1919 "Deprecate / remove the PWA": kept. Its service worker caches
only the offline page, so an installed copy always runs the live build. One
stale comment in sw.js is corrected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>