The host-side updater has been silently broken in two ways:
1. Empty staging directories would cause rm -rf of live routes/src with no
replacement, leaving the host tree gutted while the container kept serving
from its own image. Now deploy_tree() refuses to delete unless the staging
source has actual files.
2. chattr +i on critical files (used to protect security-hotfixed routes from
being clobbered by upstream tarballs) caused rm -rf to partially execute
then fail under set -e, leaving the host in a half-deleted state. Now
deploy_tree() scans for immutable files, unlocks them before replace,
and re-locks them after — so security-locked files survive every update.
Also adds:
- Channel gate: trigger.json channel=prerelease/beta/rc/alpha is rejected
unless ALLOW_PRERELEASE=true is set in /opt/dashcaddy/updates/channel.conf.
Default is 'stable only', safe for production. Staging hosts opt in.
- channel.conf.example documenting the new opt-in mechanism.
Verified end-to-end: manual trigger.json → path unit fired → routes (53 files)
+ src (62 files) deployed → container rebuilt → health check passed. totp.js
remained locked with security edits intact.