UPS monitoring, Nextcloud cron, container image bumps

ups role: NUT server on home.debyl.io for the CyberPower PR1500RT2U that backs
both it and truenas.localdomain, with staged shutdown (TrueNAS sheds at t+2min,
host at 10% charge) and best-effort IPMI power-on when mains returns. The 10%
threshold leans on ignorelb + override.battery.charge.low rather than a custom
poller, because CyberPower asserts its own low-battery flag far too early.
Credentials come from vault vars; nothing sensitive is templated in the clear.

Nextcloud background jobs: both instances have backgroundjobs_mode "cron", which
expects an external caller every ~5 minutes, and nothing was calling. The
personal instance had not run a background job since 2026-05-14 and skudak since
2024-11-20. Consequently trash and file versions never expired, stale chunked
uploads accumulated, calendar reminders never fired, and nextcloud.log was never
rotated -- which quietly made the existing log_rotate_size cap inert. Added a
systemd timer per instance, skipping cleanly when the container is down or in
maintenance so deploy windows don't show up as failed units.

Trash retention on the personal instance: the default "auto" only expires when
disk space demands it, so 66 GB of >30-day deletions sat on a host with 1.3 TB
free -- effectively unbounded. "auto, 30" makes the 30-day expiry unconditional
while still purging early under pressure.

Image bumps:
  nextcloud       33.0.0  -> 34.0.2   (both cloud and skudak-cloud)
  greg-time-bot   3.9.25  -> 3.10.0
  fulfillr        20260723.2044 -> 20260728.2155  (prod and dev)

The fulfillr bump records what is already deployed: both containers were rolled
to that image on 2026-07-29 for SCRUM-156 (digital product releases + customer
update campaign). Committing it keeps the repo from claiming an older tag than
the host is actually running, which would otherwise roll fulfillr backwards on
the next clean-checkout deploy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bastian de Byl
2026-07-30 13:00:51 -04:00
parent 16145fb6bd
commit 5776dbe1bf
27 changed files with 768 additions and 5 deletions
+7
View File
@@ -0,0 +1,7 @@
---
- name: install NUT and ipmitool
become: true
ansible.builtin.dnf:
name: "{{ ups_deps }}"
state: present
tags: ups
+12
View File
@@ -0,0 +1,12 @@
---
- name: allow NUT from the truenas slave only
become: true
ansible.posix.firewalld:
rich_rule: >-
rule family="ipv4" source address="{{ ups_slave_ip }}/32"
port port="{{ ups_listen_port }}" protocol="tcp" accept
permanent: true
immediate: true
state: enabled
notify: restart firewalld
tags: [ups, firewall]
+68
View File
@@ -0,0 +1,68 @@
---
# IPMI power control for truenas.localdomain (Dell R415, iDRAC6).
# WOL is not an option there: bce0 advertises no WOL capability and is an
# LACP lagg member, so IPMI is the only remote power-on path.
- name: deploy iDRAC credential
become: true
ansible.builtin.copy:
content: "{{ idrac_password }}\n"
dest: /etc/ups/idrac.pw
owner: root
group: nut
mode: '0640'
no_log: true
tags: ups
- name: deploy truenas power helper
become: true
ansible.builtin.template:
src: truenas-power.sh.j2
dest: /usr/local/bin/truenas-power.sh
owner: root
group: root
mode: '0755'
setype: bin_t
tags: ups
- name: deploy truenas restore script
become: true
ansible.builtin.template:
src: ups-restore.sh.j2
dest: /usr/local/bin/ups-restore.sh
owner: root
group: root
mode: '0755'
setype: bin_t
tags: ups
- name: deploy upssched command dispatcher
become: true
ansible.builtin.template:
src: ups-sched-cmd.sh.j2
dest: /usr/local/bin/ups-sched-cmd.sh
owner: root
group: root
mode: '0755'
setype: bin_t
tags: ups
# Covers the deep-drain case: if the battery ran out, this host was itself
# powered off when mains returned, so nothing was running to restore TrueNAS.
- name: deploy boot-time truenas restore unit
become: true
ansible.builtin.template:
src: ups-restore.service.j2
dest: /etc/systemd/system/ups-restore.service
owner: root
group: root
mode: '0644'
tags: ups
- name: enable boot-time truenas restore unit
become: true
ansible.builtin.systemd:
name: ups-restore.service
enabled: true
daemon_reload: true
tags: ups
+5
View File
@@ -0,0 +1,5 @@
---
- import_tasks: deps.yml
- import_tasks: nut.yml
- import_tasks: ipmi.yml
- import_tasks: firewall.yml
+129
View File
@@ -0,0 +1,129 @@
---
# NUT server. The UPS is on USB here, so this host drives it and serves
# status to truenas.localdomain over the network.
- name: set NUT run mode to netserver
become: true
ansible.builtin.template:
src: nut.conf.j2
dest: /etc/ups/nut.conf
owner: root
group: nut
mode: '0640'
notify:
- reload nut driver units
- restart nut driver
- restart nut server
- restart nut monitor
tags: ups
- name: deploy NUT driver configuration
become: true
ansible.builtin.template:
src: ups.conf.j2
dest: /etc/ups/ups.conf
owner: root
group: nut
mode: '0640'
notify:
- reload nut driver units
- restart nut driver
- restart nut server
tags: ups
- name: deploy upsd listener configuration
become: true
ansible.builtin.template:
src: upsd.conf.j2
dest: /etc/ups/upsd.conf
owner: root
group: nut
mode: '0640'
notify: restart nut server
tags: ups
- name: deploy upsd users
become: true
ansible.builtin.template:
src: upsd.users.j2
dest: /etc/ups/upsd.users
owner: root
group: nut
mode: '0640'
notify:
- restart nut server
- restart nut monitor
tags: ups
- name: deploy upsmon configuration
become: true
ansible.builtin.template:
src: upsmon.conf.j2
dest: /etc/ups/upsmon.conf
owner: root
group: nut
mode: '0640'
notify: restart nut monitor
tags: ups
- name: deploy upssched configuration
become: true
ansible.builtin.template:
src: upssched.conf.j2
dest: /etc/ups/upssched.conf
owner: root
group: nut
mode: '0640'
notify: restart nut monitor
tags: ups
# The nut package ships /usr/lib/udev/rules.d/62-nut-usbups.rules, which
# hands the UPS USB device to the nut user. Reload and retrigger so the
# driver can claim it without physically replugging the UPS.
- name: reload udev rules for NUT USB access
become: true
ansible.builtin.command:
cmd: udevadm control --reload-rules
changed_when: false
tags: ups
- name: retrigger UPS USB device
become: true
ansible.builtin.command:
cmd: >-
udevadm trigger --subsystem-match=usb
--attr-match=idVendor={{ ups_vendorid }}
changed_when: false
tags: ups
# nut-driver-enumerator reads ups.conf and generates nut-driver@{{ ups_name }}.
# The .service is oneshot (so never "started" for long) and is triggered by
# the .path unit watching ups.conf - enable both, but only start the .path.
- name: enable NUT driver enumerator
become: true
ansible.builtin.systemd:
name: nut-driver-enumerator.service
enabled: true
daemon_reload: true
tags: ups
- name: enable and start NUT driver enumerator path trigger
become: true
ansible.builtin.systemd:
name: nut-driver-enumerator.path
enabled: true
state: started
tags: ups
- name: enable and start NUT services
become: true
ansible.builtin.systemd:
name: "{{ item }}"
enabled: true
state: started
loop:
- "nut-driver@{{ ups_name }}.service"
- nut-server.service
- nut-monitor.service
- nut.target
tags: ups