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]>
206 lines
7.1 KiB
Terraform
206 lines
7.1 KiB
Terraform
variable "hypervisors" {
|
|
description = "Per-hypervisor configuration. Keys must match provider aliases in providers.tf (e.g. hv2, hv3)."
|
|
type = map(object({
|
|
api_url = string # e.g. https://192.168.1.10:8006/
|
|
node_name = string
|
|
storage = string
|
|
}))
|
|
}
|
|
|
|
variable "ipam_endpoint" {
|
|
description = "Base URL of the PHPIPAM API, e.g. https://ipam.example.com/api"
|
|
type = string
|
|
default = ""
|
|
}
|
|
|
|
variable "network_subnets" {
|
|
description = "Named map of CIDR ranges managed by PHPIPAM. Only used when enable_ipam = true."
|
|
type = map(string)
|
|
default = {}
|
|
}
|
|
|
|
variable "ansible_user" {
|
|
description = "User created on each managed host for Ansible access."
|
|
type = string
|
|
}
|
|
|
|
variable "search_domain" {
|
|
description = "DNS search domain set on all managed hosts."
|
|
type = string
|
|
}
|
|
|
|
variable "dns_nameservers" {
|
|
description = "DNS nameserver(s) set on all managed hosts."
|
|
type = string
|
|
}
|
|
|
|
variable "technitium_host" {
|
|
description = "URL of the Technitium DNS Server API (e.g. http://192.168.0.3:5380). Required once Technitium is running."
|
|
type = string
|
|
default = ""
|
|
}
|
|
|
|
variable "technitium_enabled" {
|
|
description = "Set to false to stop Terraform from managing Technitium DNS zones/records, without changing anything else. Still requires technitium_api_token in Infisical to actually turn on."
|
|
type = bool
|
|
default = true
|
|
}
|
|
|
|
variable "dns_zones" {
|
|
description = "Additional Technitium DNS zones to create, beyond the auto-managed search_domain zone. Keyed by zone name."
|
|
type = map(object({
|
|
type = optional(string, "Primary")
|
|
forwarder = optional(string)
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "dns_records" {
|
|
description = "Manually managed DNS records. Each targets a zone by name (the search_domain zone or one from dns_zones). A record in the search_domain zone whose name matches an auto-added host replaces that host record instead of conflicting with it."
|
|
type = list(object({
|
|
zone = string
|
|
name = string # subdomain label, or "@" for the zone apex
|
|
type = string # A, AAAA, CNAME, NS, TXT, MX
|
|
value = string # IP / target / text / mail exchanger
|
|
ttl = optional(number)
|
|
priority = optional(number) # required for MX records
|
|
}))
|
|
default = []
|
|
}
|
|
|
|
variable "caddy_host" {
|
|
description = "Caddy admin API endpoint. When using the SSH tunnel this is the endpoint as seen from the caddy host (e.g. http://localhost:2019). Leave empty to disable Caddy management."
|
|
type = string
|
|
default = ""
|
|
}
|
|
|
|
variable "caddy_ssh_host" {
|
|
description = "SSH target (user@host:port) used to tunnel to the Caddy admin API. Empty to reach caddy_host directly."
|
|
type = string
|
|
default = ""
|
|
}
|
|
|
|
variable "caddy_ssh_host_key" {
|
|
description = "SSH host key of the caddy host, known_hosts format (get it with: ssh-keyscan <ip>). Required when caddy_ssh_host is set."
|
|
type = string
|
|
default = ""
|
|
}
|
|
|
|
variable "caddy_proxies" {
|
|
description = "Reverse-proxy sites keyed by name. Each maps a hostname to one or more upstream backends (Caddy dial targets, e.g. \"192.168.0.40:32400\"). protocol picks plain HTTP on :80 (default) or HTTPS on :443 with automatic certificates. Set path to restrict the route to specific request paths."
|
|
type = map(object({
|
|
host = string
|
|
upstreams = list(string)
|
|
protocol = optional(string, "http")
|
|
path = optional(list(string))
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
variable "ansible_inventory_path" {
|
|
description = "Path where the generated Ansible inventory file is written. Defaults to ../ansible/inventories/generated.yml relative to this module, which points to the parent repo when used as a git submodule."
|
|
type = string
|
|
default = "./ansible/inventories/generated.yml"
|
|
}
|
|
|
|
# LXC, VM and Flatcar VM definitions per hypervisor, keyed by the same names
|
|
# used in hypervisors and providers.tf. All three maps default to {} so a node
|
|
# can be declared with any subset of workload types.
|
|
variable "nodes" {
|
|
type = map(object({
|
|
lxc = optional(map(object({
|
|
hostname = string
|
|
vmid = number
|
|
template = string
|
|
os_type = optional(string, "debian")
|
|
unprivileged = bool
|
|
onboot = bool
|
|
tags = string
|
|
cores = optional(number, 1)
|
|
memory = number
|
|
swap = number
|
|
disk_size = string
|
|
nic_name = string
|
|
bridge = string
|
|
vlan = optional(number)
|
|
ip = string
|
|
nameserver = optional(string)
|
|
gw = string
|
|
nic2 = optional(object({
|
|
nic_name = optional(string, "eth1")
|
|
bridge = optional(string, "vmbr0")
|
|
vlan = optional(number)
|
|
ip = string
|
|
gw = optional(string)
|
|
}))
|
|
})), {})
|
|
machines = optional(map(object({
|
|
hostname = string
|
|
vmid = number
|
|
ip = string
|
|
vlan = optional(number, 0)
|
|
template = string
|
|
full_clone = bool
|
|
onboot = bool
|
|
ciupgrade = optional(bool, false)
|
|
tags = string
|
|
agent = number
|
|
memory = number
|
|
disk_size = optional(string, "32")
|
|
balloon = number
|
|
cpu_type = string
|
|
cores = number
|
|
sockets = number
|
|
vcpus = number
|
|
bios = string
|
|
machine = string
|
|
bridge = string
|
|
nic2 = optional(object({
|
|
bridge = optional(string, "vmbr0")
|
|
vlan = optional(number)
|
|
ip = string
|
|
gw = optional(string)
|
|
}))
|
|
})), {})
|
|
flatcar = optional(map(object({
|
|
hostname = string
|
|
vmid = number
|
|
ip = string
|
|
gw = optional(string)
|
|
vlan = optional(number, 0)
|
|
bridge = optional(string, "vmbr0")
|
|
onboot = optional(bool, true)
|
|
tags = optional(string, "")
|
|
cores = optional(number, 2)
|
|
cpu_type = optional(string, "x86-64-v2-AES")
|
|
memory = optional(number, 4096)
|
|
disk_size = optional(number, 32)
|
|
qemu_guest_agent = optional(bool, false) # needs a Butane unit running qemu-ga in a container
|
|
butane = optional(string) # inline Butane YAML
|
|
butane_file = optional(string) # path relative to this directory
|
|
})), {})
|
|
}))
|
|
default = {}
|
|
}
|
|
|
|
# Flatcar release imported onto every node that runs Flatcar VMs. Pin a version
|
|
# and copy its sha512 from the image's .DIGESTS file; see opentofu/README.md.
|
|
variable "flatcar_image" {
|
|
type = object({
|
|
channel = optional(string, "stable")
|
|
version = string
|
|
sha512 = string
|
|
datastore = optional(string, "local")
|
|
})
|
|
default = {
|
|
version = "4593.2.5"
|
|
sha512 = "21fdba07ffc73aac80aa40f1f99e0460e2d53b1c2815a774d5e2940d2b5cfa78fb2c86391fbf3bb76e28e8ece1ca41333f30ce18d1dd3f0c016e95bed120aba8"
|
|
}
|
|
}
|
|
|
|
variable "snippets_datastore" {
|
|
description = "Datastore holding Ignition snippets for Flatcar VMs. Needs the snippets content type enabled on every node."
|
|
type = string
|
|
default = "local"
|
|
}
|