add ups stuff
This commit is contained in:
parent
0b3a981ae3
commit
8d063bcec4
7 changed files with 933 additions and 0 deletions
304
ups/nut-setup.yml
Normal file
304
ups/nut-setup.yml
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
---
|
||||
# NUT (Network UPS Tools) Setup Playbook
|
||||
# Run from laptop: ansible-playbook -i inventory nut-setup.yml
|
||||
#
|
||||
# Prerequisites:
|
||||
# - UPS physically connected to server via USB
|
||||
# - SSH access to target server
|
||||
#
|
||||
# Variables to customize:
|
||||
# - ups_name: logical name for the UPS in NUT
|
||||
# - ups_password: password for upsmon user
|
||||
# - uptime_kuma_push_url: your Uptime Kuma push monitor URL
|
||||
|
||||
- name: Setup NUT for CyberPower UPS
|
||||
hosts: nodito
|
||||
become: true
|
||||
vars:
|
||||
ups_name: cyberpower
|
||||
ups_desc: "CyberPower CP900EPFCLCD"
|
||||
ups_driver: usbhid-ups
|
||||
ups_port: auto
|
||||
ups_user: counterweight
|
||||
ups_password: "changeme" # TODO: use ansible-vault in production
|
||||
ups_offdelay: 120 # Seconds after shutdown command before UPS cuts outlet power
|
||||
ups_ondelay: 30 # Seconds after mains returns before UPS restores outlet power
|
||||
# Note: Shutdown threshold is controlled by UPS's battery.runtime.low (default 300s = 5 min)
|
||||
uptime_kuma_push_url: "https://uptime.example.com/api/push/xxxxx"
|
||||
|
||||
tasks:
|
||||
# ------------------------------------------------------------------
|
||||
# Installation
|
||||
# ------------------------------------------------------------------
|
||||
- name: Install NUT packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- nut
|
||||
- nut-client
|
||||
- nut-server
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Verify UPS is detected (informational)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Check if UPS is detected via USB
|
||||
ansible.builtin.shell: lsusb | grep -i cyber
|
||||
register: lsusb_output
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Display USB detection result
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lsusb_output.stdout | default('UPS not detected via USB - ensure it is plugged in') }}"
|
||||
|
||||
- name: Reload udev rules (NUT installs rules but they need triggering for already-plugged devices)
|
||||
ansible.builtin.shell: |
|
||||
udevadm control --reload-rules
|
||||
udevadm trigger --subsystem-match=usb --action=add
|
||||
changed_when: true
|
||||
|
||||
- name: Verify USB device has nut group permissions
|
||||
ansible.builtin.shell: |
|
||||
# Find the UPS device and check its permissions
|
||||
BUS_DEV=$(lsusb | grep -i cyber | grep -oP 'Bus \K\d+|Device \K\d+' | tr '\n' '/' | sed 's/\/$//')
|
||||
if [ -n "$BUS_DEV" ]; then
|
||||
BUS=$(echo $BUS_DEV | cut -d'/' -f1)
|
||||
DEV=$(echo $BUS_DEV | cut -d'/' -f2)
|
||||
ls -la /dev/bus/usb/$BUS/$DEV
|
||||
else
|
||||
echo "UPS device not found"
|
||||
exit 1
|
||||
fi
|
||||
register: usb_permissions
|
||||
changed_when: false
|
||||
|
||||
- name: Display USB permissions
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ usb_permissions.stdout }} — should show 'root nut', not 'root root'"
|
||||
|
||||
- name: Scan for UPS with nut-scanner
|
||||
ansible.builtin.command: nut-scanner -U
|
||||
register: nut_scanner_output
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Display nut-scanner result
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ nut_scanner_output.stdout_lines }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Configuration files
|
||||
# ------------------------------------------------------------------
|
||||
- name: Configure NUT mode (standalone)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/nut/nut.conf
|
||||
content: |
|
||||
# Managed by Ansible
|
||||
MODE=standalone
|
||||
owner: root
|
||||
group: nut
|
||||
mode: "0640"
|
||||
notify: Restart NUT services
|
||||
|
||||
- name: Configure UPS device
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/nut/ups.conf
|
||||
content: |
|
||||
# Managed by Ansible
|
||||
[{{ ups_name }}]
|
||||
driver = {{ ups_driver }}
|
||||
port = {{ ups_port }}
|
||||
desc = "{{ ups_desc }}"
|
||||
offdelay = {{ ups_offdelay }}
|
||||
ondelay = {{ ups_ondelay }}
|
||||
owner: root
|
||||
group: nut
|
||||
mode: "0640"
|
||||
notify: Restart NUT services
|
||||
|
||||
- name: Configure upsd to listen on localhost
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/nut/upsd.conf
|
||||
content: |
|
||||
# Managed by Ansible
|
||||
LISTEN 127.0.0.1 3493
|
||||
owner: root
|
||||
group: nut
|
||||
mode: "0640"
|
||||
notify: Restart NUT services
|
||||
|
||||
- name: Configure upsd users
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/nut/upsd.users
|
||||
content: |
|
||||
# Managed by Ansible
|
||||
[{{ ups_user }}]
|
||||
password = {{ ups_password }}
|
||||
upsmon master
|
||||
owner: root
|
||||
group: nut
|
||||
mode: "0640"
|
||||
notify: Restart NUT services
|
||||
|
||||
- name: Configure upsmon
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/nut/upsmon.conf
|
||||
content: |
|
||||
# Managed by Ansible
|
||||
MONITOR {{ ups_name }}@localhost 1 {{ ups_user }} {{ ups_password }} master
|
||||
|
||||
MINSUPPLIES 1
|
||||
SHUTDOWNCMD "/sbin/shutdown -h +0"
|
||||
POLLFREQ 5
|
||||
POLLFREQALERT 5
|
||||
HOSTSYNC 15
|
||||
DEADTIME 15
|
||||
POWERDOWNFLAG /etc/killpower
|
||||
|
||||
# Notifications
|
||||
NOTIFYMSG ONLINE "UPS %s on line power"
|
||||
NOTIFYMSG ONBATT "UPS %s on battery"
|
||||
NOTIFYMSG LOWBATT "UPS %s battery is low"
|
||||
NOTIFYMSG FSD "UPS %s: forced shutdown in progress"
|
||||
NOTIFYMSG COMMOK "Communications with UPS %s established"
|
||||
NOTIFYMSG COMMBAD "Communications with UPS %s lost"
|
||||
NOTIFYMSG SHUTDOWN "Auto logout and shutdown proceeding"
|
||||
NOTIFYMSG REPLBATT "UPS %s battery needs replacing"
|
||||
|
||||
# Log all events to syslog (upsmon handles LB shutdown automatically)
|
||||
NOTIFYFLAG ONLINE SYSLOG
|
||||
NOTIFYFLAG ONBATT SYSLOG
|
||||
NOTIFYFLAG LOWBATT SYSLOG
|
||||
NOTIFYFLAG FSD SYSLOG
|
||||
NOTIFYFLAG COMMOK SYSLOG
|
||||
NOTIFYFLAG COMMBAD SYSLOG
|
||||
NOTIFYFLAG SHUTDOWN SYSLOG
|
||||
NOTIFYFLAG REPLBATT SYSLOG
|
||||
owner: root
|
||||
group: nut
|
||||
mode: "0640"
|
||||
notify: Restart NUT services
|
||||
|
||||
# NOTE: No upssched configuration needed. The UPS sets LB (Low Battery) flag
|
||||
# when runtime < battery.runtime.low (default 300s) or charge < battery.charge.low (default 10%).
|
||||
# upsmon handles LB automatically and triggers shutdown—no custom scripts required.
|
||||
|
||||
# NOTE: /lib/systemd/system-shutdown/nutshutdown is provided by the NUT package.
|
||||
# It already checks for the killpower flag and runs `upsdrvctl shutdown` to tell
|
||||
# the UPS to cut outlet power. No need to create or modify it.
|
||||
|
||||
- name: Verify late-stage shutdown script exists
|
||||
ansible.builtin.stat:
|
||||
path: /lib/systemd/system-shutdown/nutshutdown
|
||||
register: nutshutdown_script
|
||||
|
||||
- name: Warn if nutshutdown script is missing
|
||||
ansible.builtin.debug:
|
||||
msg: "WARNING: /lib/systemd/system-shutdown/nutshutdown not found. UPS may not cut power after shutdown, breaking auto-restart."
|
||||
when: not nutshutdown_script.stat.exists
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Services
|
||||
# Note: nut-driver-enumerator reads ups.conf and starts drivers via nut-driver@<name>.service
|
||||
# ------------------------------------------------------------------
|
||||
- name: Enable and start NUT driver enumerator
|
||||
ansible.builtin.systemd:
|
||||
name: nut-driver-enumerator
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Enable and start NUT server
|
||||
ansible.builtin.systemd:
|
||||
name: nut-server
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Enable and start NUT monitor
|
||||
ansible.builtin.systemd:
|
||||
name: nut-monitor
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Uptime Kuma heartbeat monitoring
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create UPS heartbeat script
|
||||
ansible.builtin.copy:
|
||||
dest: /usr/local/bin/ups-heartbeat.sh
|
||||
content: |
|
||||
#!/bin/bash
|
||||
# UPS heartbeat for Uptime Kuma - Managed by Ansible
|
||||
STATUS=$(upsc {{ ups_name }}@localhost ups.status 2>/dev/null)
|
||||
|
||||
if [[ -z "$STATUS" ]]; then
|
||||
# Cannot communicate with UPS
|
||||
curl -fsS "{{ uptime_kuma_push_url }}?status=down&msg=UPS%20communication%20lost" > /dev/null 2>&1
|
||||
elif [[ "$STATUS" == *"OL"* ]]; then
|
||||
# On line power
|
||||
curl -fsS "{{ uptime_kuma_push_url }}?status=up&msg=UPS%20on%20mains" > /dev/null 2>&1
|
||||
else
|
||||
# On battery or other state
|
||||
curl -fsS "{{ uptime_kuma_push_url }}?status=down&msg=UPS%20on%20battery%20($STATUS)" > /dev/null 2>&1
|
||||
fi
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Setup cron job for UPS heartbeat
|
||||
ansible.builtin.cron:
|
||||
name: "UPS heartbeat to Uptime Kuma"
|
||||
minute: "*"
|
||||
job: "/usr/local/bin/ups-heartbeat.sh"
|
||||
user: root
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Verification
|
||||
# ------------------------------------------------------------------
|
||||
- name: Verify NUT can communicate with UPS
|
||||
ansible.builtin.command: upsc {{ ups_name }}@localhost
|
||||
register: upsc_output
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Display UPS status
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ upsc_output.stdout_lines }}"
|
||||
|
||||
- name: Get UPS status summary
|
||||
ansible.builtin.shell: |
|
||||
echo "Status: $(upsc {{ ups_name }}@localhost ups.status 2>/dev/null)"
|
||||
echo "Battery: $(upsc {{ ups_name }}@localhost battery.charge 2>/dev/null)%"
|
||||
echo "Runtime: $(upsc {{ ups_name }}@localhost battery.runtime 2>/dev/null)s"
|
||||
echo "Load: $(upsc {{ ups_name }}@localhost ups.load 2>/dev/null)%"
|
||||
register: ups_summary
|
||||
changed_when: false
|
||||
|
||||
- name: Display UPS summary
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ ups_summary.stdout_lines }}"
|
||||
|
||||
- name: Verify low battery thresholds
|
||||
ansible.builtin.shell: |
|
||||
echo "Runtime threshold: $(upsc {{ ups_name }}@localhost battery.runtime.low 2>/dev/null)s"
|
||||
echo "Charge threshold: $(upsc {{ ups_name }}@localhost battery.charge.low 2>/dev/null)%"
|
||||
echo "LB triggers when runtime < threshold OR charge < threshold"
|
||||
register: thresholds
|
||||
changed_when: false
|
||||
|
||||
- name: Display low battery thresholds
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ thresholds.stdout_lines }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Handlers
|
||||
# ------------------------------------------------------------------
|
||||
handlers:
|
||||
- name: Restart NUT services
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
state: restarted
|
||||
loop:
|
||||
- nut-driver-enumerator
|
||||
- nut-server
|
||||
- nut-monitor
|
||||
Loading…
Add table
Add a link
Reference in a new issue