# infra/tofu — dev/test cluster nodes (OpenTofu + libvirt) Phase 1 **T-1.1**. Provisions the "Create" lifecycle stage (docs/02): a dedicated NAT network and 3 cloud-init'd KVM VMs, each with a thin OS disk and a blank thin Longhorn data disk. **Authoring only — nothing here has been applied.** Serves **R-ENV-01/02**, **R-PLAT-01**, ADR-0001 (k3s substrate), and the network half of the LOCKED PHASE1-SCOPE decisions. ## What it builds (defaults = LOCKED decisions) | Resource | Value | |---|---| | NAT network | `k3s-dev`, `10.73.0.0/24`, gateway `10.73.0.1`, **DHCP off** (static IPs) | | Nodes | `k3s-dev-1/2/3` at `10.73.0.11/.12/.13` | | Per node | 4 vCPU, 6 GiB RAM, **host-passthrough** CPU (nested virt for KubeVirt) | | OS disk | 20 GiB thin qcow2, backed by the base cloud image | | Data disk | 40 GiB blank thin qcow2 → `/dev/vdb`, mounted at `/var/lib/longhorn` by cloud-init | | Seed | `libvirt_cloudinit_disk` wired to `../cloud-init/` templates | | Reserved | `10.73.0.240–.250` kept out of any DHCP range for Cilium L2 ingress VIPs | Base OS image is a **variable** (`image_source`), defaulting to Debian 12 genericcloud amd64. All sizing/IPs/counts are variables — see `variables.tf`. ## Files - `versions.tf` — OpenTofu `1.9.0` pin + `dmacvicar/libvirt ~> 0.8.1` provider pin. - `variables.tf` — every knob (counts, sizing, IPs, image, SSH key). - `main.tf` — network, base/OS/data volumes, cloud-init seed, domains. - `outputs.tf` — node IPs/names, `primary_node_ip`, `k3s_api_endpoint`, and a rendered `ansible_inventory` for `infra/ansible/`. - `terraform.tfvars.example` — copy to `terraform.tfvars`; only `ssh_authorized_key` is required. ## Usage (operator, in the dev container — NOT run here) ``` cd infra/tofu cp terraform.tfvars.example terraform.tfvars # set ssh_authorized_key tofu init tofu plan tofu apply # hand the node IPs to Ansible: tofu output -raw ansible_inventory > ../ansible/inventory/hosts.ini ``` ## Hand-off contract - `primary_node_ip` / `k3s_api_endpoint` (`10.73.0.11:6443`) is the value the Ansible Cilium bootstrap and the Flux `platform/cilium` HelmRelease must BOTH use for `k8sServiceHost` / `k8sServicePort` (docs/02 parity contract). - Destroy/rebuild proof (T-1.5): `tofu destroy -target=libvirt_domain.node[1]` then `tofu apply` re-creates that node; Ansible re-joins it; Longhorn replicas reconverge (nodes are cattle — docs/02 statelessness). ## Static validation (PM, in dev container) ``` tofu fmt -check -recursive tofu init -backend=false tofu validate ```