Replaces the Debian VM that hosted the Gitea Actions runner and adds two new hosts on goose, all provisioned by Ignition instead of cloud-init: - oswald-runner: Gitea Actions runner, keeping its registration in /var/lib/oswald_runner on disk rather than running ephemeral, which was leaving stale offline runners behind whenever a job was interrupted. - ai: LiteLLM proxy and the Gitea MCP server. - oswl-connector: tailscaled against headscale, advertising 192.168.7.0/24 and itself as an exit node. Their registration tokens and keys come from Infisical through the new butane_vars plumbing, so nothing secret is committed -- but each one does land in plaintext in the rendered Ignition snippet on the node, which is the tradeoff for having no cloud-init here. The provider gains an ssh block on both nodes because Proxmox has no API for uploading snippets. That, the snippets content type, and the headscale preauth key all have to be set up by hand first; flatcar-todo.md has the steps and the reasoning behind the awkward parts. Co-Authored-By: Claude Opus 5 <[email protected]>
265 lines
9.0 KiB
HCL
265 lines
9.0 KiB
HCL
# ── 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 = "[email protected]: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.
|
|
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"
|
|
}
|
|
}
|
|
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"
|
|
}
|
|
}
|
|
}
|
|
} |