Fleet management¶
Ansible is the primary way to manage collectors, patching and container
updates across the fleet. scripts/lxc-install.sh collector is a legacy
bootstrap path.
New to Ansible?
This page assumes you know what an inventory, a playbook and a role are. If not, read Getting started first; it takes about fifteen minutes and uses examples from this repo.
cd ansible
ansible-playbook playbooks/site.yml # everything, in order
ansible-playbook playbooks/site.yml --limit rpi5 # one host
ansible-playbook playbooks/site.yml --limit pi # one group
ansible-playbook playbooks/site.yml --check --diff # dry run
Safe to re-run. A clean fleet reports changed=0 everywhere.
What this manages¶
monitored is bifurcated by platform, then by distro family. Every
monitored host sits in exactly one leaf.
| Group | Hosts | Collector | Auto-patched | Container updates |
|---|---|---|---|---|
lxc → lxc_debian |
tailscale-router, plex, memory, monitor-lxc | yes | yes, central see below |
memory only |
vm → vm_debian |
ubuntu-dev, ubuntu-ai, cloud-services, matrix | yes | yes | yes |
pi → pi_debian |
rpi5, rpi4b | yes | yes | rpi5 only |
Today every leaf is _debian. That is the point of the split rather than an
argument against it: a future vm_fedora slots in beside its sibling without
disturbing anything, and --limit vm keeps meaning "every VM". Variables
divide along the same seam, and deeper groups win:
- platform (
lxc/vm/pi): true because of the hardware or the virtualisation. SD-card IO on the Pis. Shared kernel on the containers. - distro (
*_debian): true because of the package manager. Anything naming an apt package belongs here, because that is exactly what a_fedorasibling would not share.
_debian means the Debian family
Four members of vm_debian actually run Ubuntu 26.04. They are grouped
with Debian because the axis every role in this repo cares about is
apt-vs-dnf, and Ubuntu is on the apt side of it. Reserve a new leaf for
distros that would genuinely break these roles.
Read the platform off the host, never off its name
Two hosts are not what they look like. tailscale-router is an LXC,
not a VM: it reports a -pve kernel only because a container shares the
hypervisor's kernel, and it has no /etc/pve. matrix is a VM, not
a container, despite sitting on the LAN beside the LXC guests, which is
why it is the one guest with a real sudo setup. Check before you move
one: ansible <host> -m setup -a 'filter=ansible_virtualization*'.
Overlay groups¶
These cut across the platform tree because they answer different questions. A host is in as many as apply.
| Group | Hosts | Answers |
|---|---|---|
lan_guests |
plex, memory, matrix | how it is reached: ProxyJump via lan_jump_host |
central |
monitor-lxc | what it runs: the monitoring stack itself |
autoupdate |
all ten | whether it is auto-patched |
Do not fold an overlay into the platform tree. lxc_debian holds both
tailscale-router (reached directly over the tailnet) and monitor-lxc
(reached through the jump host), so "is a container" and "needs a jump host"
are genuinely independent facts. lan_guests are Proxmox guests that are
not Tailscale members; they still push telemetry to the same public ingest
endpoint as everything else.
The one rule that decides patching¶
Group membership, never a role conditional
Whether a host is auto-patched is decided solely by its membership of
the autoupdate group in the inventory. The unattended_upgrades role
has no internal host conditional, because a conditional inside a role is
one typo away from auto-upgrading a hypervisor.
The docker_updates role does check for a Docker socket, but that is a
capability gate, not a policy gate: it decides whether the role is even
applicable, not whether the host is allowed to update.
The central node is a special case¶
On monitor-lxc, OS and security patches apply automatically, but the
monitoring stack itself is upgraded deliberately with
scripts/lxc-update.sh central, which re-renders the configs in the same run.
Otherwise a 03:00 unattended run could:
- restart Grafana and Loki, blinding the fleet at exactly the wrong moment
- reload nginx, briefly 502-ing every collector's ingest path
- upgrade a package whose config file this repo owns, without re-rendering it
playbooks/update-metrics.yml runs there too, so anything held back still
shows up in apt_upgrades_pending. The blacklist suppresses installs, not
visibility.
Deliberately out of scope¶
Documented so the omission is a decision, not a gap.
qbittorrent (LXC 103)
Dropped for two independent reasons. Its sshd rejects every key we hold,
so it was never onboarded and has no collector. And its data filesystem is
expected to run at ~100% full, which made the disk alert permanently
noisy. While it was still in monitored it also sat in the host-down
or-chain, so it fired Host Down every minute forever.
To bring it back: install a key, re-add it under lxc_debian,
lan_guests and autoupdate, and give it a mountpoint exclusion in
rules-resources.yaml before you do, or you will re-create the disk
noise.
t7920, the Proxmox host itself
All of its guests are monitored, but the hypervisor is not. Accept the consequence: host-level CPU, RAM, disk and ZFS pressure on the machine everything else runs on is invisible here. If a guest looks starved, there is no fleet metric explaining why. Watch it in the Proxmox UI.
OPNsense (VM 102, FreeBSD)
Monitor via the os-node_exporter plugin if you want it, scraped rather
than pushed. Firmware updates stay manual: an unattended firewall upgrade
takes out remote access to everything else.
Others
- arr-stack (LXC 104): stopped in Proxmox.
- win11 (VM 108): would need
windows_exporter, a separate path. - skullsiants, expl01t: pentest tooling is deliberately version-pinned.
- Macs, iPhone: excluded by request.
Inventory layout¶
ansible/inventory/
hosts.local.yml real site data, GITIGNORED
hosts.example.yml committed placeholders
group_vars/
all/main.yml shared paths and endpoints
all/vault.yml ansible-vault, committed encrypted
pi/main.yml SD-card IO tuning (platform)
pi_debian/main.yml RPi kernel/bootloader blacklist (distro)
central/main.yml loopback ingest, stack blacklist
lan_guests/main.yml ProxyJump defaults
host_vars/
monitor-lxc.yml pins MONITOR_HOSTNAME
tailscale-router.yml PVE-safe overrides, small-rootfs caps
lxc, lxc_debian, vm and vm_debian have no var files yet. They exist to
be extended: the platform/distro seam is where the next override goes, rather
than into a host_vars file that quietly grows a second copy of the same
setting.
Why tailscale-router is host_vars, not a group
Those overrides used to be group_vars/proxmox, a group of exactly one
host described as "hosts running a PVE kernel". That was true but
misleading, and once monitored split into lxc/vm/pi a third thing
called proxmox sitting beside them was actively confusing. Every value
in it is justified by that host's 2.0 GB rootfs and exit-node role, so it
belongs to the host. Promoting any of it to lxc would be a real change
for plex, memory and monitor-lxc.
The repo is public
All real domains, IPs and usernames live only in hosts.local.yml, which
.gitignore excludes via ansible/**/*.local.yml. Note the pre-existing
*.local pattern matches only names ending in .local, which would
not catch secrets.local.yml.
Variable precedence gotcha¶
group_vars/<group>/main.yml beats vars set in the inventory file itself.
A placeholder left in group_vars/lan_guests silently overrode the real
lan_jump_host from hosts.local.yml and cost an afternoon. If a value is
site-specific, put it in hosts.local.yml and keep it out of group_vars.