diff --git a/.claude/skills/openstack-cloud-ops/SKILL.md b/.claude/skills/openstack-cloud-ops/SKILL.md index 778828d..29fcf3f 100644 --- a/.claude/skills/openstack-cloud-ops/SKILL.md +++ b/.claude/skills/openstack-cloud-ops/SKILL.md @@ -94,6 +94,7 @@ | Why is it built this way? / proposing changes | repo `docs/design-decisions.md` (D-NNN); grep before assigning a new number | | Versions, channels, pins | repo `runbooks/appendix-B-asbuilt-version-lock.md` | | Environment facts (hosts, repo, planes) | `references/environment.md` | +| OpenTofu / libvirt / MAAS / OPNsense IaC work (repo `opentofu/`) | `references/opentofu-provider-docs.md` FIRST (sources + fetch method), then repo `opentofu/README.md` (per-module ground truth, schema notes, audit findings) | ## Standard loops (repeatable session shapes) diff --git a/.claude/skills/openstack-cloud-ops/references/opentofu-provider-docs.md b/.claude/skills/openstack-cloud-ops/references/opentofu-provider-docs.md new file mode 100644 index 0000000..a9cfc5e --- /dev/null +++ b/.claude/skills/openstack-cloud-ops/references/opentofu-provider-docs.md @@ -0,0 +1,113 @@ +# OpenTofu / provider documentation -- sources, versions, and fetch method + +Read this before writing or extending anything under `repo opentofu/` (the +VR1 DC-DC infrastructure-as-code, D-103). Built up during the 2026-07-09 +OpenTofu delivery; see `repo opentofu/README.md` for the full, per-module +account (schema notes, an "Audit pass" section, and every finding's exact +sourcing) -- this file is the index into that plus the fetch methodology, +so a future session does not have to rediscover it by trial and error. + +## The core lesson: doc-summarization is lossy, real example code is not + +Every provider doc page fetched this session went through a summarizing +model, which reliably got FIELD NAMES right but repeatedly lost or +mis-stated CALL SYNTAX (block vs. attribute style, exact nesting). The +`opentofu/` scaffold shipped with a real syntax bug from trusting doc +summaries alone (fixed once real example `.tf` files were fetched and +read directly -- see `repo opentofu/README.md`'s syntax-bug note). **For any +new provider/resource: find real example `.tf`/`.go` source before trusting +a doc-summary's syntax claims.** Doc-summaries are fine for "what fields +exist"; they are not reliable for "how do I write this." + +## Fetch gotchas + +- The Terraform/OpenTofu Registry's own doc pages (`registry.terraform.io/ + providers/.../docs`) are client-side-rendered -- a plain fetch returns only + the page shell, no content. Go straight to the provider's GitHub repo + instead. +- GitHub's raw file URLs need the CORRECT default branch (varies: `main` for + some repos, `master` for others -- check `api.github.com/repos//` + for the `default_branch` field first, don't guess). +- A general web search's own synthesized "summary" of a doc page can itself + be wrong (encountered once this session: a search summary confidently + named the wrong branch and file path). Treat search-result summaries as + leads to verify, not answers -- always follow through to the primary + source. +- When a fetch refuses to "reproduce verbatim" (copyright caution), ask for + a specific, narrow thing instead -- "list bare field names only, no + prose" reliably works where "reproduce this page" does not. +- `api.github.com/repos///contents/` (a JSON directory + listing) is a reliable way to discover the REAL current file layout when + a guessed raw-file path 404s -- doc reorganizations happen (one provider + moved from `website/docs/r/*.markdown` to `docs/resources/*.md` between + the versions training data and current reality disagree on). + +## Providers used in `repo opentofu/` + +### `dmacvicar/libvirt` (pinned `0.9.8`, confirmed 2026-07-09) +- Registry: https://registry.terraform.io/providers/dmacvicar/libvirt/latest + (JS-rendered -- use the GitHub repo for actual content) +- GitHub: https://github.com/dmacvicar/terraform-provider-libvirt (default + branch `main`) +- Docs: `docs/resources/*.md`, `docs/data-sources/*.md` (NOT the older + `website/docs/r/*.markdown` layout some search results/training data + reference -- that's stale) +- **Real example `.tf` files (the higher-value source): `examples/*.tf`** -- + `domain_with_network.tf`, `alpine_cloudinit.tf` were the two that resolved + every syntax question this session. Fetch these FIRST for any new + resource, before the doc pages. +- Key fact: this provider code-generates its schema as a 1:1 mirror of + libvirt's own domain/network/pool XML (confirmed via a general search of + provider discussion, not the docs pages themselves) -- when a Terraform + schema question stalls, libvirt's own native XML docs + (https://libvirt.org/formatdomain.html, https://www.libvirt.org/pci-addresses.html) + are a legitimate, stable cross-reference for what a nested attribute is + likely to be named/shaped, since the mapping is direct. + +### `canonical/maas` (pinned `2.7.2`, confirmed 2026-07-09) +- Registry: https://registry.terraform.io/providers/canonical/maas/latest + (same JS-rendering issue -- use GitHub) +- GitHub: https://github.com/canonical/terraform-provider-maas (default + branch `master` -- NOT `main`, confirmed by checking `default_branch` + directly rather than assuming) +- Docs: `docs/resources/*.md`, `docs/index.md` (provider block config) +- Moved from an older `maas/maas` registry namespace -- use `canonical/maas`, + not `maas/maas`, for anything current. +- Key lesson (see `repo opentofu/README.md`'s "MAAS registration + tc netem + research" section for the full account): `maas_vm_host` (register a virsh + chassis) and `maas_vm_host_machine` (MAAS composes/creates a new VM + itself) are NOT interchangeable -- read the schema (which fields are + INPUTS) before assuming a resource matches a design intent just because + its name sounds related. + +### OpenTofu core (language features, not a provider) +- `local-exec` provisioner: https://opentofu.org/docs/language/resources/provisioners/local-exec/ +- `terraform_data` (built-in, replaces the older `null_resource` for + provisioner-only use): https://opentofu.org/docs/language/resources/tf-data/ + (note the URL slug is `tf-data`, not `terraform_data` or `terraform-data`) +- Both pages rendered cleanly on a direct fetch (static docs, no JS-rendering + issue) -- these were the most reliable fetches of the whole session. + +### OPNsense (not a Terraform provider -- the guest OS itself) +- Official docs: https://docs.opnsense.org/manual/install.html, + .../manual/backups.html, .../development/backend/autorun.html +- Forum (community consensus, cite specific threads, not the forum + generally): https://forum.opnsense.org/index.php?topic=42517.0 (cloud-init + unreliable on FreeBSD -- the finding that ruled out reusing + `modules/cloudinit-vm` for OPNsense) +- GitHub issues (feature provenance): https://github.com/opnsense/core/issues/5733 + (ISO9660 Configuration-Importer support added specifically for VM/cloud + automation), .../issues/10017 (corroborates it shipped and is live) +- Full sourced writeup: `repo opentofu/README.md`'s "OPNsense deployment + research" section. + +## Where the full findings live (don't duplicate here, it will drift) + +`repo opentofu/README.md` is the authoritative, per-module account -- +schema notes, what's confirmed vs. assumed per attribute, and a dated +"Audit pass" section recording a later re-review (a real documentation +error was found and fixed there: an early claim that libvirt interface +list-order directly sets OPNsense's LAN/WAN role conflated two separate +mechanisms -- read that section before trusting anything about NIC +ordering/roles). Treat this file as the index/methodology; treat that one +as the ground truth for what each module actually does and why. diff --git a/docs/changelog-20260709-opentofu-audit.md b/docs/changelog-20260709-opentofu-audit.md new file mode 100644 index 0000000..88f5e63 --- /dev/null +++ b/docs/changelog-20260709-opentofu-audit.md @@ -0,0 +1,106 @@ +# Changelog 2026-07-09 -- OpenTofu module audit pass (DOCFIX-148) + +No live infrastructure touched -- documentation/comment corrections only, +plus confirmatory research. Operator asked to pull documentation for +anything drafted without it and review all 9 modules built this session for +errors/assumptions, after DOCFIX-142/144/145/146/147 had already shipped. + +## Item + +### 1. DOCFIX-148 -- audited every flagged UNVERIFIED/inferred/assumed note +FILES: `opentofu/modules/opnsense-edge/{main,variables}.tf`, +`opentofu/modules/node-vm/main.tf`, `opentofu/modules/maas-vm-host/variables.tf`, +`scripts/opnsense-build-config-iso.sh`, `opentofu/README.md`. + +METHOD: grepped the entire `opentofu/` tree for every "UNVERIFIED"/"NOT +confirmed"/"inference"/"assumed"/"not independently" marker left by prior +deliveries, then researched each one further (libvirt's own official XML +docs, a second independent source on the provider's volume-fetch mechanism, +FreeBSD/OPNsense interface-naming documentation, `genisoimage` reference +docs, MAAS provider zone/pool behavior) rather than re-stating the same +hedge unchanged. + +**One genuine error found and fixed**, not just a hedge re-confirmed: +`opnsense-edge`'s comments claimed `devices.interfaces` list order +("index 0 = LAN") directly determines OPNsense's LAN/WAN role. Researching +FreeBSD/OPNsense's interface-assignment model showed this conflates two +separate things: list order plausibly controls which `vtnetN` device number +a NIC gets (libvirt's own PCI-addressing docs: auto-assigned addresses +"usually match" XML order for a simple topology), but OPNsense's actual +LAN/WAN designation is a SEPARATE, explicit mapping set inside `config.xml` +itself (or the interactive/API assignment step) -- "`vtnet0` = WAN" is a +convention some guides choose, not an enforced rule. `main.tf`'s comments +and both `lan_network_name`/`wan_network_name` variable descriptions +rewritten to state this correctly: the variables express intent for which +network should end up as which role, but whoever writes the still-undesigned +`config.xml` must independently confirm the real vtnetN<->network mapping on +a boot and set ``/`` accordingly. No `config.xml` has been written +yet in this repo, so this corrects documentation accuracy for a future +author, not current runtime behavior. + +**Confidence upgraded (not resolved outright) on three other items:** +- `node-vm`'s `boot = { order = N }` shape: confirmed the provider + code-generates its schema as a 1:1 mirror of libvirt's own domain XML + (an architectural fact not visible in the resource doc pages alone), and + confirmed libvirt's own docs show the native element is exactly + `` -- consistent with the established single-attribute + mapping pattern already seen elsewhere in this schema. Still recommend a + real `tofu providers schema -json` check before trusting it, but this is + reasoned support now, not a bare guess. +- `create.content.url` accepting local filesystem paths: corroborated by a + second, independent source describing the underlying fetch mechanism as + supporting local paths alongside remote URLs. +- `genisoimage`/`xorriso` invocation flags (`-V`/`-J`/`-R`): confirmed as + standard, correct usage for a plain cross-platform ISO9660 image -- + separated cleanly from the genuinely still-open question (whether + OPNsense's Importer actually reads the result on a real boot), which + remains unverified. + +**Confirmed safe, previously assumed:** `maas_vm_host`'s `zone`/`pool` being +computed by MAAS when left unset -- validates the `default = null` design +choice in `modules/maas-vm-host` as a supported no-op. + +**Deliberately NOT re-researched:** findings already backed by a primary, +authoritative source on the first pass (the OPNsense Configuration Importer +mechanism itself, the `maas_vm_host` vs. `maas_vm_host_machine` distinction, +the provider's attribute-vs-block syntax fix) -- re-verifying already-solid +findings on every audit would be its own kind of waste; the audit targeted +specifically the notes marked as gaps. + +`opentofu/README.md` gained a new "Audit pass (2026-07-09)" section +documenting all of the above with reasoning, positioned as its own dated +entry alongside the existing OPNsense and MAAS/netem research sections +rather than silently editing history. + +### 2. Documentation references added to the `openstack-cloud-ops` skill +FILE (new): `.claude/skills/openstack-cloud-ops/references/opentofu-provider-docs.md`. +FILES (modified): `.claude/skills/openstack-cloud-ops/SKILL.md` (new routing +entry), `opentofu/README.md` (cross-reference back to the skill file). + +WHAT: operator asked for documentation links/access to be added to the skill +so future sessions can reference the direct sources rather than +rediscovering them. The new file indexes every provider/doc source used +building `opentofu/` (dmacvicar/libvirt, canonical/maas, OpenTofu's own +`local-exec`/`terraform_data` docs, OPNsense's docs/forum/GitHub issues) with +their exact URLs, confirmed versions, and default branches -- plus, more +valuably than the links alone, the FETCH METHODOLOGY that actually worked +this session: the Registry's own doc pages are JS-rendered and return empty +shells on a plain fetch (go to the provider's GitHub repo instead); GitHub +raw-file branch names vary and should be checked via the API's +`default_branch` field, not assumed; a general web search's own summary can +itself be wrong (encountered once); asking for "bare field names only" gets +past copyright-caution refusals that "reproduce this page" trips; real +example `.tf` files are consistently more reliable than doc-summarized prose +for syntax questions (the root cause of the DOCFIX-142 syntax bug this +whole delivery arc has referenced). + +Deliberately does NOT duplicate the per-module findings themselves +(schema notes, what's confirmed vs. assumed per attribute) -- that stays in +`opentofu/README.md` as the single source of truth, cross-referenced from +both directions, so it can't drift into two disagreeing copies. + +REVERT: `git rm .claude/skills/openstack-cloud-ops/references/opentofu-provider-docs.md`; +revert the two cross-reference edits if reverting fully. + +REVERT: `git checkout HEAD~ -- opentofu/modules/opnsense-edge opentofu/modules/node-vm/main.tf opentofu/modules/maas-vm-host/variables.tf scripts/opnsense-build-config-iso.sh opentofu/README.md` +(re-arms the LAN/WAN documentation error -- not recommended). diff --git a/docs/session-ledger.md b/docs/session-ledger.md index 8afbe31..7627066 100644 --- a/docs/session-ledger.md +++ b/docs/session-ledger.md @@ -845,7 +845,40 @@ up. Neither module instantiated (MAAS zone/pool, vcloud SSH target, and real netem parameters all still pending). `opentofu/README.md` gained a new sourced research section; workflow doc + visual tracker updated. - Uncommitted -- operator has not yet asked to commit this piece. + COMMITTED + PUSHED (ea276c5). +- **DONE -- DOCFIX-148 (changelog `docs/changelog-20260709-opentofu-audit.md`):** + operator asked to check back over all current modules, pull docs for + anything drafted without it, and review for errors/assumptions. Grepped + every "UNVERIFIED"/"inferred"/"assumed" marker across all 9 modules and + researched each further rather than re-stating hedges unchanged. **Found + and fixed a genuine documentation error, not just a hedge:** + `opnsense-edge`'s comments claimed interface list-order directly sets + OPNsense's LAN/WAN role -- researching OPNsense's own interface-assignment + model showed role assignment is a SEPARATE, explicit config.xml mapping, + not automatic from device order ("vtnet0=WAN" is a convention some guides + use, not an enforced rule). Comments + both LAN/WAN variable descriptions + corrected; no config.xml has been written yet so this fixes documentation + accuracy for whoever writes it next, not current runtime behavior. + Confidence UPGRADED (not fully resolved) on three items: `node-vm`'s + `boot`-order shape (confirmed the provider 1:1-mirrors libvirt's own XML, + and libvirt's own docs confirm ``'s single-attribute + shape), `create.content.url` accepting local paths (second independent + source), and the `genisoimage` flags (confirmed standard, cleanly + separated from the still-open "does OPNsense's Importer actually read it" + question). Confirmed safe: `maas_vm_host`'s `zone`/`pool` computed-if-unset + behavior. `opentofu/README.md` gained a new "Audit pass" section. Deliberately + did NOT re-research already-well-sourced findings (Importer mechanism, + vm_host-vs-vm_host_machine, the syntax-bug fix) -- targeted only real gaps. + Also added (same delivery): `.claude/skills/openstack-cloud-ops/references/ + opentofu-provider-docs.md` -- indexes every provider/doc source used + building `opentofu/` with exact URLs, confirmed versions, default + branches, AND the fetch methodology that actually worked this session + (Registry doc pages are JS-rendered, use GitHub instead; branch names vary, + check `default_branch` via the API; real example `.tf` files beat + doc-summarized prose for syntax questions). New SKILL.md routing entry; + cross-referenced from `opentofu/README.md` in both directions, no content + duplicated (README.md stays the single source of truth for per-module + findings). Uncommitted -- operator has not yet asked to commit this piece. diff --git a/opentofu/README.md b/opentofu/README.md index fbc91ed..06da703 100644 --- a/opentofu/README.md +++ b/opentofu/README.md @@ -5,7 +5,10 @@ OPNsense edge VMs, and the Office1 service VMs. Runs from the Office1 operator VM against the vcloud host libvirt. See `docs/dc-dc-buildout-design.md` and `docs/dc-dc-deployment-workflow.md` (Stage 1-2, tooling gap register item 2) -for the surrounding plan. +for the surrounding plan. See the skill's +`.claude/skills/openstack-cloud-ops/references/opentofu-provider-docs.md` +for where every provider fact below was sourced from and the fetch +methodology that worked -- read that first if extending any module here. **Status: SCAFFOLD, UNVALIDATED.** No `tofu`/`terraform` binary was available in the session that authored this -- nothing here has been run through @@ -309,13 +312,19 @@ - `create.content.url` accepts plain LOCAL filesystem paths, not just http(s) URLs -- inferred from `examples/alpine_cloudinit.tf` assigning `libvirt_cloudinit_disk.alpine_seed.path` (a local path, not a URL) - directly to `create.content.url`. `modules/opnsense-edge` relies on this - for BOTH its base image (`scripts/opnsense-prep-image.sh`'s output path) - and its config-seed ISO (`scripts/opnsense-build-config-iso.sh`'s output - path) -- neither is fetched from a real URL. Reasonably high confidence - (it's exactly what the real example does), but the mechanism wasn't named - explicitly in the docs pages fetched this session -- confirm with - `tofu plan` if it errors on a `file://`-less bare path. + directly to `create.content.url`, and corroborated by a second, + independent source during the 2026-07-09 audit pass describing the same + volume-creation mechanism as supporting "both local paths (relative to the + machine running Terraform) and remote images" via an underlying + go-getter-style fetch (a well-known library that treats bare/`file://` + local paths as one of its supported source schemes alongside http/https). + `modules/opnsense-edge` relies on this for BOTH its base image + (`scripts/opnsense-prep-image.sh`'s output path) and its config-seed ISO + (`scripts/opnsense-build-config-iso.sh`'s output path) -- neither is + fetched from a real URL. Reasonably high confidence now (two independent + corroborating sources), though still not seen named explicitly in the + provider's own prose docs -- confirm with `tofu plan` if it errors on a + `file://`-less bare path. - `maas_vm_host`'s `type` argument accepts `"virsh"` or `"lxd"` (confirmed from the provider's own docs); `power_address` is a flat string, no nested attributes; `zone`/`pool` are flat optional strings, not objects. Simpler, @@ -334,6 +343,63 @@ `input`) since nothing needs to be stored/reflected, just re-triggered when the bridge or netem args change. +## Audit pass (2026-07-09) -- re-checked every module's flagged assumptions + +Operator asked to pull documentation for anything drafted without it, and +review every module for errors/assumptions, after 5 modules had already +shipped across 4 changelogs. Went through every "UNVERIFIED"/"inferred"/ +"assumed" note in the codebase and did further research rather than leaving +them as-is. Outcomes: + +- **`node-vm`'s `boot = { order = N }` shape -- confidence UPGRADED, not + resolved outright.** Confirmed the provider code-generates its schema as a + 1:1 mirror of libvirt's own domain XML (an architectural fact, not + documented in the resource pages fetched earlier), and confirmed libvirt's + own official docs show the native element is exactly `` + -- one attribute, matching the established single-attribute-element + pattern already confirmed elsewhere in this schema (`mtu.size`, + `network.domain.name`). Comment updated to reflect this is now + well-supported reasoning, not a blind guess -- still recommend a real + schema check before trusting it. +- **`opnsense-edge`'s LAN/WAN interface-ordering claim -- GENUINE ERROR + FOUND AND FIXED, not just re-confirmed.** The original comment conflated + two separate things: which `vtnetN` device number a NIC gets (plausibly + controlled by `devices.interfaces` list order -- libvirt's own docs on PCI + addressing say auto-assigned addresses "usually match" XML order for a + simple topology like this one) and OPNsense's actual LAN/WAN ROLE + assignment, which is a SEPARATE, explicit mapping made inside `config.xml` + itself (or the interactive/API assignment step) -- confirmed via OPNsense's + own interface-configuration model ("maps logical 'friendly' names... to + physical or virtual system devices"). "`vtnet0` = WAN" is a convention + some setup guides choose, not a FreeBSD/OPNsense-enforced rule. Both + `main.tf`'s comments and `variables.tf`'s `lan_network_name`/ + `wan_network_name` descriptions were rewritten to state this correctly: + the variables express INTENT for the network layer, but the real LAN/WAN + role still has to be set correctly in `config.xml` against whichever + vtnetN is confirmed (via a real boot) to correspond to which network -- + this repo does not yet write that config.xml, so the correction changes + documentation accuracy, not current behavior, but would have misled + whoever writes it next. +- **`create.content.url` accepting local paths -- corroborated by a second, + independent source** (a go-getter-style fetch mechanism explicitly + described as supporting local paths). Confidence upgraded; schema notes + entry above updated. +- **`genisoimage`/`xorriso` flags (`-V`/`-J`/`-R`) -- CONFIRMED standard, + correct usage**, not just "reasonable." `scripts/opnsense-build-config-iso.sh`'s + header comment corrected to separate this (now confirmed) from the + genuinely still-open question (whether OPNsense's Importer actually reads + the result on a real boot). +- **`maas_vm_host`'s `zone`/`pool` optionality -- CONFIRMED safe.** Both are + computed by MAAS when left unset, validating the `default = null` design + choice as a supported no-op rather than an unhandled edge case. + +**Not re-litigated:** items already backed by a primary, authoritative +source the first time (OPNsense's Configuration Importer mechanism itself, +the provider's syntax-bug fix, `maas_vm_host` vs. `maas_vm_host_machine`) -- +re-researching well-sourced findings on every pass would be its own kind of +waste. The audit targeted specifically the notes marked UNVERIFIED/inferred/ +assumed, since those were the actual gaps. + ## Conventions carried from the rest of this repo - No hardcoded repo name (repo-lint L9 would need extending to cover `.tf` diff --git a/opentofu/modules/maas-vm-host/variables.tf b/opentofu/modules/maas-vm-host/variables.tf index c348cfb..8d0bd34 100644 --- a/opentofu/modules/maas-vm-host/variables.tf +++ b/opentofu/modules/maas-vm-host/variables.tf @@ -18,13 +18,13 @@ } variable "zone" { - description = "MAAS zone name for this VM host. Optional in the provider's own schema -- null lets MAAS apply its own default rather than this module guessing one." + description = "MAAS zone name for this VM host. Optional in the provider's own schema -- confirmed (2026-07-09 audit pass) as computed by MAAS when left unset, i.e. leaving this null is a safe, supported no-op, not an unhandled edge case. null here rather than this module guessing a zone name." type = string default = null } variable "pool" { - description = "MAAS resource pool name for this VM host. Optional in the provider's own schema -- null lets MAAS apply its own default rather than this module guessing one." + description = "MAAS resource pool name for this VM host. Optional in the provider's own schema -- same confirmed computed-if-unset behavior as `zone` above." type = string default = null } diff --git a/opentofu/modules/node-vm/main.tf b/opentofu/modules/node-vm/main.tf index dcabf23..e939cbb 100644 --- a/opentofu/modules/node-vm/main.tf +++ b/opentofu/modules/node-vm/main.tf @@ -10,13 +10,18 @@ # examples/domain_with_network.tf and examples/alpine_cloudinit.tf (fetched # and read directly this session, tag v0.9.8) -- attribute-style nested # objects (`os = { ... }`, `devices = { ... }`), NOT classic HCL blocks. The -# ONE inference in this file, clearly marked below, is the per-device `boot` -# object's internal shape (`order`) -- confirmed to EXIST as a field name on -# both `interfaces` and `disks` entries (matching native libvirt's per-device -# ), but its exact internal shape was not independently -# confirmed from a real example. VERIFY with `tofu providers schema -json` -# before the first real apply; if wrong, `tofu plan` will simply reject the -# attribute name, not silently misconfigure anything. +# ONE inference in this file, the per-device `boot` object's internal shape +# (`order`), is now BETTER-SUPPORTED after a 2026-07-09 audit pass (see +# opentofu/README.md): the provider is confirmed to code-generate its schema +# as a 1:1 mirror of libvirt's own domain XML, and libvirt's own official +# docs confirm the native XML element is exactly `` (one +# attribute, `order`) -- consistent with every other single-attribute +# element already confirmed in this schema (e.g. `mtu = { size = N }` mirrors +# ``). Reasonably confident, not a blind guess anymore, but +# still not seen in an actual generated schema dump -- VERIFY with +# `tofu providers schema -json` before the first real apply regardless; if +# wrong, `tofu plan` will simply reject the attribute name, not silently +# misconfigure anything. resource "libvirt_volume" "disk" { name = "${var.vm_name}-disk.qcow2" diff --git a/opentofu/modules/opnsense-edge/main.tf b/opentofu/modules/opnsense-edge/main.tf index e8019af..db213da 100644 --- a/opentofu/modules/opnsense-edge/main.tf +++ b/opentofu/modules/opnsense-edge/main.tf @@ -14,15 +14,27 @@ # scripts/opnsense-build-config-iso.sh respectively -- see each variable's # description for why. # -# UNVERIFIED, flagged plainly (see opentofu/README.md for the full account): +# UNVERIFIED, flagged plainly (see opentofu/README.md for the full account, +# including a 2026-07-09 audit pass that corrected the LAN/WAN claim below): # (a) whether the Configuration Importer's ISO9660 support actually behaves # as described once booted for real -- the research is well-sourced but not # independently tested this session (no real OPNsense boot, no -# genisoimage/xorriso available); (b) whether `devices.interfaces` list -# ORDER reliably maps to which NIC FreeBSD/OPNsense enumerates as its first -# vs. second interface (assumed here, consistent with libvirt XML ordering -# generally, but not independently confirmed for this specific guest OS). -# Confirm both on a real boot before trusting LAN/WAN role assignment. +# genisoimage/xorriso available). +# (b) `devices.interfaces` list ORDER and OPNsense's LAN/WAN ROLE are TWO +# SEPARATE THINGS, not one -- corrected during the audit pass (the original +# version of this comment conflated them). List order plausibly determines +# which `vtnetN` device number FreeBSD assigns each NIC (libvirt's own docs: +# PCI addresses auto-assigned in XML order "usually match" for a simple +# topology like this one -- reasonably well-supported, not a hard guarantee). +# But OPNsense's LAN/WAN designation is a SEPARATE, explicit mapping made +# inside config.xml itself (or the interactive/API assignment step) from +# device name to role -- "vtnet0 = WAN" is a convention some guides choose, +# not a FreeBSD/OPNsense-enforced rule. So: `var.lan_network_name` at index 0 +# only expresses INTENT for which network should end up as LAN; the real +# config.xml (still undesigned, see variables.tf) MUST independently +# confirm, on a real boot, which vtnetN corresponds to which network, and +# map / to the CORRECT device name accordingly -- do not assume +# "first in this list" automatically becomes OPNsense's LAN role. # # No PXE boot-order attribute is used here (unlike modules/node-vm): this VM # boots from its own pre-installed disk, not over the network, so the @@ -104,10 +116,12 @@ } ] - # order matters: index 0 is LAN, index 1 is WAN, matching OPNsense's own - # interface-1-is-LAN/interface-2-is-WAN default convention. See the - # UNVERIFIED note above -- this ordering assumption is not independently - # confirmed for this specific guest. + # index 0 = the network intended for LAN, index 1 = the network intended + # for WAN -- this ORDER plausibly controls which vtnetN device number + # each gets (see the file header's corrected note), but does NOT by + # itself make OPNsense treat either as LAN/WAN -- that role assignment + # is config.xml's job. Confirm actual vtnetN<->network mapping on a real + # boot before writing config.xml's / interface names. interfaces = [ { model = { diff --git a/opentofu/modules/opnsense-edge/variables.tf b/opentofu/modules/opnsense-edge/variables.tf index 47b1e5b..d86c1d4 100644 --- a/opentofu/modules/opnsense-edge/variables.tf +++ b/opentofu/modules/opnsense-edge/variables.tf @@ -53,15 +53,30 @@ variable "lan_network_name" { description = <<-EOT - libvirt_network name this edge's LAN-side interface attaches to. OPNsense - defaults interface 1 = LAN (see opentofu/README.md) -- kept as an - explicit named variable rather than an ordered list, deliberately, so a - LAN/WAN swap can't happen silently via list-ordering. + libvirt_network name intended to become this edge's LAN side -- placed + at interfaces[0] in main.tf, which plausibly (not guaranteed) determines + which vtnetN device FreeBSD assigns it. IMPORTANT (corrected 2026-07-09 + audit): this variable does NOT by itself make OPNsense treat it as LAN -- + that role assignment happens separately, inside config.xml's own + interface-to-role mapping (or the interactive/API assignment step). + "vtnet0 = WAN" is a convention some guides use, not an enforced rule. + Confirm the real vtnetN<->network mapping on a real boot (e.g. `ifconfig`) + before writing config.xml's / interface names -- do not assume + "passed as lan_network_name" automatically becomes OPNsense's LAN role. + Kept as an explicit named variable (not an ordered list) so the INTENT + stays clear in this file even though the real role still comes from + config.xml. EOT type = string } variable "wan_network_name" { - description = "libvirt_network name this edge's WAN-side interface attaches to (OPNsense's interface 2 = WAN convention -- see opentofu/README.md). The mesh-link legs (modules/mesh-link) or a per-site ISP-simulated segment are the expected callers here." - type = string + description = <<-EOT + libvirt_network name intended to become this edge's WAN side -- placed at + interfaces[1] in main.tf. Same caveat as lan_network_name: this ordering + does not itself assign the WAN role in OPNsense; config.xml does. The + mesh-link legs (modules/mesh-link) or a per-site ISP-simulated segment + are the expected callers here. + EOT + type = string } diff --git a/scripts/opnsense-build-config-iso.sh b/scripts/opnsense-build-config-iso.sh index 802484f..27877bd 100644 --- a/scripts/opnsense-build-config-iso.sh +++ b/scripts/opnsense-build-config-iso.sh @@ -8,12 +8,15 @@ # mechanically identical to how modules/cloudinit-vm attaches its NoCloud # seed via a libvirt_volume + create.content.url pointing at a local path. # -# NOTE: the genisoimage/xorriso invocation below is a reasonable, standard -# construction for a plain ISO9660 volume containing one file at a fixed -# path -- it has NOT been independently verified against a real OPNsense -# boot this session (neither tool nor a real OPNsense VM was available to -# test with). Confirm the Configuration Importer actually picks this up -# before relying on it operationally. +# NOTE (updated after a 2026-07-09 audit pass): the genisoimage/xorriso flags +# below (-V volume label, -J Joliet, -R Rock Ridge) are CONFIRMED standard, +# correct usage for building a plain cross-platform ISO9660 image from a +# directory -- this part is no longer a guess. What remains genuinely +# unverified is a DIFFERENT claim: whether OPNsense's Configuration Importer +# actually reads /conf/config.xml back off an ISO9660 volume built this way +# once booted for real (neither genisoimage/xorriso nor a real OPNsense VM +# was available this session to test that end-to-end). Confirm that +# specifically before relying on it operationally. # # Requires: genisoimage or xorriso. Read-only w.r.t. the repo; writes only # to and a self-cleaning temp dir.