Ansible
Ansible is the configuration-management tool for every host that doesn't run Talos Linux. Talos is API-driven and immutable — Ansible would be redundant. The Proxmox hosts, the NetBird connector LXCs on them, and the operator's Fedora workstation are all mutable Linux, and that's where Ansible earns its keep.
Source of truth: ansible/ at the repo root.
Where Ansible fits
The IaC tool and Ansible are complementary: OpenTofu creates the box, Ansible turns it into a useful one.
Playbooks and roles
Four playbooks live under ansible/playbooks/, and they map cleanly onto the host groups:
| Playbook | Hosts | What it does |
|---|---|---|
proxmox_commissioning.yaml | proxmox_hosts | Applies the igpu-passthrough role — IOMMU in GRUB, VFIO modules, the iGPU bound to vfio-pci, initramfs regenerated |
proxmox_update.yml | proxmox | apt upgrade + autoremove, and keeps the operator SSH keys in root's authorized_keys |
netbird_lxc_unattended_upgrades.yaml | netbird_lxcs | Enables unattended-upgrades for Debian and the NetBird package origin on the connector LXCs |
local_software.yaml | localhost | Sets up the Fedora workstation — zsh (with the dotfiles and Nerd Font), and optionally vscode, nextcloud, homebrew, docker, netbird, thunderbird |
The roles under ansible/roles/ are exactly the ones those playbooks reference: igpu-passthrough for the hypervisors, and docker, homebrew, netbird, nextcloud, thunderbird, vscode, zsh for the workstation. The igpu-passthrough role is the load-bearing one — it is what makes the GPU passthrough into the Talos worker VMs possible.
Inventory pattern
There is a single inventory, ansible/inventory.yml, wired in through ansible.cfg so no -i flag is needed. It groups hosts by role, not by physical location:
proxmox→proxmox_hosts:proxmox-1…proxmox-4at192.168.100.51–54, reached asroot.netbird_lxcs:lxc-proxmox{1,2,3,4}-netbirdat192.168.100.71–74, the mesh connectors created by OpenTofu on each hypervisor.synology:maresaat192.168.0.5, the Docker host.home-assistat192.168.2.20, a standalone appliance.
Talos nodes are absent by design — there is no SSH to reach them. group_vars/edge.yaml and host_vars/edge-1.yaml are leftovers from the K3s edge era (December 2024 to October 2025, see K3s (legacy)) and match no host in the inventory.
Conventions
- Idempotent always. Running a playbook twice should be a no-op the second time.
- No state files. Drift detection is
--check --diff; if the diff is non-empty, that's the drift. - Nothing sensitive in the playbooks. The current plays carry only public SSH keys and package lists; cluster and cloud secrets live in SOPS on the Kubernetes and Tofu side (Operations → Secrets). The only Ansible Vault strings left are the K3s-era edge variables, kept for the record and referenced by no play.
- Roles before tasks. A playbook should mostly compose roles, not declare tasks inline. Tasks-in-playbooks are a smell that wants extracting.
Day-2 commands
# From ansible/ — the inventory comes from ansible.cfg
# Dry-run the hypervisor update
ansible-playbook playbooks/proxmox_update.yml --check --diff
# Apply
ansible-playbook playbooks/proxmox_update.yml
# Commission a single new hypervisor
ansible-playbook playbooks/proxmox_commissioning.yaml --limit proxmox-4
Where to look next
- Proxmox — the hypervisors the commissioning and update playbooks target
- Docker hosts — the runtime that the
dockerrole provisions - Operations → Secrets → SOPS — the pattern that keeps Tofu tokens and cluster secrets out of plaintext