Run oswald-runner, ai and oswl-connector as Flatcar VMs

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]>
This commit is contained in:
nickandClaude Opus 5 committed 2026-09-22 10:02:23 +00:00
1 parent 5ee0114281
commit 90a2753a2a
11 files changed
+601 -42

No files matched your search

+41 -3
View File
@@ -103,9 +103,9 @@ variable "ansible_inventory_path" {
default = "./ansible/inventories/generated.yml"
}
# LXC and VM definitions per hypervisor, keyed by the same names used in
# hypervisors and providers.tf. Both lxc and machines default to {} so a node
# can be declared with only one type of workload.
# 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({
@@ -162,6 +162,44 @@ variable "nodes" {
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"
}