router-release signs a release as a binary cache; router-update verifies it against the router's update keys, migrates settings.json, and applies it with commit-confirm. Includes the web UI page and a VM test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
settings.json migrations
settings.json carries a top-level "version" (absent means 0). When a
release changes what settings.json may contain (a renamed or removed option, a
new default that must be written down), add N.jq here, where N is the next
number after the highest one. The file is a jq program that turns a version
N-1 document into a version N document; keep unknown keys as they are.
router-update runs the missing ones, in order, on the router's settings.json
before it rebuilds, and sets "version" after each. A release's schema version
is the number of N.jq files; the build refuses settings that are not at that
version, so a router never builds settings its release does not understand.
Example 1.jq, renaming router.wan.interface to router.wan.device:
.router.wan |= (.device = .interface | del(.interface))