opnsense-set-interface-v6: script the LAN IPv6 the REST API cannot set (D-113 amendment)
Operator ruling 2026-07-14: take the PHP-model-over-SSH path, not a GUI click and
not the unproven VIP path. Rationale: the DC0 and DC1 edges get the same treatment
at Stage 3, so this must be a script, not a manual step paid three times.

WHY THIS EXISTS: measured on the live Office1 edge (OPNsense 26.1) -- there is NO
REST endpoint for a base interface's ipaddrv6. Interfaces > [LAN] is served by the
LEGACY page /interfaces.php?if=lan; only the Devices and Neighbors sub-trees were
ever migrated to MVC. D-113(a2)'s claim that "interfaces" are API-covered was
inferred from DHCP/firewall being API-native, never measured, and is false.

WHY IT IS NOT THE FORBIDDEN config.xml PUSH -- the distinction is the whole point:
it never AUTHORS a config. It ships opnsense-set-iface-v6.php, which loads the
edge's LIVE config through OPNsense's own Config singleton, mutates exactly two
leaf fields (ipaddrv6, subnetv6), and lets the vendor's own code serialize it --
byte-for-byte the code path interfaces.php runs on Save. Nothing is replaced
wholesale, so the API-managed Kea DHCP and the 8 firewall rules cannot be
clobbered. Same principle as opnsense-bootstrap-apikey.sh, which mints a key by
calling the vendor's own model rather than re-implementing its $6$ crypt format:
call the interface, do not re-implement the internals.

Safety properties, all pinned by the harness (40/40):
  - DRY BY DEFAULT in both halves; --commit required to write.
  - The interface COUNT is asserted unchanged before saving AND re-asserted on a
    fresh forceReload() read-back. A silent structural drop on a live router fails
    loud rather than being found later by a ping that stopped.
  - Only ipaddrv6/subnetv6 may be assigned -- the harness counts the assignments.
  - Values validated locally BEFORE they ever reach the router.
  - Remote commands go through `sh -s`: root's shell on the edge is TCSH, where a
    $(...) in a quoted remote command dies QUIETLY.
  - Ground truth is the KERNEL (ifconfig), never the config file or a self-report.

NOT YET RUN against the edge -- that is the next, individually-gated step.
repo-lint 0 fail; gauntlet ALL GREEN (57 harnesses, +1).

Revert: git revert HEAD  (removes both scripts + the harness; the edge is untouched)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ZBxzAbmqwLW7jEmMG23Xw
1 parent 3b01dd4 commit 522ba7d73fd91f7df267968912e0075ba2cc6ecc
@JANeumatrix JANeumatrix authored 1 day ago
Showing 3 changed files
View
scripts/opnsense-set-iface-v6.php 0 → 100644
View
scripts/opnsense-set-interface-v6.sh 0 → 100755
View
tests/opnsense-set-interface-v6/run-tests.sh 0 → 100755