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]>
113 lines
3.9 KiB
Terraform
113 lines
3.9 KiB
Terraform
terraform {
|
|
required_providers {
|
|
proxmox = {
|
|
source = "bpg/proxmox"
|
|
version = "0.112.0"
|
|
}
|
|
local = {
|
|
source = "hashicorp/local"
|
|
version = "~> 2.2"
|
|
}
|
|
# Butane -> Ignition transpiler for Flatcar VMs (no provider config needed).
|
|
ct = {
|
|
source = "poseidon/ct"
|
|
version = "~> 0.14"
|
|
}
|
|
technitium = {
|
|
source = "kenske/technitium"
|
|
version = "~> 0.2"
|
|
}
|
|
caddy = {
|
|
source = "conradludgate/caddy"
|
|
version = "~> 0.2"
|
|
}
|
|
# phpipam = {
|
|
# source = "lord-kyron/phpipam"
|
|
# version = "1.6.2"
|
|
# }
|
|
}
|
|
}
|
|
|
|
provider "technitium" {
|
|
host = var.technitium_host
|
|
# Fall back to a placeholder so the provider passes its credential check on
|
|
# the first run (before the token exists in Infisical). No technitium
|
|
# resources are created while the token is absent, so the dummy is never used.
|
|
token = coalesce(try(data.external.infisical.result["technitium_api_token"], ""), "token-not-yet-provisioned")
|
|
}
|
|
|
|
provider "caddy" {
|
|
# Endpoint as seen from wherever the request lands: with the SSH tunnel below
|
|
# this is the caddy host's own loopback admin API; without it, a directly
|
|
# reachable admin endpoint. Falls back to the Caddy default so the provider
|
|
# configures cleanly before caddy_host is set (no resources use it until then).
|
|
host = coalesce(var.caddy_host, "http://localhost:2019")
|
|
|
|
# Tunnel to the admin API over SSH when it only listens on loopback (the
|
|
# default, recommended setup). Reuses the ansible key generated above. Leave
|
|
# caddy_ssh_host empty to talk to caddy_host directly.
|
|
dynamic "ssh" {
|
|
for_each = var.caddy_ssh_host != "" ? [1] : []
|
|
content {
|
|
host = var.caddy_ssh_host
|
|
key_file = local_sensitive_file.ssh_private_key.filename
|
|
host_key = var.caddy_ssh_host_key
|
|
}
|
|
}
|
|
}
|
|
|
|
data "external" "infisical" {
|
|
program = ["bash", "-c", "infisical export --template=./secrets.tmpl"]
|
|
}
|
|
|
|
# Uncomment when phpIPAM is running and credentials are in Infisical.
|
|
# provider "phpipam" {
|
|
# app_id = "tofu"
|
|
# endpoint = var.ipam_endpoint
|
|
# username = coalesce(try(data.external.infisical.result["ipam_username"], ""), "user-not-yet-provisioned")
|
|
# password = coalesce(try(data.external.infisical.result["ipam_password"], ""), "pass-not-yet-provisioned")
|
|
# insecure = false
|
|
# nest_custom_fields = false
|
|
# }
|
|
|
|
# One provider alias per hypervisor. The API token is "user@realm!tokenid=secret".
|
|
provider "proxmox" {
|
|
alias = "proxmox"
|
|
endpoint = trimsuffix(var.hypervisors["proxmox"].api_url, "/api2/json")
|
|
api_token = "${data.external.infisical.result["token_id"]}=${data.external.infisical.result["hv2_token_secret"]}"
|
|
insecure = true
|
|
|
|
# SSH is only used to upload Ignition snippets for Flatcar VMs (Proxmox has
|
|
# no API for snippets). Requires the ansible public key in root's
|
|
# authorized_keys on the node; see flatcar-todo.md.
|
|
ssh {
|
|
username = "root"
|
|
private_key = base64decode(data.external.infisical.result["ssh_private_key_b64"])
|
|
|
|
node {
|
|
name = var.hypervisors["proxmox"].node_name
|
|
address = regex("^https?://([^:/]+)", var.hypervisors["proxmox"].api_url)[0]
|
|
}
|
|
}
|
|
}
|
|
|
|
provider "proxmox" {
|
|
alias = "goose"
|
|
endpoint = trimsuffix(var.hypervisors["goose"].api_url, "/api2/json")
|
|
api_token = "${data.external.infisical.result["token_id"]}=${data.external.infisical.result["goose_token_secret"]}"
|
|
insecure = true
|
|
|
|
# SSH is only used to upload Ignition snippets for Flatcar VMs (Proxmox has
|
|
# no API for snippets). Requires the ansible public key in root's
|
|
# authorized_keys on the node; see flatcar-todo.md.
|
|
ssh {
|
|
username = "root"
|
|
private_key = base64decode(data.external.infisical.result["ssh_private_key_b64"])
|
|
|
|
node {
|
|
name = var.hypervisors["goose"].node_name
|
|
address = regex("^https?://([^:/]+)", var.hypervisors["goose"].api_url)[0]
|
|
}
|
|
}
|
|
}
|