| 2026-07-10 |

DOCFIX-149: OPNsense config.xml template + tested renderer
...
Real design work, researched before drafting: fetched OPNsense's own actual
shipped config.xml.sample and its Route.xml static-routes model before
writing anything.
opentofu/templates/opnsense-config.xml.tmpl: {{TOKEN}}-parameterized (reuses
this repo's existing clientdocs convention), covering 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 real sample
fetch directly confirmed last turn's audit finding with an actual example:
it ships literal placeholder device names inside each interface's own <if>
block, proving LAN/WAN role assignment really is that explicit per-block
mapping, not declaration order.
scripts/opnsense-render-config.sh: the renderer. Needs no external tool
(unlike every other opnsense-* script this session), so it's tested
END-TO-END, not just guard clauses -- 8/8, including well-formed-XML
validation. The harness caught a real bug before it shipped: the token
HOSTNAME collides with bash's own built-in $HOSTNAME variable (unset
doesn't actually clear it), silently passing a test that should have
failed. Renamed to OPNSENSE_HOSTNAME throughout.
opentofu/templates/README.md's token legend marks exactly which values are
real (NTP pool default, D-106 naming) vs. pending Stage 0 ratification
(D-100/D-101/D-107) vs. only measurable on a real boot (vtnetN assignment)
vs. a security requirement (root password hash must be freshly generated,
never the stock sample's own shipped default).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-147: opentofu/modules/maas-vm-host + modules/netem-link
...
Registers the vcloud host with MAAS as a virsh VM host, and applies tc netem
WAN-simulation parameters -- the two remaining unbuilt mechanisms from the
OpenTofu scope, researched before drafting per instruction.
maas-vm-host uses canonical/maas's maas_vm_host (register the chassis so
MAAS discovers already-existing VMs), deliberately not maas_vm_host_machine
(composes new VMs itself -- takes cores/memory/disks as inputs). Read both
schemas and cross-checked against D-103's explicit "MAAS does NOT compose
new ones" before picking one.
netem-link uses terraform_data (OpenTofu's own recommended replacement for
null_resource) with an SSH-wrapped local-exec provisioner -- confirmed
local-exec runs on the machine invoking tofu apply (Office1 per D-103), not
the vcloud host where the bridges live, so a bare local command would do
nothing useful.
Neither module instantiated: needs a real MAAS zone/pool, the vcloud host's
SSH target, and real netem parameters, none of which exist yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-146: opnsense-edge module + nano-image/config-ISO prep scripts
...
Implements the mechanism identified in today's OPNsense deployment research
(cloud-init confirmed unreliable on FreeBSD; the real native mechanism is
OPNsense's Configuration Importer, which scans an attached volume for
/conf/config.xml and gained ISO9660 support specifically for VM/cloud
automation).
scripts/opnsense-prep-image.sh: downloads the nano image, decompresses,
converts raw->qcow2, resizes -- work create.content.url almost certainly
can't do itself (a plain fetch, no decompression). No hardcoded mirror URL;
OPNSENSE_MIRROR_BASE is required explicitly.
scripts/opnsense-build-config-iso.sh: builds the ISO9660 image containing
/conf/config.xml that the Configuration Importer picks up.
opentofu/modules/opnsense-edge: wires both together, mechanically identical
to modules/cloudinit-vm's cdrom-attach shape but with no libvirt_cloudinit_disk
resource (wrong format for this). Explicit lan_network_name/wan_network_name
variables rather than an ordered list, since OPNsense's interface-1-is-LAN
convention makes list-ordering a real place for a silent mistake to hide. No
PXE boot-order attribute used -- this VM boots from its own disk, not the
network, so node-vm's flagged-unverified boot field doesn't apply here.
Both new scripts' harnesses (4/4, 3/3) exercise REAL missing-tool guards --
qemu-img/genisoimage/xorriso are all genuinely absent from this environment.
Two things flagged, not presented as fact: whether the Importer's ISO9660
behavior holds on a real boot, and whether interface list-order reliably
maps to LAN/WAN NIC enumeration for this guest. config_iso_path has no
default -- real config.xml content per site is still undesigned. Not
instantiated in root main.tf.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-145 + OPNsense deployment research
...
opentofu/modules/base-image + modules/cloudinit-vm: the cloud-init/pre-built-
image VM pattern for Office1's own service VMs (MAAS-region, NetBox,
GitBucket), verified against examples/alpine_cloudinit.tf. The base image's
.path is threaded across the module boundary as a real attribute reference
rather than reconstructed from pool+name strings. user_data/meta_data/
network_config are required inputs with no default -- the real Office1 VM
configs aren't designed yet, and a plausible DHCP default would silently
fail anyway since these planes carry no libvirt DHCP.
OPNsense research (no DOCFIX -- research delivery, not code): checked
OPNsense's own docs/forum/GitHub. Confirmed cloud-init is genuinely
unreliable on OPNsense (FreeBSD; OPNsense's own forum consensus), closing
the open question cloudinit-vm's build left. Found the real mechanism: the
Configuration Importer scans an attached volume for /conf/config.xml in a
2-3s boot window; ISO9660 support was added to it specifically for VM/cloud
automation -- mechanically identical to cloudinit-vm's cdrom-attach shape,
different payload. Use the nano image for KVM, not the installer images.
Fully sourced in opentofu/README.md. Not built yet, itemized: a decompress/
convert preprocessing script, an ISO9660 config.xml-seed builder, and the
real config.xml content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-144: opentofu/modules/node-vm + fix real syntax bug in DOCFIX-142
...
Built the D-103 node-VM pattern (blank disk, PXE-boot, MAAS-managed) for
Stage 3 -- this time from the provider's own real example .tf files
(examples/domain_with_network.tf, examples/alpine_cloudinit.tf), not
doc-summarization alone, since that confidence gap is exactly why the domain
module was deferred last time.
That surfaced a real bug in the already-pushed dc-planes/mesh-link/
dc-storage-pool modules (14d7382): they used classic HCL block syntax
(domain { ... }) where this provider's current schema needs attribute-style
objects (domain = { ... }) -- confirmed once real examples showed the actual
convention. All three fixed; opentofu/README.md's schema notes now state this
as a provider-wide assumption for any field not personally confirmed
otherwise.
One inference flagged, not presented as fact: the per-device boot-order
attribute's exact internal shape ({ order = N }) -- the field's existence is
confirmed, its shape is not. Not instantiated in root main.tf: node
count/sizing is a pending Phase-0 decision, not invented here. Still
deferred: the cloud-init/base-image VM pattern (Office1 services + OPNsense),
DC2 planes, netem application.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
Fold OPNsense's four separable sub-gaps into the tooling gap register
...
Gap #4 ("OPNsense automation + netem") was too coarse -- broke it into what
it actually is: (a) network substrate DONE (modules/mesh-link), (b) the edge
VM/image BLOCKED on the deferred domain module with no image source chosen,
(c) OPNsense's own routing/firewall config NOT STARTED with no mechanism
picked (config.xml is untouched by the libvirt provider), (d) tc netem WAN
simulation is a separate OS-level mechanism with no Terraform-native
equivalent, blocked on an unruled D-100 parameter decision before it can even
be scripted. Same breakdown mirrored into opentofu/README.md's deferred-items
list and the companion visual tracker.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-142: opentofu/ network + storage-pool scaffold (VR1 IaC, gap #2)
...
First OpenTofu content in the repo. Network/pool layer only: modules/dc-planes
(the six per-DC planes, D-052/D-100), modules/mesh-link (the D-100 dark-fiber
triangle), modules/dc-storage-pool, wired for DC1 (inherits DC0's CIDRs per
D-101) and Office1. DC2 deliberately not wired -- its CIDRs aren't assigned
yet (D-101 open sub-item).
Provider schema (dmacvicar/libvirt v0.9.8) verified against its actual current
docs this session, not memory -- it has diverged materially from older common
examples (network isolation is a nested forward.mode, not top-level mode;
libvirt_domain restructured to ~40 args mirroring raw libvirt XML). Node-VM/
volume resources, OPNsense config, and tc netem application are deliberately
deferred: that schema is too large/unfamiliar to author safely without a real
`tofu providers schema` pass on a connected machine -- see opentofu/README.md
for the full account and the concrete next step.
Added scripts/opentofu-validate.sh (fmt/init/validate wrapper) + a harness
testing what's actually exercisable without a tofu binary (none is available
anywhere this repo has been worked in this session -- logged, not hidden).
Extended repo_lint.py's L1 check to cover .tf files (uncovered before since
none existed). repo-lint 0 fail / 1 documented warn; both harnesses green.
Workflow doc + its companion visual tracker updated to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
Add DC-DC deployment workflow overview + audited tooling gap register
...
Followable stage-by-stage checklist for the VR1 buildout (Stage 0 decision
ratification through Stage 7 Designate/COS/CAPI), cross-referenced to
docs/dc-dc-buildout-design.md and D-100..D-110. Includes an 11-item tooling
gap register audited directly against the repo (scripts/, tests/, netbox/,
runbooks/) rather than inferred from the design doc -- notably that
lib-net.sh/lib-hosts.sh are not actually DC-agnostic yet (single-deployment
hardcoded literals), which the Stage 5 reuse claim depends on, and that
OpenTofu has zero files in the repo today.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|