# Changelog 2026-07-09 -- OPNsense config.xml design (DOCFIX-149)

No live infrastructure touched. Real design work, not a placeholder --
researched OPNsense's own real, currently-shipped config.xml before
drafting, per this session's established discipline.

## Item

### 1. DOCFIX-149 -- `opentofu/templates/opnsense-config.xml.tmpl` + renderer
FILES (new): `opentofu/templates/opnsense-config.xml.tmpl`,
`opentofu/templates/README.md`, `scripts/opnsense-render-config.sh`,
`tests/opnsense-render-config/run-tests.sh`.

WHAT: a `{{TOKEN}}`-parameterized OPNsense `config.xml` template (this
repo's existing clientdocs token convention, reused rather than inventing a
new one), covering system/interfaces/gateway/staticroutes/DNS/NTP and
D-107-shaped firewall rules (default-deny WAN egress except NTP + the
per-DC mirror's upstream sync -- the two exceptions D-107 names). Paired
with `scripts/opnsense-render-config.sh`, which substitutes tokens from
required environment variables (no invented defaults except the NTP pool,
which defaults to the exact same public pool OPNsense's own sample ships --
a real, confirmed default, not an invention) and feeds the result to the
already-existing `scripts/opnsense-build-config-iso.sh` -> ISO9660 ->
`modules/opnsense-edge` pipeline.

VERIFICATION: built directly from OPNsense's own real `config.xml.sample`
(`opnsense/core`'s repo, `src/etc/config.xml.sample`) fetched and read as
real content, not summarized -- the same discipline that caught the
DOCFIX-142 syntax bug and the DOCFIX-148 LAN/WAN documentation error. This
fetch also DIRECTLY CONFIRMED the DOCFIX-148 audit finding: the real sample
ships literal placeholder device names (`mismatch0`/`mismatch1`) inside each
interface's own `<if>` element, proving LAN/WAN role assignment is exactly
the per-block explicit mapping DOCFIX-148 concluded it was, now with a real
example rather than research-by-inference. The `staticroutes` schema was
confirmed from `opnsense/core`'s own `Route.xml` MVC model file (the actual
schema-defining source, not a doc page) -- `network`/`gateway`/`descr`/
`enabled` fields, and `gateway` must reference a named gateway object
(hence the template's own `<gateways><gateway_item name="WAN_GW">` addition,
which the stock sample doesn't need since it uses DHCP).

TESTED END-TO-END, not just guard clauses: unlike every other opnsense-*
script this session, `opnsense-render-config.sh` needs no external tool
(pure bash + template substitution), so its harness (8/8) exercises the
real behavior -- happy path, well-formed-XML output (verified with
`python3 -m xml.dom.minidom`), the NTP real-default fallback, one static
route rendering while an unset second slot correctly emits nothing, and
three failure paths.

**A real bug the harness itself caught:** the token `HOSTNAME` collides
with bash's own built-in `$HOSTNAME` variable -- `unset HOSTNAME` does not
actually clear it (bash repopulates it), so a "missing required token"
test that should have failed instead silently passed with the shell's own
system hostname. Renamed to `OPNSENSE_HOSTNAME` throughout (template,
script, docs, tests) before this shipped, not after.

WHAT'S STILL NOT REAL: `opentofu/templates/README.md`'s token legend marks
each token's status precisely -- several cannot be filled with a genuine
value yet because the decision they depend on is still PROPOSED
(D-100/D-101/D-107's WAN addressing, LAN plane assignment, mirror
software choice), and two (`WAN_IF`/`LAN_IF`) are only measurable on a real
boot (which `vtnetN` a network becomes). `ROOT_PASSWORD_HASH` must be
freshly generated per deployment -- the stock sample's own shipped hash is
a well-known default and must never be reused verbatim.

`opentofu/README.md`'s OPNsense research section and gap register item 4
in `docs/dc-dc-deployment-workflow.md` (+ its summary table, + the
companion visual tracker) updated to reflect this as BUILT (the mechanism
and the tested renderer), not just the still-pending final values.

REVERT: `git rm -r opentofu/templates scripts/opnsense-render-config.sh
tests/opnsense-render-config`; revert the `opentofu/README.md` and
workflow-doc sections if reverting fully.

## Next actionable step

Two independent things still block a real per-site config.xml: (1) Stage 0
ratification of D-100/D-101/D-107 (WAN addressing, LAN plane assignment,
mirror software choice) -- a decision, not a build task; (2) a real boot to
measure `vtnetN` device assignment before setting `WAN_IF`/`LAN_IF`. Neither
needs more OpenTofu/script work -- the mechanism is done and tested.
