# ── Site configuration ───────────────────────────────────────────────────────── hypervisors = { proxmox = { api_url = "https://192.168.7.240:8006/" node_name = "pve" storage = "local-lvm" } goose = { api_url = "https://192.168.7.241:8006/" node_name = "goose" storage = "local-lvm" } } ipam_endpoint = "https://ipam.example.net/api" network_subnets = { lan = "192.168.0.0/24" atlashorizon = "192.168.7.0/24" } ansible_user = "ansible" search_domain = "lan.oswaldtheotter.net" dns_nameservers = "192.168.7.2" technitium_host = "http://192.168.7.2:5380" # ── DNS (Technitium) ──────────────────────────────────────────────────────────── # Every declared node automatically gets an A record in the search_domain zone # (lan.oswaldtheotter.net). Define extra zones and manual records below. # Set to false to stop Terraform from managing Technitium DNS zones/records # (the LXC itself is unaffected; only module.technitium is skipped). technitium_enabled = false # Additional zones beyond the auto-managed search_domain zone. dns_zones = { # "internal.oswaldtheotter.net" = { type = "Primary" } # "10.168.192.in-addr.arpa" = { type = "Primary" } } # Manual records. In the search_domain zone, a record whose name matches an # auto-added host replaces that host's record instead of conflicting with it. dns_records = [ # { zone = "lan.oswaldtheotter.net", name = "router", type = "A", value = "192.168.0.1" }, # { zone = "lan.oswaldtheotter.net", name = "nas", type = "CNAME", value = "dev.lan.oswaldtheotter.net" }, # { zone = "lan.oswaldtheotter.net", name = "@", type = "MX", value = "mail.lan.oswaldtheotter.net", priority = 10 }, ] # ── Reverse proxy (Caddy) ─────────────────────────────────────────────────────── # Managed on the caddy LXC (192.168.0.5). The admin API listens on loopback, so # reach it by tunneling SSH to the box as the ansible user. Clear caddy_host to # disable Caddy management entirely. caddy_host = "http://localhost:2019" caddy_ssh_host = "ansible@192.168.0.5:22" # From: ssh-keyscan 192.168.0.5 — must be refreshed if the LXC is rebuilt. # Must be the ECDSA key: the provider's (old) Go SSH client prefers # ecdsa-sha2-nistp256 during negotiation and pins the exact key it receives. caddy_ssh_host_key = "192.168.0.5 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLjluBg9JCITeqG4NPad4VNTkAcZDY3y9FU9CP5BAuKIr3TDhTFYzuAdNnROCEhW1ddJI9ZFKI5y/3xqNTmkHbA=" # Each proxy serves plain HTTP on :80 by default; set protocol = "https" to # serve it on :443 instead. Note: .lan hostnames can't get public certificates, # so HTTPS sites use Caddy's internal CA — trust its root cert on your devices # to avoid browser warnings. caddy_proxies = { # dns = { # host = "dns.oswaldtheotter.lan" # upstreams = ["192.168.0.2:5380"] # } # secure-example = { # host = "secure.oswaldtheotter.lan" # upstreams = ["192.168.0.202:80"] # protocol = "https" # } code = { host = "code.oswaldtheotter.lan" upstreams = ["192.168.0.200:8080"] } } # ── Node definitions ─────────────────────────────────────────────────────────── nodes = { proxmox = { lxc = { "caddy" = { hostname = "caddy" vmid = 101 template = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst" unprivileged = true onboot = true tags = "" memory = 1024 swap = 0 disk_size = "8G" nic_name = "eth0" bridge = "vmbr0" ip = "192.168.0.5/24" nameserver = "192.168.0.2" gw = "192.168.0.1" } "audiopen" = { hostname = "audiopen" vmid = 801 template = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst" unprivileged = true onboot = true tags = "" memory = 512 swap = 0 disk_size = "8G" nic_name = "eth0" bridge = "vmbr0" ip = "192.168.0.202/24" gw = "192.168.0.1" } "remoteterm" = { hostname = "remoteterm" vmid = 117 template = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst" unprivileged = true onboot = false tags = "" memory = 2048 swap = 0 disk_size = "16G" nic_name = "eth0" bridge = "vmbr0" ip = "192.168.0.203/24" gw = "192.168.0.1" } "technitium" = { hostname = "technitium" vmid = 118 template = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst" unprivileged = true onboot = true tags = "" memory = 2048 swap = 0 disk_size = "8G" nic_name = "eth0" bridge = "vmbr0" ip = "192.168.0.2/24" gw = "192.168.0.1" # Manually added out-of-band so Technitium can also serve/resolve on # the atlashorizon VLAN; brought under management here to match. nic2 = { nic_name = "eth1" bridge = "vmbr0" vlan = 7 ip = "192.168.7.2/24" } } } machines = { } } goose = { lxc = { "dev" = { hostname = "dev" vmid = 200 template = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst" unprivileged = true onboot = true tags = "" memory = 4096 swap = 2048 disk_size = "32G" nic_name = "eth0" bridge = "vmbr0" vlan = 1 ip = "192.168.0.200/24" nameserver = "192.168.0.2" gw = "192.168.0.1" } "donetick" = { hostname = "donetick" vmid = 201 template = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst" unprivileged = true onboot = true tags = "" memory = 4096 swap = 2048 disk_size = "32G" nic_name = "eth0" bridge = "vmbr0" vlan = 1 ip = "192.168.0.202/24" nameserver = "192.168.0.2" gw = "192.168.0.1" } } # Flatcar Container Linux VMs, provisioned by Ignition. Read # opentofu/README.md (Flatcar section) and flatcar-todo.md before enabling. flatcar = { "oswald-runner" = { hostname = "oswald-runner" vmid = 210 ip = "192.168.7.60" vlan = 7 memory = 4096 cores = 4 disk_size = 32 qemu_guest_agent = false # Rendered with templatefile(): ${gitea_runner_token} comes from # Infisical (GITEA_RUNNER_TOKEN in secrets.tmpl), so the token is # never committed. Also runs n8n on :5678 (N8N_* secrets, Postgres # on argo); see the prerequisites in the Butane file. butane_file = "butane/oswald_runner.yaml.tftpl" } "ai" = { hostname = "ai" vmid = 211 ip = "192.168.7.61" vlan = 7 memory = 4096 cores = 2 disk_size = 32 qemu_guest_agent = false butane_file = "butane/ai.yaml.tftpl" } # Headscale subnet router / exit node. Registers as an ephemeral node, # so the preauth key must be --ephemeral --reusable; the routes and the # exit node still need approving on the headscale side once it appears. "oswl-connector" = { hostname = "oswl-connector" vmid = 212 ip = "192.168.7.62" vlan = 7 memory = 2048 cores = 2 disk_size = 32 qemu_guest_agent = false butane_file = "butane/oswl_connector.yaml.tftpl" } # Dozzle log viewer hub. Pulls container logs from the dozzle agents on # the other Flatcar VMs (port 7007) plus its own local docker socket. # Keeps no state on disk, so it is safe to rebuild at any time. "monitor" = { hostname = "monitor" vmid = 213 ip = "192.168.7.63" vlan = 7 memory = 2048 cores = 2 disk_size = 32 qemu_guest_agent = false butane_file = "butane/monitor.yaml.tftpl" } # CISA Known Exploited Vulnerabilities catalog as an RSS feed. # Stateless container; feed at :3000/rss. "cisa-kev-rss" = { hostname = "cisa-kev-rss" vmid = 214 ip = "192.168.7.64" vlan = 7 memory = 1024 cores = 1 disk_size = 16 qemu_guest_agent = false butane_file = "butane/cisa_kev_rss.yaml" } } machines = { "grist" = { hostname = "grist" vmid = 204 ip = "192.168.7.54" vlan = 7 template = "Debian13" full_clone = true onboot = true tags = "" agent = 1 memory = 4096 disk_size = "32" balloon = 1 cpu_type = "kvm64" cores = 4 sockets = 1 vcpus = 0 bios = "ovmf" machine = "q35" bridge = "vmbr0" } } } }