Replies: 3 comments
|
Companion RFC posted: #98 (backup coverage overhaul). It fixes the raw-backup side this RFC deliberately leaves in place for same-version snapshots. An audit found the web backup and sysupgrade.conf keep lists have silently diverged (an in-place sysupgrade loses /etc/openvpn keys; backup/restore loses /etc/tor; neither preserves the web TLS cert), plus restore has no manifest/version/board checks at all. #98 adds a declarative keep.d registry, a backup manifest, and a restore pre-flight that routes cross-major tarballs to this RFC's profile path. |
Amendment: cross-hardware portability + the capability-negotiated import reportA concrete requirement pushes on this RFC: "a backup restorable on any compatible Gargoyle router — whatever the version or hardware, restoring as if native — with warnings for features the target can't cope with." This RFC already makes that possible (a profile with zero hardware facts can be replayed anywhere), but it treats cross-hardware as a side effect of the cross-version design rather than a feature to build. This amendment promotes it to co-equal and specifies the two things the RFC under-designs for it to actually work and be honest. 1. Cross-hardware is co-equal with cross-versionRaw config varies along three independent axes, not two — verified against a real running config:
2. The capability oracle already ships:
|
| Tier | Examples | On a mismatched target |
|---|---|---|
| 1 Portable verbatim | hostname, password hash, DHCP ranges, leases, port-forwards, firewall/restriction/quota by IP/MAC, SSID+key | applies identically everywhere |
| 2 Role-mappable | LAN/WAN assignment, per-port VLAN by role name | mapped via target's own switchinfo; degrades when port count differs |
| 3 Capability-gated | 6 GHz, 160 MHz, WPA3, DFS channels | checked vs board.json; degrades to nearest-valid, credential always survives |
| 4 Feature-gated | QoS, Tor, OpenVPN, WireGuard, USB/DLNA | if plugin absent → held aside, not dropped ("install + Apply") |
| 5 Never carried | device names, phy paths, MACs, safe_apply snapshots, lease file, counters | regenerated fresh; carrying these is what bricks |
4. The import report — this is the "warnings"
Every fragment resolves to exactly one of Applied / Adapted / Deferred / Dropped, each with a one-line human reason, assembled into a reviewable checklist (persisted to /etc/gargoyle/import-report.json, surfaced in the pending-import banner this RFC already specifies). Applied needs no attention; the other three are the warnings. Design rule: an importer must classify into one of the four — never half-apply-and-move-on. "The backend applied it" and "the setting survived, or the user was told it couldn't" are different claims; the report enforces the second.
This is the honest core: "restore as if native" is literally true only for Tiers 1–2. You cannot restore a 6 GHz SSID onto a dual-band router — the only truthful outcomes are adapt it or tell the user, and the report is what makes that a line item instead of a silent failure.
Prior art
pfSense/OPNsense do exactly this — restore onto different hardware drops you into an interface-assignment screen ("saved config had 4 interfaces, this box has 2, map them"), which is precisely the Tier-2 report. UniFi/Omada push off-device intent to any adopted model. MikroTik /export is the counter-example (port names physical, not hardware-abstracted) — the reason role-mapping is load-bearing.
Fits the existing phases (additions, not new phases)
- P1 — each importer gains its Tier classification +
board.jsoncheck + report assembly. Exporters unchanged (a profile is already hardware-free). - P2 — surface the report in the pending-import banner; add fix affordances (install-and-apply for Deferred).
- P3 — cross-device matrix + a
--dry-runthat emits the report without applying, so the GUI can say before committing exactly what will carry/adapt/defer/drop onto a named target (pairs with RFC: Signed OTA firmware upgrade (usign/fwtool/sysupgrade + signed manifest) #62 pre-flight).
New vnet cases T-PROF-07..10 cover cross-hardware port mapping, capability degrade, feature-gated defer, and report exhaustiveness — using a synthetic-board.json swap so one x86 VM can stand in for dual-band / low-port-count targets.
Full write-up with the verified config evidence lives in docs/settings-profile-cross-hardware-amendment.md (gargoyle-tools). Happy to fold this straight into the RFC body if the direction lands.
P1 skeleton scopeScoped the smallest buildable slice that proves the whole architecture end-to-end, grounded in the real tree ( Three contracts:
Reference feature =
Build order — first PR = frame + 10-system: |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Status: Ready for review · companion to #62 (signed OTA upgrade) · Updated: cross-hardware portability promoted to a first-class goal — the
board.jsoncapability check and the Applied/Adapted/Deferred/Dropped import report (see "Cross-hardware: the capability check and the import report"). Earlier updates: fleet-scaling argument, role-based hardware mapping, legacy converter, fixed-point testing.The problem
Every major Gargoyle upgrade today effectively means reconfiguring the router from scratch — and moving your settings to a different router model means the same thing. Backup/restore and
sysupgrade's keep-config path both preserve the raw/etc/configfiles, and raw config varies along three independent axes that a raw restore silently assumes all match:br-lanovereth1 eth2 eth3with WAN oneth0.10on one box; on an MT6000 the same roles arelan1..lan4/wan. Restore across models and those device names don't exist → no LAN → unreachable router (the same class as the liveLAN2-vs-lan2VLAN lockout).ipaddr→CIDR list,hwmode→band.So the safe advice becomes "don't keep settings," and users pay for every major upgrade — or every new router — with their entire configuration. That is a real adoption blocker: a router you must re-program twice a year loses to stock firmware that upgrades in one click.
The idea: preserve intent, not files
The raw UCI files are an implementation detail of one firmware version on one box. What the user owns is their intent: "LAN is 192.168.12.1/24, SSID is X with key Y, these six port forwards, this QoS shape." Every release already contains code that renders that intent into its own native config — the GUI's save paths. This RFC gives that intent a stable, versioned file (
gargoyle-profile.json) with zero hardware facts in it, and makes upgrades regenerate config from it instead of carrying files forward:Because the profile is hardware-free, the same mechanism that survives a version bump also carries a config onto a different model — cross-version and cross-hardware are one design, not two. This is the model pfSense/OPNsense, FRITZ!Box and MikroTik all use — none carry raw backend state across versions.
Why not chained migration scripts?
The obvious alternative (preserve raw config +
migrations/NNN.shtransforming it version-by-version) was considered and rejected: every upstream schema change must be modeled twice forever, chains break on skipped versions, and it's untestable against configs nobody anticipated. With the profile approach, each release maintains only its own generators (code that must exist anyway for the GUI) plus tolerant JSON readers — and version skipping is free by construction, because importers translate intent to the running schema, never old-schema to new-schema. 1.13→1.15 is exactly the same code path as 1.14→1.15.Cross-hardware: the capability check and the import report
"Restore on any compatible router as if native" is achievable — but only honest if the importer can tell what the target can actually do, and report what it couldn't. Two pieces make that work.
The capability oracle already ships. Every OpenWrt device carries
/etc/board.json— a machine-readable model of that exact box: the port topology (which named roles physically exist) and, per radio, the supported bands (2G/5G/6G), the PHY modes (ht/vht/he/eht), the max channel width, and the legal channel list. On import, each fragment is checked againstboard.jsonbefore it's applied — so the importer knows, per target, whether it can do 6 GHz, 160 MHz, WPA3, or a given port, rather than writing config that silently fails. We read the abstraction OpenWrt maintains per-model; we don't replicate it (the same O(features) property the fleet-scaling argument relies on).Every field has a portability tier — the spec for what each importer does on a mismatched target:
lan2)lan3/lan4→ 2-port router drops them)board.json; degrades to nearest-valid — the SSID + key always survive ("6 GHz SSID → 5 GHz"; "160 → 80 MHz")The import is a report, not a silent best-effort. Every fragment resolves to exactly one of four outcomes, assembled into a reviewable checklist (persisted to
/etc/gargoyle/import-report.json, surfaced in the pending-import banner below):Appliedneeds no attention; the other three are the warnings. Design rule: an importer must classify into one of the four — never half-apply-and-move-on. "The backend applied it" and "the setting survived, or the user was told it couldn't" are different claims; the report enforces the second. This is the honest core — "as if native" is literally true only for Tiers 1–2. You cannot restore a 6 GHz SSID onto a dual-band router; the truthful outcomes are adapt or tell the user, and the report makes each one a line item instead of a silent failure. (pfSense does exactly this: a cross-hardware restore drops you into an interface-assignment screen — that prompt is precisely the Tier-2 report.)Scaling to the real fleet (hundreds of models, memory classes, ancient installs)
The design's central scaling property: per-model knowledge lives in code that already exists and that someone else already maintains per-model.
board.json,ubus call system board). OpenWrt maintains that abstraction across all its boards; we ride it rather than replicate it.eth0.1became DSA'slan1, and interface names are case-sensitive (theLAN2vslan2live bug). Anything unmappable becomes a Dropped line in the report, never a guess.min_ram_kb/min_flash_kb/eol/final_version): devices below the floor are never offered an upgrade, EOL devices are told honestly they are at their final version. This RFC's job is that for every device that does upgrade or reflash, the configuration survives.Fleet tiers (shared with #62): Tier A validated boards get full OTA + profile survival; Tier B (buildable, not yet OTA-validated) get profile survival with a manual flash — P1 delivers this with zero OTA infrastructure; Tier C (EOL/below floor) are frozen at their final version and told so.
Design in brief
profile_versionbumps only on breaking semantic changes./usr/lib/gargoyle/profile.d/NN-<feature>.sh export|import, independent and idempotent; a thingargoyle_profile.shorchestrator assembles/splits the JSON (jshn is already on every image). Each importer performs its Tier classification and (for Tiers 2–4) itsboard.json/ plugin-presence check, emitting an entry to the import report for every fragment it touches. Each importer lives next to the page code it mirrors, with a shared round-trip test (below) so page evolution can't silently orphan its importer.sysupgrade -f(the archive defines the preserved set, so no-nis needed —-foverrides it in sysupgrade's own source anyway); a shipped/etc/uci-defaults/99-gargoyle-profile-importimports it once on first boot before network/wireless start (stock OpenWrt run-once mechanism, no new plumbing). The RFC: Signed OTA firmware upgrade (usign/fwtool/sysupgrade + signed manifest) #62 manifest'smin_profile_versionlets the upgrade page state before flashing what will carry over. All three flash entry points share this wiring: OTA (RFC: Signed OTA firmware upgrade (usign/fwtool/sysupgrade + signed manifest) #62), the existing manual upload page (its "preserve settings" checkbox routes cross-major saves through the profile stash instead of raw keep-config), and CLI. A successful import also marks the router as provisioned (the password came from the profile), so the firstboot wizard doesn't re-prompt a user whose router just upgraded itself./etc/gargoyle/pending-import.json(alongside theimport-report.jsonabove) with a GUI banner ("N of M settings groups need review") and per-feature retry. Wholesale failure still boots clean defaults, reachable at the default LAN IP, profile intact. Worst case today: unreachable router, full manual rebuild. Worst case with this: one login and a guided re-import. One further contract, imposed by RFC: Signed OTA firmware upgrade (usign/fwtool/sysupgrade + signed manifest) #62's downgrade design: when a profile is newer than the running firmware (a downgrade), the importer applies the readable subset but preserves the newer profile file intact on disk — this is what makes downgrade→re-upgrade a lossless round-trip.Free wins
export --redactedbeats "describe your settings."Honest limits
Physical impossibility is real, and stated rather than hidden: tri-band → dual-band loses a radio, 4 VLAN'd ports → a 2-port router loses two — each is a Dropped/Adapted line in the report, by design. Features removed upstream can't import (the report says so rather than pretending). Firmware older than this system can't export — the legacy converter recovers most of it, not all. The profile covers what the GUI manages; hand-edited raw UCI stays the raw tarball's job (the raw-backup side — divergent keep lists, missing manifest, blind restore — is owned by companion RFC #98, whose restore pre-flight routes cross-major tarballs to this profile path). Single-partition targets still can't auto-rollback a bad flash (#62's scope) — this removes the config half of the risk, not the flash half.
Phases
board.json/plugin capability check, and import-report entries. Export/Import on the Backup page. Ships value with zero OTA infrastructure (Tier B: manual flash + settings survival) — and cross-device moves work at P1 because the profile is already hardware-free.min_profile_version, integration withota_upgrade.sh(RFC: Signed OTA firmware upgrade (usign/fwtool/sysupgrade + signed manifest) #62 phase 3), legacy converter (old raw backup tarball → best-effort profile, exposed as "Import legacy backup" on the Backup page).--dry-runthat emits the import report without applying — so the GUI can state, before committing, exactly what will carry / adapt / defer / drop onto a named target (pairs with RFC: Signed OTA firmware upgrade (usign/fwtool/sysupgrade + signed manifest) #62's pre-flight, which already refuses combinations it can't honor before flashing).Testing
New vnet phase
NN-settings_profile.test.sh:lan3/lan4onto a target modeled with 2 LAN ports; assert lan1/lan2 apply on the wire and lan3/lan4 appear as Dropped in the report.board.jsonadvertising only 2G/5G, 80 MHz, WPA2; assert SSID+key are live on 5 GHz and the report shows the three Adapted lines with reasons.T-PROF-07..10 use a synthetic-
board.jsonswap (feed the importer a constrained capability model) so a single x86 VM can stand in for dual-band / low-port-count targets without needing that physical hardware in the sim. The browser-journey requirement in T-PROF-03 is deliberate: recent VLAN-save debugging proved "the backend applied it" and "the user's settings actually survived" are different claims, and only the second counts.Full design docs:
docs/settings-profile-upgrade-plan.mdanddocs/settings-profile-cross-hardware-amendment.mdinispyisail/gargoyle-tools.Feedback welcome — especially on the profile schema's feature list for P1, which old versions the legacy converter should target first, and whether the Backup page should default to profile export (with the raw tarball as the "advanced" option) once this lands.
All reactions