Establish clean homelab infrastructure baseline
lint / yaml (push) Has been cancelled
lint / ansible (push) Has been cancelled
lint / terraform (push) Has been cancelled

Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
This commit is contained in:
2026-09-09 16:47:20 +00:00
commit 88a02ababa
418 changed files with 50579 additions and 0 deletions
@@ -0,0 +1,28 @@
---
# HA-managed guests. DELIBERATELY SHORT: the cluster's stated posture is "no HA,
# guests are disposable" (see ../../../CLAUDE.md). Anything listed here is an
# exception that has earned it, and the reason belongs in the comment.
pve_ha_resources:
# vyos-rtr. It is the gateway for BOTH SDN VNets and an OSPF speaker, so losing
# it takes labnet+retronet offline (the main LAN is unaffected — the NEC IX is
# its gateway). Disk is on pve-rg (DRBD, place-count 2), so it can start on any
# node; pve3 would attach diskless.
# NOTE this buys crash-RESTART (~1-3 min outage), not seamless failover. Real
# gateway HA would be a second VyOS with VRRP.
- sid: "vm:100"
state: started
max_restart: 3
max_relocate: 2
# --- Watchdog ---------------------------------------------------------------
# ⚠ WHY THIS MATTERS: adding ANY HA resource arms fencing cluster-wide. PVE's
# default is `softdog`, a SOFTWARE watchdog — a kernel timer, which CANNOT fire if
# the kernel itself is frozen. That is precisely the failure this cluster has
# actually seen (pve2's Raven Ridge idle freeze). A hardware watchdog is
# independent silicon and fires regardless.
#
# Verified available 2026-07-26:
# pve1 (Intel i3-6100U) -> iTCO_wdt (timeout 30s)
# pve2/pve3 (Ryzen 2400GE) -> sp5100_tco (timeout 60s)
# Set per-host in inventory host_vars; empty string = leave PVE's softdog default.
pve_ha_watchdog_module: ""
@@ -0,0 +1,10 @@
---
# Deliberately does NOT reboot. Swapping the watchdog under an armed cluster is a
# manual, ordered operation (see ../../../README-ha.md); a surprise rolling reboot
# of all three nodes is exactly what you do not want an idempotent play to do.
- name: reboot required for watchdog
ansible.builtin.debug:
msg: >-
Watchdog config changed on {{ inventory_hostname }}. It takes effect on the
NEXT REBOOT of this node. Until then fencing still uses softdog, which cannot
fence a frozen kernel. See proxmox/README-ha.md for the live-swap procedure.
@@ -0,0 +1,76 @@
---
# Proxmox HA. Read roles/pve_ha/defaults/main.yml first — it explains why the
# resource list is short and why the watchdog choice is not cosmetic.
# --- Watchdog ---------------------------------------------------------------
# Done BEFORE registering resources: adding a resource arms fencing, and arming
# fencing on a software watchdog is the weakest configuration.
- name: Select the hardware watchdog module
ansible.builtin.lineinfile:
path: /etc/default/pve-ha-manager
regexp: '^#?\s*WATCHDOG_MODULE='
line: "WATCHDOG_MODULE={{ pve_ha_watchdog_module }}"
create: false
when: pve_ha_watchdog_module | length > 0
notify: reboot required for watchdog
tags: [ha, watchdog]
# watchdog-mux opens /dev/watchdog, which belongs to whichever watchdog registered
# FIRST. PVE loads softdog at boot, so it wins and the hardware module ends up as
# an unused watchdog1. Blacklisting softdog is what actually makes the hardware
# one take effect — setting WATCHDOG_MODULE alone does nothing.
- name: Blacklist softdog so the hardware watchdog claims /dev/watchdog
ansible.builtin.copy:
dest: /etc/modprobe.d/pve-ha-watchdog.conf
mode: "0644"
content: |
# Managed by ansible (roles/pve_ha). See that role for the reasoning.
# softdog cannot fence a frozen kernel; blacklisting it lets
# {{ pve_ha_watchdog_module }} register as watchdog0 and own /dev/watchdog.
blacklist softdog
when: pve_ha_watchdog_module | length > 0
notify: reboot required for watchdog
tags: [ha, watchdog]
# ⚠ NOT DONE LIVE ON PURPOSE. Swapping the watchdog on a running node means
# stopping watchdog-mux and unloading softdog while fencing is armed — get the
# order wrong and the node self-fences (reboots). The safe live procedure is
# documented in ../../README-ha.md; otherwise it simply takes effect on the next
# reboot, which is why the handler only WARNS rather than rebooting anything.
# --- Resources ---------------------------------------------------------------
- name: Read current HA resources
ansible.builtin.command: ha-manager status
register: pve_ha_status
changed_when: false
failed_when: false
run_once: true
tags: [ha]
- name: Register HA resources
ansible.builtin.command: >-
ha-manager add {{ item.sid }}
--state {{ item.state }}
--max_restart {{ item.max_restart }}
--max_relocate {{ item.max_relocate }}
loop: "{{ pve_ha_resources }}"
loop_control:
label: "{{ item.sid }}"
# `ha-manager add` errors if the resource already exists, so gate on the status
# output. This is what keeps a second run at changed=0.
when: "'service ' ~ item.sid not in (pve_ha_status.stdout | default(''))"
run_once: true
tags: [ha]
- name: Verify HA is armed and the resource is known
ansible.builtin.command: ha-manager status
register: pve_ha_verify
changed_when: false
run_once: true
tags: [ha]
- name: Show HA state
ansible.builtin.debug:
msg: "{{ pve_ha_verify.stdout_lines }}"
run_once: true
tags: [ha]