# esh-pve — move from the software watchdog to the PCH hardware watchdog. # # WHY: esh-pve hard-froze at 03:34 on 2026-08-19 (no panic, no OOM, no MCE — # the journal simply stops mid-line) and stayed frozen for ~4.5 hours until it # was power-cycled by hand. Everything on it went with it, including the only # DNS resolver the esh-userland VLAN is handed, so the whole house lost name # resolution. # # Nothing on the box could have recovered it: # - `softdog` was the loaded watchdog. A SOFTWARE watchdog cannot rescue a # hard kernel freeze, because the frozen kernel is the thing that would # have to fire its timer. # - Proxmox's `watchdog-mux` held /dev/watchdog but never armed it: it only # pets the device while an HA client is connected, and this cluster has no # HA resources configured (`ha-manager status` reports quorum only). # # The board's PCH TCO timer is present and NOT blocked by firmware — verified # before writing this: # iTCO_wdt: Found a Intel PCH TCO device (Version=6, TCOBASE=0x0400) # iTCO_wdt: initialized. heartbeat=30 sec (nowayout=0) # (no "unable to reset NO_REBOOT flag" line, which is the BIOS-blocked case). # # APPROACH: systemd owns the hardware watchdog directly. Setting # WATCHDOG_MODULE=iTCO_wdt in /etc/default/pve-ha-manager would point # watchdog-mux at the right device but would still never arm it without HA, so # it does not solve this. systemd's RuntimeWatchdogSec pets unconditionally, # which is what "reboot me if I wedge" actually requires. # # ⚠️ CONSEQUENCE — READ BEFORE ENABLING PROXMOX HA ON THIS CLUSTER. # This masks `watchdog-mux`. If HA is ever configured on esh-pve, watchdog-mux # must own /dev/watchdog again and this must be reverted, or HA fencing will # not work. That is not a near-term concern: esh-pve-cluster is TWO nodes with # no qdevice, so a single node loss already costs quorum and the survivor would # fence itself. HA here would make availability worse, not better. # # Revert: unmask + enable watchdog-mux, delete the three dropped files, # `systemctl daemon-reexec`, reboot. # # Run: scripts/elway esh-pve --playbook playbooks/esh-pve-hardware-watchdog.yaml vars: # 60s: long enough that a busy-but-healthy host is never reset, short enough # that a freeze costs a minute rather than half a working day. systemd pets # at half this interval. PID 1 does not block on filesystem I/O, so the known # NFS-wedge history on this host does not put it at risk of a false trip. runtime_watchdog_sec: 60 steps: - name: Load iTCO_wdt at every boot shell: | printf '# PCH hardware watchdog — see playbooks/esh-pve-hardware-watchdog.yaml\niTCO_wdt\n' \ > /etc/modules-load.d/itco-watchdog.conf when: "! grep -qx 'iTCO_wdt' /etc/modules-load.d/itco-watchdog.conf 2>/dev/null" - name: Stop softdog being auto-loaded so iTCO_wdt claims watchdog0 shell: | printf '# softdog cannot rescue a hard freeze; iTCO_wdt can.\n# See playbooks/esh-pve-hardware-watchdog.yaml\nblacklist softdog\n' \ > /etc/modprobe.d/blacklist-softdog.conf when: "! grep -qx 'blacklist softdog' /etc/modprobe.d/blacklist-softdog.conf 2>/dev/null" - name: Mask watchdog-mux (idle without HA, and it holds the device) shell: systemctl disable --now watchdog-mux.service && systemctl mask watchdog-mux.service when: "[ \"$(systemctl is-enabled watchdog-mux.service 2>/dev/null)\" != masked ]" - name: Hand the watchdog to systemd shell: | mkdir -p /etc/systemd/system.conf.d cat > /etc/systemd/system.conf.d/watchdog.conf <<'EOF' # Hardware watchdog (iTCO_wdt). See playbooks/esh-pve-hardware-watchdog.yaml # for why systemd owns this rather than Proxmox's watchdog-mux. [Manager] RuntimeWatchdogSec={{ runtime_watchdog_sec }} RebootWatchdogSec=10min EOF when: "! grep -q 'RuntimeWatchdogSec={{ runtime_watchdog_sec }}' /etc/systemd/system.conf.d/watchdog.conf 2>/dev/null" # Renumber live so the change takes effect without waiting for a reboot: # softdog currently holds watchdog0, so systemd would otherwise arm the # software watchdog — the exact device that failed us. - name: Make iTCO_wdt the active watchdog0 now shell: | rmmod softdog 2>/dev/null || true rmmod iTCO_wdt 2>/dev/null || true modprobe iTCO_wdt # Gated, not unconditional: once systemd holds /dev/watchdog0 the rmmod # would fail anyway, and re-running this on an already-correct host would # otherwise churn the device for no reason. Only renumber when watchdog0 # is NOT already iTCO_wdt. when: "! grep -qx 'iTCO_wdt' /sys/class/watchdog/watchdog0/identity 2>/dev/null" - name: Re-exec systemd so RuntimeWatchdogSec takes effect # daemon-reload does NOT apply [Manager] settings; a re-exec is required. # Skipped once systemd already reports it owns the hardware watchdog. shell: systemctl daemon-reexec when: "! journalctl -b --no-pager | grep -q 'Using hardware watchdog .iTCO_wdt.'" verify: - name: iTCO_wdt is the kernel's watchdog0 shell: grep -qx 'iTCO_wdt' /sys/class/watchdog/watchdog0/identity changed_when: "false" - name: The watchdog is ARMED, not merely present shell: grep -qx 'active' /sys/class/watchdog/watchdog0/state changed_when: "false" - name: systemd reports it owns a hardware watchdog shell: journalctl -b --no-pager | grep -q 'Using hardware watchdog .iTCO_wdt.' changed_when: "false" - name: softdog is not loaded shell: "! lsmod | grep -qE '^softdog'" changed_when: "false" - name: watchdog-mux is masked shell: "[ \"$(systemctl is-enabled watchdog-mux.service 2>/dev/null)\" = masked ]" changed_when: "false" - name: Config survives a reboot shell: grep -qx 'iTCO_wdt' /etc/modules-load.d/itco-watchdog.conf && grep -q RuntimeWatchdogSec /etc/systemd/system.conf.d/watchdog.conf changed_when: "false"