personal_infra/ansible/infra/nodito/32_zfs_pool_setup_playbook.yml

225 lines
7.7 KiB
YAML
Raw Normal View History

2025-10-29 00:13:15 +01:00
- name: Setup ZFS RAID 1 Pool for Proxmox Storage
hosts: hypervisor
2025-10-29 00:13:15 +01:00
become: true
tasks:
- name: Verify Proxmox VE is running
command: pveversion
register: pve_version_check
changed_when: false
failed_when: pve_version_check.rc != 0
- name: Update package cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install ZFS utilities
package:
name:
- zfsutils-linux
- zfs-initramfs
state: present
- name: Load ZFS kernel module
modprobe:
name: zfs
- name: Ensure ZFS module loads at boot
lineinfile:
path: /etc/modules
line: zfs
state: present
- name: Check if ZFS pool already exists
command: zpool list {{ zfs_pool_name }}
register: zfs_pool_exists
failed_when: false
changed_when: false
- name: Check if disks are in use
shell: |
for disk in {{ zfs_disk_1 }} {{ zfs_disk_2 }}; do
if mount | grep -q "^$disk"; then
echo "ERROR: $disk is mounted"
exit 1
fi
if lsblk -n -o MOUNTPOINT "$disk" | grep -v "^$" | grep -q .; then
echo "ERROR: $disk has mounted partitions"
exit 1
fi
done
register: disk_usage_check
failed_when: disk_usage_check.rc != 0
changed_when: false
- name: Create ZFS RAID 1 pool with optimized settings
command: >
zpool create {{ zfs_pool_name }}
-o ashift=12
-O mountpoint=none
mirror {{ zfs_disk_1 }} {{ zfs_disk_2 }}
when: zfs_pool_exists.rc != 0
register: zfs_pool_create_result
- name: Check if ZFS dataset already exists
command: zfs list {{ zfs_pool_name }}/vm-storage
register: zfs_dataset_exists
failed_when: false
changed_when: false
- name: Create ZFS dataset for Proxmox storage
command: zfs create {{ zfs_pool_name }}/vm-storage
when: zfs_dataset_exists.rc != 0
register: zfs_dataset_create_result
- name: Set ZFS dataset properties for Proxmox
command: zfs set {{ item.property }}={{ item.value }} {{ zfs_pool_name }}/vm-storage
loop:
- { property: "mountpoint", value: "{{ zfs_pool_mountpoint }}" }
- { property: "compression", value: "lz4" }
- { property: "atime", value: "off" }
- { property: "xattr", value: "sa" }
- { property: "acltype", value: "posixacl" }
- { property: "dnodesize", value: "auto" }
when: zfs_dataset_exists.rc != 0
- name: Set ZFS pool properties for Proxmox
command: zpool set autotrim=off {{ zfs_pool_name }}
when: zfs_pool_exists.rc != 0
- name: Set ZFS pool mountpoint for Proxmox
command: zfs set mountpoint={{ zfs_pool_mountpoint }} {{ zfs_pool_name }}
when: zfs_pool_exists.rc == 0
- name: Export and re-import ZFS pool for Proxmox compatibility
shell: |
zpool export {{ zfs_pool_name }}
zpool import {{ zfs_pool_name }}
when: zfs_pool_exists.rc != 0
register: zfs_pool_import_result
- name: Ensure ZFS services are enabled
systemd:
name: "{{ item }}"
enabled: yes
state: started
loop:
- zfs-import-cache
- zfs-import-scan
- zfs-mount
- zfs-share
- zfs-zed
- name: Check if ZFS pool storage already exists in Proxmox config
stat:
path: /etc/pve/storage.cfg
register: storage_cfg_file
- name: Check if storage name exists in Proxmox config
shell: "grep -q '^zfspool: {{ zfs_pool_name }}' /etc/pve/storage.cfg"
register: storage_exists_check
failed_when: false
changed_when: false
when: storage_cfg_file.stat.exists
- name: Set storage not configured when config file doesn't exist
set_fact:
storage_exists_check:
rc: 1
when: not storage_cfg_file.stat.exists
- name: Debug storage configuration status
debug:
msg: |
Config file exists: {{ storage_cfg_file.stat.exists }}
Storage check result: {{ storage_exists_check.rc }}
Pool exists: {{ zfs_pool_exists.rc == 0 }}
Will add storage: {{ zfs_pool_exists.rc == 0 and storage_exists_check.rc != 0 }}
nodito: de-Uptime-Kuma the ZFS and NUT playbooks, extract templates These two are host-specific by design - nodito is a pet, not cattle - so they stay playbooks rather than becoming roles. But they had rotted. De-Kuma, following the pattern of the six service roles: Both plays opened with an assert on uptime_kuma_username/password, which were removed from the vault, so both failed before doing anything. Dropped that, the two embedded Python monitor-creation scripts, and their /tmp cleanup. Kept every check, threshold and systemd timer - those are the durable part. Reporting is now generic: `healthcheck_push_url` goes into the unit as Environment=HEALTHCHECK_PUSH_URL and the script reads ${HEALTHCHECK_PUSH_URL:-}, treating empty as normal rather than an error. The exit code is the real answer; systemd keeps it. Both scripts now also report status=down on failure instead of only going silent. Live push URLs harvested into the vault so nothing observable changes for ZFS. Three live bugs found while check-diffing: 1. 32_zfs would have DE-REGISTERED the Proxmox storage. `pvesm remove` was gated on the storage existing and `pvesm add` on it NOT existing - mutually exclusive - so a real run removed the proxmox-tank-1 entry backing every VM and never put it back. It would also have dropped `mountpoint /var/lib/vz`, which the live entry has and `pvesm add` does not set. Registration is now add-only. 2. zfs_disk_1 named ata-...WX11TN0Z, a disk no longer in the machine. The live mirror is WX120LHQ + WX11TN2P; a leg was replaced and the repo never caught up. Inert behind `when: zfs_pool_exists.rc != 0`, but wrong on any disaster-recovery run. This is the seventh instance of an identifier written down once whose hardware later moved. 3. 34_nut has NEVER been applied to nodito - no /etc/nut file carries the "Managed by Ansible" marker; they were written by hand in January 2026. The vault held the literal CHANGE_ME_TO_SECURE_PASSWORD, so applying it would have overwritten a working upsd/upsmon auth pair with a placeholder and restarted NUT, leaving the hypervisor's UPS unable to trigger a clean shutdown on mains loss. The Kuma assert was the only thing stopping that, so removing it without a replacement would have armed the gun: there is now an explicit assert that refuses to run on the placeholder. The real password is in the vault and `Configure upsd users` check-diffs clean. Templates reconciled with the live files first, so applying 34_nut is close to a no-op: added `maxretry = 3` to ups.conf and OFFDURATION / RBWARNTIME / NOCOMMWARNTIME / FINALDELAY plus quoted POWERDOWNFLAG to upsmon.conf. The only substantive additions left are the NOTIFYMSG/NOTIFYFLAG syslog lines. /usr/local/bin/ups-heartbeat.sh on the box is an orphan - mode 0644, not executable, referenced by no unit and no cron entry - but its push token belongs to a monitor that still exists and answers, so that monitor has had no heartbeat since January. Harvested as healthcheck_push_urls.ups; applying this play is what will finally feed it. Finish the host_vars migration: infra/nodito/nodito_vars.yml was byte-identical to host_vars/nodito/main.yml. Deleted it, moved nodito_secrets.yml to host_vars/nodito/vault.yml, and stripped the dead vars_files entries from all three playbooks. Extract the 13 inline `content: |` blocks to infra/nodito/templates/, pulled via a YAML load rather than retyped. 681+582 lines become 313+344 plus templates. Ownership parity against HEAD checked mechanically: no owner/group/mode drift on any surviving task. Neither playbook has been applied. ZFS play 2 check-runs failed=0 with two changes: the script rewrite (the deployed one is a hand-edited DEBUG VERSION with `set -x`) and the Environment line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 19:07:50 +02:00
# Registration is add-only on purpose. There used to be a "Remove existing
# storage if it exists" task here that ran `pvesm remove` whenever the
# storage WAS present, paired with an add that only ran when it was ABSENT.
# The two conditions are mutually exclusive, so a real run against a
# correctly-configured hypervisor removed the storage entry backing every VM
# and never put it back. It would also have dropped `mountpoint /var/lib/vz`,
# which the live entry has and which `pvesm add` below does not set.
#
# If the storage entry ever needs its options changed, edit
# /etc/pve/storage.cfg or use `pvesm set` - do not re-register it from here.
2025-10-29 00:13:15 +01:00
- name: Add ZFS pool storage to Proxmox using pvesm
command: >
pvesm add zfspool {{ zfs_pool_name }}
--pool {{ zfs_pool_name }}
--content rootdir,images
--sparse 1
when:
- zfs_pool_exists.rc == 0
- storage_exists_check.rc != 0
register: pvesm_add_result
- name: Verify ZFS pool is healthy
command: zpool status {{ zfs_pool_name }}
register: final_zfs_status
changed_when: false
- name: Fail if ZFS pool is not healthy
fail:
msg: "ZFS pool {{ zfs_pool_name }} is not in a healthy state"
when: "'ONLINE' not in final_zfs_status.stdout"
2026-01-04 23:19:19 +01:00
nodito: de-Uptime-Kuma the ZFS and NUT playbooks, extract templates These two are host-specific by design - nodito is a pet, not cattle - so they stay playbooks rather than becoming roles. But they had rotted. De-Kuma, following the pattern of the six service roles: Both plays opened with an assert on uptime_kuma_username/password, which were removed from the vault, so both failed before doing anything. Dropped that, the two embedded Python monitor-creation scripts, and their /tmp cleanup. Kept every check, threshold and systemd timer - those are the durable part. Reporting is now generic: `healthcheck_push_url` goes into the unit as Environment=HEALTHCHECK_PUSH_URL and the script reads ${HEALTHCHECK_PUSH_URL:-}, treating empty as normal rather than an error. The exit code is the real answer; systemd keeps it. Both scripts now also report status=down on failure instead of only going silent. Live push URLs harvested into the vault so nothing observable changes for ZFS. Three live bugs found while check-diffing: 1. 32_zfs would have DE-REGISTERED the Proxmox storage. `pvesm remove` was gated on the storage existing and `pvesm add` on it NOT existing - mutually exclusive - so a real run removed the proxmox-tank-1 entry backing every VM and never put it back. It would also have dropped `mountpoint /var/lib/vz`, which the live entry has and `pvesm add` does not set. Registration is now add-only. 2. zfs_disk_1 named ata-...WX11TN0Z, a disk no longer in the machine. The live mirror is WX120LHQ + WX11TN2P; a leg was replaced and the repo never caught up. Inert behind `when: zfs_pool_exists.rc != 0`, but wrong on any disaster-recovery run. This is the seventh instance of an identifier written down once whose hardware later moved. 3. 34_nut has NEVER been applied to nodito - no /etc/nut file carries the "Managed by Ansible" marker; they were written by hand in January 2026. The vault held the literal CHANGE_ME_TO_SECURE_PASSWORD, so applying it would have overwritten a working upsd/upsmon auth pair with a placeholder and restarted NUT, leaving the hypervisor's UPS unable to trigger a clean shutdown on mains loss. The Kuma assert was the only thing stopping that, so removing it without a replacement would have armed the gun: there is now an explicit assert that refuses to run on the placeholder. The real password is in the vault and `Configure upsd users` check-diffs clean. Templates reconciled with the live files first, so applying 34_nut is close to a no-op: added `maxretry = 3` to ups.conf and OFFDURATION / RBWARNTIME / NOCOMMWARNTIME / FINALDELAY plus quoted POWERDOWNFLAG to upsmon.conf. The only substantive additions left are the NOTIFYMSG/NOTIFYFLAG syslog lines. /usr/local/bin/ups-heartbeat.sh on the box is an orphan - mode 0644, not executable, referenced by no unit and no cron entry - but its push token belongs to a monitor that still exists and answers, so that monitor has had no heartbeat since January. Harvested as healthcheck_push_urls.ups; applying this play is what will finally feed it. Finish the host_vars migration: infra/nodito/nodito_vars.yml was byte-identical to host_vars/nodito/main.yml. Deleted it, moved nodito_secrets.yml to host_vars/nodito/vault.yml, and stripped the dead vars_files entries from all three playbooks. Extract the 13 inline `content: |` blocks to infra/nodito/templates/, pulled via a YAML load rather than retyped. 681+582 lines become 313+344 plus templates. Ownership parity against HEAD checked mechanically: no owner/group/mode drift on any surviving task. Neither playbook has been applied. ZFS play 2 check-runs failed=0 with two changes: the script rewrite (the deployed one is a hand-edited DEBUG VERSION with `set -x`) and the Environment line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 19:07:50 +02:00
# ─────────────────────────────────────────────────────────────────────────────
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
# The monthly scrub.
#
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
# The ZFS HEALTH CHECK that used to share this play is gone: it is now the
# zfs-health check in infra/400_host_monitoring.yml, which carries the same five
# conditions - pool state, device states, resilver in progress, read/write/
# checksum errors, and errors from the last scan - but reports to Gatus like
# every other host check instead of owning its own push plumbing.
#
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
# The scrub itself stays here, because it is not monitoring: it is the
# maintenance that gives the health check something true to report. A pool that
# is never scrubbed has no idea whether it is healthy.
nodito: de-Uptime-Kuma the ZFS and NUT playbooks, extract templates These two are host-specific by design - nodito is a pet, not cattle - so they stay playbooks rather than becoming roles. But they had rotted. De-Kuma, following the pattern of the six service roles: Both plays opened with an assert on uptime_kuma_username/password, which were removed from the vault, so both failed before doing anything. Dropped that, the two embedded Python monitor-creation scripts, and their /tmp cleanup. Kept every check, threshold and systemd timer - those are the durable part. Reporting is now generic: `healthcheck_push_url` goes into the unit as Environment=HEALTHCHECK_PUSH_URL and the script reads ${HEALTHCHECK_PUSH_URL:-}, treating empty as normal rather than an error. The exit code is the real answer; systemd keeps it. Both scripts now also report status=down on failure instead of only going silent. Live push URLs harvested into the vault so nothing observable changes for ZFS. Three live bugs found while check-diffing: 1. 32_zfs would have DE-REGISTERED the Proxmox storage. `pvesm remove` was gated on the storage existing and `pvesm add` on it NOT existing - mutually exclusive - so a real run removed the proxmox-tank-1 entry backing every VM and never put it back. It would also have dropped `mountpoint /var/lib/vz`, which the live entry has and `pvesm add` does not set. Registration is now add-only. 2. zfs_disk_1 named ata-...WX11TN0Z, a disk no longer in the machine. The live mirror is WX120LHQ + WX11TN2P; a leg was replaced and the repo never caught up. Inert behind `when: zfs_pool_exists.rc != 0`, but wrong on any disaster-recovery run. This is the seventh instance of an identifier written down once whose hardware later moved. 3. 34_nut has NEVER been applied to nodito - no /etc/nut file carries the "Managed by Ansible" marker; they were written by hand in January 2026. The vault held the literal CHANGE_ME_TO_SECURE_PASSWORD, so applying it would have overwritten a working upsd/upsmon auth pair with a placeholder and restarted NUT, leaving the hypervisor's UPS unable to trigger a clean shutdown on mains loss. The Kuma assert was the only thing stopping that, so removing it without a replacement would have armed the gun: there is now an explicit assert that refuses to run on the placeholder. The real password is in the vault and `Configure upsd users` check-diffs clean. Templates reconciled with the live files first, so applying 34_nut is close to a no-op: added `maxretry = 3` to ups.conf and OFFDURATION / RBWARNTIME / NOCOMMWARNTIME / FINALDELAY plus quoted POWERDOWNFLAG to upsmon.conf. The only substantive additions left are the NOTIFYMSG/NOTIFYFLAG syslog lines. /usr/local/bin/ups-heartbeat.sh on the box is an orphan - mode 0644, not executable, referenced by no unit and no cron entry - but its push token belongs to a monitor that still exists and answers, so that monitor has had no heartbeat since January. Harvested as healthcheck_push_urls.ups; applying this play is what will finally feed it. Finish the host_vars migration: infra/nodito/nodito_vars.yml was byte-identical to host_vars/nodito/main.yml. Deleted it, moved nodito_secrets.yml to host_vars/nodito/vault.yml, and stripped the dead vars_files entries from all three playbooks. Extract the 13 inline `content: |` blocks to infra/nodito/templates/, pulled via a YAML load rather than retyped. 681+582 lines become 313+344 plus templates. Ownership parity against HEAD checked mechanically: no owner/group/mode drift on any surviving task. Neither playbook has been applied. ZFS play 2 check-runs failed=0 with two changes: the script rewrite (the deployed one is a hand-edited DEBUG VERSION with `set -x`) and the Environment line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 19:07:50 +02:00
# ─────────────────────────────────────────────────────────────────────────────
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
- name: Schedule the monthly ZFS scrub
hosts: hypervisor
2026-01-04 23:19:19 +01:00
become: true
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
vars_files:
- ../../infra_vars.yml
2026-01-04 23:19:19 +01:00
vars:
zfs_systemd_scrub_service_name: zfs-monthly-scrub
tasks:
- name: Create systemd service for ZFS monthly scrub
nodito: de-Uptime-Kuma the ZFS and NUT playbooks, extract templates These two are host-specific by design - nodito is a pet, not cattle - so they stay playbooks rather than becoming roles. But they had rotted. De-Kuma, following the pattern of the six service roles: Both plays opened with an assert on uptime_kuma_username/password, which were removed from the vault, so both failed before doing anything. Dropped that, the two embedded Python monitor-creation scripts, and their /tmp cleanup. Kept every check, threshold and systemd timer - those are the durable part. Reporting is now generic: `healthcheck_push_url` goes into the unit as Environment=HEALTHCHECK_PUSH_URL and the script reads ${HEALTHCHECK_PUSH_URL:-}, treating empty as normal rather than an error. The exit code is the real answer; systemd keeps it. Both scripts now also report status=down on failure instead of only going silent. Live push URLs harvested into the vault so nothing observable changes for ZFS. Three live bugs found while check-diffing: 1. 32_zfs would have DE-REGISTERED the Proxmox storage. `pvesm remove` was gated on the storage existing and `pvesm add` on it NOT existing - mutually exclusive - so a real run removed the proxmox-tank-1 entry backing every VM and never put it back. It would also have dropped `mountpoint /var/lib/vz`, which the live entry has and `pvesm add` does not set. Registration is now add-only. 2. zfs_disk_1 named ata-...WX11TN0Z, a disk no longer in the machine. The live mirror is WX120LHQ + WX11TN2P; a leg was replaced and the repo never caught up. Inert behind `when: zfs_pool_exists.rc != 0`, but wrong on any disaster-recovery run. This is the seventh instance of an identifier written down once whose hardware later moved. 3. 34_nut has NEVER been applied to nodito - no /etc/nut file carries the "Managed by Ansible" marker; they were written by hand in January 2026. The vault held the literal CHANGE_ME_TO_SECURE_PASSWORD, so applying it would have overwritten a working upsd/upsmon auth pair with a placeholder and restarted NUT, leaving the hypervisor's UPS unable to trigger a clean shutdown on mains loss. The Kuma assert was the only thing stopping that, so removing it without a replacement would have armed the gun: there is now an explicit assert that refuses to run on the placeholder. The real password is in the vault and `Configure upsd users` check-diffs clean. Templates reconciled with the live files first, so applying 34_nut is close to a no-op: added `maxretry = 3` to ups.conf and OFFDURATION / RBWARNTIME / NOCOMMWARNTIME / FINALDELAY plus quoted POWERDOWNFLAG to upsmon.conf. The only substantive additions left are the NOTIFYMSG/NOTIFYFLAG syslog lines. /usr/local/bin/ups-heartbeat.sh on the box is an orphan - mode 0644, not executable, referenced by no unit and no cron entry - but its push token belongs to a monitor that still exists and answers, so that monitor has had no heartbeat since January. Harvested as healthcheck_push_urls.ups; applying this play is what will finally feed it. Finish the host_vars migration: infra/nodito/nodito_vars.yml was byte-identical to host_vars/nodito/main.yml. Deleted it, moved nodito_secrets.yml to host_vars/nodito/vault.yml, and stripped the dead vars_files entries from all three playbooks. Extract the 13 inline `content: |` blocks to infra/nodito/templates/, pulled via a YAML load rather than retyped. 681+582 lines become 313+344 plus templates. Ownership parity against HEAD checked mechanically: no owner/group/mode drift on any surviving task. Neither playbook has been applied. ZFS play 2 check-runs failed=0 with two changes: the script rewrite (the deployed one is a hand-edited DEBUG VERSION with `set -x`) and the Environment line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 19:07:50 +02:00
template:
src: templates/zfs-monthly-scrub.service.j2
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
dest: "/etc/systemd/system/{{ zfs_systemd_scrub_service_name }}.service"
2026-01-04 23:19:19 +01:00
owner: root
group: root
mode: '0644'
- name: Create systemd timer for monthly ZFS scrub
nodito: de-Uptime-Kuma the ZFS and NUT playbooks, extract templates These two are host-specific by design - nodito is a pet, not cattle - so they stay playbooks rather than becoming roles. But they had rotted. De-Kuma, following the pattern of the six service roles: Both plays opened with an assert on uptime_kuma_username/password, which were removed from the vault, so both failed before doing anything. Dropped that, the two embedded Python monitor-creation scripts, and their /tmp cleanup. Kept every check, threshold and systemd timer - those are the durable part. Reporting is now generic: `healthcheck_push_url` goes into the unit as Environment=HEALTHCHECK_PUSH_URL and the script reads ${HEALTHCHECK_PUSH_URL:-}, treating empty as normal rather than an error. The exit code is the real answer; systemd keeps it. Both scripts now also report status=down on failure instead of only going silent. Live push URLs harvested into the vault so nothing observable changes for ZFS. Three live bugs found while check-diffing: 1. 32_zfs would have DE-REGISTERED the Proxmox storage. `pvesm remove` was gated on the storage existing and `pvesm add` on it NOT existing - mutually exclusive - so a real run removed the proxmox-tank-1 entry backing every VM and never put it back. It would also have dropped `mountpoint /var/lib/vz`, which the live entry has and `pvesm add` does not set. Registration is now add-only. 2. zfs_disk_1 named ata-...WX11TN0Z, a disk no longer in the machine. The live mirror is WX120LHQ + WX11TN2P; a leg was replaced and the repo never caught up. Inert behind `when: zfs_pool_exists.rc != 0`, but wrong on any disaster-recovery run. This is the seventh instance of an identifier written down once whose hardware later moved. 3. 34_nut has NEVER been applied to nodito - no /etc/nut file carries the "Managed by Ansible" marker; they were written by hand in January 2026. The vault held the literal CHANGE_ME_TO_SECURE_PASSWORD, so applying it would have overwritten a working upsd/upsmon auth pair with a placeholder and restarted NUT, leaving the hypervisor's UPS unable to trigger a clean shutdown on mains loss. The Kuma assert was the only thing stopping that, so removing it without a replacement would have armed the gun: there is now an explicit assert that refuses to run on the placeholder. The real password is in the vault and `Configure upsd users` check-diffs clean. Templates reconciled with the live files first, so applying 34_nut is close to a no-op: added `maxretry = 3` to ups.conf and OFFDURATION / RBWARNTIME / NOCOMMWARNTIME / FINALDELAY plus quoted POWERDOWNFLAG to upsmon.conf. The only substantive additions left are the NOTIFYMSG/NOTIFYFLAG syslog lines. /usr/local/bin/ups-heartbeat.sh on the box is an orphan - mode 0644, not executable, referenced by no unit and no cron entry - but its push token belongs to a monitor that still exists and answers, so that monitor has had no heartbeat since January. Harvested as healthcheck_push_urls.ups; applying this play is what will finally feed it. Finish the host_vars migration: infra/nodito/nodito_vars.yml was byte-identical to host_vars/nodito/main.yml. Deleted it, moved nodito_secrets.yml to host_vars/nodito/vault.yml, and stripped the dead vars_files entries from all three playbooks. Extract the 13 inline `content: |` blocks to infra/nodito/templates/, pulled via a YAML load rather than retyped. 681+582 lines become 313+344 plus templates. Ownership parity against HEAD checked mechanically: no owner/group/mode drift on any surviving task. Neither playbook has been applied. ZFS play 2 check-runs failed=0 with two changes: the script rewrite (the deployed one is a hand-edited DEBUG VERSION with `set -x`) and the Environment line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 19:07:50 +02:00
template:
src: templates/zfs-monthly-scrub.timer.j2
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
dest: "/etc/systemd/system/{{ zfs_systemd_scrub_service_name }}.timer"
2026-01-04 23:19:19 +01:00
owner: root
group: root
mode: '0644'
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
- name: Enable and start the monthly scrub timer
2026-01-04 23:19:19 +01:00
systemd:
name: "{{ zfs_systemd_scrub_service_name }}.timer"
enabled: yes
state: started
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
daemon_reload: yes
2026-01-04 23:19:19 +01:00
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
- name: Report the scrub schedule
2026-01-04 23:19:19 +01:00
debug:
monitoring: retire the Uptime-Kuma-era checks, add ZFS pool capacity Five things deprecated, each verified against the DEPLOYED script before being deleted rather than assumed superseded: infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400) infra/420_system_healthcheck.yml -> liveness check (infra/400) infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400) 32_zfs play 2 (monitoring half) -> zfs-health check (infra/400) 34_nut play 2 (entirely) -> ups-status check (infra/400) Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime and pushed, which is exactly a liveness heartbeat. The old disk monitor was WEAKER than its replacement: it checked "/" alone at 80%, where the new one walks every real filesystem at 85%. Deleting the playbooks was not the hard part. The units they installed live on the hosts, enabled, and keep firing regardless of what the repo says - two of them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine machines. A playbook deleted without a cleanup leaves its output running forever with nothing left to explain it. So infra/409_remove_legacy_monitoring stops, disables and removes the units, deletes /opt/{disk-monitoring, system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma reference and reporting it. Kept permanently and idempotent, so a rebuilt or restored host cannot quietly bring them back. A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the play wholesale would have silently stopped scrubbing the pool - and an unscrubbed pool makes the health check meaningless, because it would have nothing true to report. That play is now scrub-only and check-runs ok=5 changed=0. ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read separately with `zpool list` - and it is the failure you get warning of rather than the one you discover. Threshold 80%, because ZFS allocation degrades badly past roughly that and fragmentation is hard to undo. The pool is at 47%. Verified both directions: passes on the real pool, and a simulated 91% exits 1. Also fixed the waste recorded in c2de6db: the healthcheck role installed its dependencies once per CHECK rather than per HOST - 29 apt transactions estate-wide for a curl already present, and the slowest part of every deploy. It now deduplicates within a play run, and the redundant standalone daemon_reload is gone (the systemd task already does one). site.yml updated, which exposed that services/gatus was never in it. It now runs before the three registration playbooks, since registering endpoints against a Gatus that is not yet serving would simply fail. Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 10:18:57 +02:00
msg: >-
Monthly scrub of {{ zfs_pool_name }}:
last day of each month at 04:00.
Health is reported separately by the zfs-health check
(infra/400_host_monitoring.yml).