20 KiB
Snarfnet Homelab Power Resilience & UPS Automation Plan
This document details the comprehensive power resilience strategy, tiered graceful shutdown sequence, and automated recovery orchestration for the snarfnet homelab. The system is centered around a dedicated NUT (Network UPS Tools) Master Server at 14.4.4.44.
1. Core Architecture & Component Roles
The power resilience architecture uses a centralized NUT Master Server host that polls connected UPS hardware and broadcasts status alerts (ONBATT, LOWBATT, ONLINE, FSD) across all homelab VLANs.
| Role | Hostname / Identifier | Target IP | Subnet / VLAN | Description & Failure Protocol |
|---|---|---|---|---|
| NUT Master Server | nutserver.snarfnet.net |
14.4.4.44 |
VLAN 50 (Smart Home) | Primary NUT upsd daemon polling physical UPS via USB/Serial/SNMP. Runs upsmon master. |
| Edge Ingress / Reverse Proxy | nginx.snarfnet.net |
140.44.4.74 |
VLAN 20 (Swarm 04) | NPM running in PublishMode: host on ports 80/443/81. Dependent on Synology NFS; must be stopped prior to NAS Safe Mode. |
| Authentication & SSO | authentik.snarfnet.net |
140.44.4.29:444 |
VLAN 20 (Aerodrome K3s) | Handles Forward Auth for Peanut, Gotify, Homebox, WLED. Runs on Longhorn storage in Aerodrome. |
| UPS Web Dashboard | peanut.snarfnet.net |
140.44.4.70:8050 |
VLAN 20 (Swarm VIP) | Peanut container UI consuming 14.4.4.44:3493. Protected by Authentik Forward Auth externally. |
| Emergency Push Notifications | gotify.snarfnet.net |
140.44.4.70:8090 |
VLAN 20 (Swarm VIP) | Gotify server. Internal outage alerts MUST use direct IP (http://140.44.4.70:8090) to bypass Authentik during shutdown. |
| Smart Home Automation | homeassistant.snarfnet.net |
14.4.4.20 |
VLAN 50 (Smart Home) | Home Assistant integration monitoring 14.4.4.44:3493 for state changes, critical push alerts, and automated shedding. |
| Centralized Monitoring | zabbix.snarfnet.net |
140.44.4.61 |
VLAN 20 (Spaceport K3s) | Zabbix Agent 2 / NUT exporter polling 14.4.4.44:3493 for battery voltage, load %, and runtime metrics. |
| Primary Storage 1 | sn-nas.snarfnet.net |
14.10.10.71 |
VLAN 40 (Storage) | Synology DSM UPS client polling 14.4.4.44. Hosts NPM NFS shares (/volume1/docker/nginx/data). |
| Primary Storage 2 | snarfnas.snarfnet.net |
14.10.10.72 |
VLAN 40 (Storage) | Synology DSM UPS client polling 14.4.4.44. Hosts secondary backup shares. |
| Distributed Block Storage | longhorn.aerodrome.snarfnet.net |
140.44.4.36:80 |
VLAN 20 (Aerodrome K3s) | Longhorn CSI storage engine powering Authentik, AWX, Grafana PVCs. Protected with recurring snapshots (snapshot-4h, snapshot-daily). |
Important
Emergency Alerting Path Warning:
peanut.snarfnet.netandgotify.snarfnet.netare protected externally behind Authentik Forward Auth (oauth2-proxy). During a power outage, when K3saerodromeshuts down in Tier 3, Authentik goes offline. Therefore, emergency outage alerts and NUT notification scripts must communicate directly with Gotify via internal IP (http://140.44.4.70:8090/message?token=...) or native Home Assistant push notifications, bypassing NPM and Authentik.
2. Power Resilience Topology Map
flowchart TD
subgraph Grid ["Utility Power Grid"]
Mains["Main AC Utility Power"]
end
subgraph PowerHardware ["Power Hardware & Protection"]
UPS["Central UPS Hardware (Battery Bank)"]
end
subgraph NUTMasterHost ["NUT Master Node (14.4.4.44)"]
UPSD["upsd Daemon (Port 3493)"]
UPSMON_M["upsmon (Master Daemon)"]
NOTIFY["nut-notify.sh (Direct Alert Engine)"]
end
subgraph OutageAlerts ["Emergency Notification Channels"]
HA_Alert["Home Assistant (14.4.4.20)\nLocal Push Notification"]
Gotify_Direct["Gotify Internal Direct\nhttp://140.44.4.70:8090\n(Bypasses Authentik / NPM)"]
Syslog["Wall / Syslog Broadcast"]
end
subgraph MonitoringPanels ["Monitoring & Management"]
Peanut["Peanut Dashboard\n(peanut.snarfnet.net)"]
Zabbix["Zabbix Server\n(140.44.4.61)"]
end
subgraph InfrastructureConsumers ["Homelab Tiered Consumers"]
SwarmNFS["Docker Swarm (140.44.4.71-75)\nNPM Host Mode (140.44.4.74)"]
K8sCompute["Aerodrome & Spaceport K3s\nLonghorn Engine & Worker Nodes"]
NAS["Synology NAS Arrays\n(14.10.10.71 / 14.10.10.72)"]
K8sControl["K3s Control Planes & etcd"]
Harvester["Spacedock HCI Hypervisors\n(140.44.4.81 - 83)"]
CoreEdge["UDM Pro Gateway (14.44.4.1)\nCore PoE Switches"]
end
Mains --> UPS
UPS -- USB / SNMP --> UPSD
UPSD --> UPSMON_M
UPSMON_M --> NOTIFY
NOTIFY -- Direct API Token --> Gotify_Direct
NOTIFY -- TCP 3493 Event --> HA_Alert
NOTIFY --> Syslog
UPSD -- TCP 3493 --> Peanut
UPSD -- TCP 3493 --> Zabbix
UPSMON_M -. Tiered Signal .-> SwarmNFS
UPSMON_M -. Tiered Signal .-> K8sCompute
UPSMON_M -. Tiered Signal .-> NAS
UPSMON_M -. Tiered Signal .-> K8sControl
UPSMON_M -. Tiered Signal .-> Harvester
UPSMON_M -. Final Cut .-> CoreEdge
3. Tiered Graceful Shutdown Hierarchy
To eliminate data corruption, prevent stale NFS file handles, and ensure Longhorn volume detach cleanly, the homelab executes a strict 6-stage progressive shutdown sequence.
+--------------------------------------------------------------------------------------------------+
| [T+0s] Grid Power Outage -> NUT Master (14.4.4.44) detects ONBATT |
| - Broadcasts emergency alert via Home Assistant push & Gotify direct API (http://140.44.4.70:8090)|
+--------------------------------------------------------------------------------------------------+
|
v
+--------------------------------------------------------------------------------------------------+
| [T+3m / Batt <= 85%] TIER 1: Non-Essential & Transient Workloads |
| - Dev environments: Coder workspaces (coder.snarfnet.net) & Jenkins build executors stopped |
| - Media transcoders (Plex active sessions signaled) |
| - AI inference stack: Turing Pi AI nodes (140.44.4.7 / 140.44.4.8) halted |
+--------------------------------------------------------------------------------------------------+
|
v
+--------------------------------------------------------------------------------------------------+
| [T+6m / Batt <= 70%] TIER 2: NFS-Mounted Docker Swarm Services (NPM / Peanut) |
| - Stop `nginx-proxy-manager_app` & `nginx-proxy-manager_backup` on snshpd4lnxswm04 (140.44.4.74) |
| - Stop `peanut` container on Swarm |
| - Flushes open SQLite databases (database.sqlite) and unmounts NFS mounts to 14.10.10.71 |
+--------------------------------------------------------------------------------------------------+
|
v
+--------------------------------------------------------------------------------------------------+
| [T+8m / Batt <= 55%] TIER 3: Kubernetes Stateful Workloads & Longhorn Volume Detach |
| - Harvester guest VMs (harvester.spacedock.snarfnet.net 140.44.4.90) gracefully shut down via API|
| - Cordon & scale down stateful pods on Aerodrome (Authentik PostgreSQL, AWX Postgres, Grafana) |
| - Longhorn CSI cleanly syncs cached writes and detaches persistent volumes from worker nodes |
| - Aerodrome (140.44.4.21-25) & Spaceport (140.44.4.51-56) worker nodes execute shutdown |
+--------------------------------------------------------------------------------------------------+
|
v
+--------------------------------------------------------------------------------------------------+
| [T+10m / Batt <= 40%] TIER 4: Storage Arrays (Synology DSM Safe Mode) |
| - SN-NAS (14.10.10.71) & SnarfNAS (14.10.10.72) enter DSM Safe Mode via NUT client trigger |
| - Unmounts all Btrfs volume pools, terminates NFS/iSCSI daemon threads, parks drive read heads |
| - Zero active network clients remain connected to storage |
+--------------------------------------------------------------------------------------------------+
|
v
+--------------------------------------------------------------------------------------------------+
| [T+12m / Batt <= 25%] TIER 5: Control Planes & Baremetal Hypervisors |
| - Aerodrome K3s Control Planes (140.44.4.11-13) take etcd snapshot and halt |
| - Spaceport K3s Control Planes (140.44.4.41-44) take etcd snapshot and halt |
| - Swarm Manager nodes (140.44.4.71-75) halt |
| - Harvester baremetal hypervisors (140.44.4.81-83) halt via IPMI/SSH |
+--------------------------------------------------------------------------------------------------+
|
v
+--------------------------------------------------------------------------------------------------+
| [T+15m / Batt Low] TIER 6: Forced Shutdown (FSD) & Edge Power Cut |
| - NUT Master (14.4.4.44) issues final FSD broadcast to all remaining clients |
| - NUT Master issues `upsdrvctl shutdown` with /etc/killpower flag (commands UPS load drop) |
| - Core Edge: UDM Pro Gateway (14.44.4.1) & Core PoE switch survive on battery until power drops |
+--------------------------------------------------------------------------------------------------+
4. Configuration Blueprints & Integration Scripts
A. NUT Master Server (14.4.4.44)
/etc/nut/ups.conf
[snarf-ups]
driver = usbhid-ups
port = auto
desc = "Snarfnet Main Rack UPS"
pollinterval = 5
/etc/nut/upsd.conf
LISTEN 14.4.4.44 3493
LISTEN 127.0.0.1 3493
MAXAGE 15
/etc/nut/upsd.users
[admin]
password = ADMIN_PASSWORD_CHANGE_ME
actions = SET
instcmds = ALL
[upsmon_master]
password = MASTER_MONITOR_PASS
upsmon master
[upsmon_slave]
password = SLAVE_MONITOR_PASS
upsmon slave
[peanut]
password = PEANUT_MONITOR_PASS
upsmon slave
/etc/nut/upsmon.conf
MONITOR snarf-ups@14.4.4.44 1 upsmon_master MASTER_MONITOR_PASS master
MINSUPPLIES 1
SHUTDOWNCMD "/usr/local/bin/homelab_power_shutdown.sh"
NOTIFYCMD /usr/local/bin/nut-notify.sh
POWERDOWNFLAG /etc/killpower
NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC
NOTIFYFLAG FSD SYSLOG+WALL+EXEC
NOTIFYFLAG COMMBAD SYSLOG+WALL+EXEC
NOTIFYFLAG COMMOK SYSLOG+WALL+EXEC
B. Outage Notification Engine (/usr/local/bin/nut-notify.sh)
This script runs on the NUT Master (14.4.4.44) whenever a power state event triggers. It sends push notifications directly to Gotify (http://140.44.4.70:8090) and Home Assistant (http://14.4.4.20:8123) without touching NPM or Authentik.
#!/bin/bash
# /usr/local/bin/nut-notify.sh
# Emergency power notification script for Network UPS Tools
set -euo pipefail
EVENT_TYPE="${1:-$NOTIFYTYPE}"
UPS_NAME="${UPSNAME:-snarf-ups}"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
GOTIFY_INTERNAL_URL="http://140.44.4.70:8090/message"
GOTIFY_APP_TOKEN="A4uN4fEmErGeNcYtOkEn" # Replace with valid Gotify App Token
send_gotify() {
local title="$1"
local message="$2"
local priority="$3"
curl -s -m 5 -X POST "${GOTIFY_INTERNAL_URL}?token=${GOTIFY_APP_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"title\": \"${title}\", \"message\": \"${message}\", \"priority\": ${priority}}" >/dev/null 2>&1 || true
}
case "$EVENT_TYPE" in
ONBATT)
send_gotify "⚡ Grid Power Lost" "[$TIMESTAMP] Homelab is running on UPS battery (${UPS_NAME}). Staged shutdown countdown started." 8
logger -t nut-notify "Grid power failure detected - running on UPS battery."
;;
ONLINE)
send_gotify "✅ Grid Power Restored" "[$TIMESTAMP] Utility AC power restored to ${UPS_NAME}. Battery recharging." 5
logger -t nut-notify "AC utility power restored."
;;
LOWBATT)
send_gotify "🚨 UPS Battery Critical" "[$TIMESTAMP] Battery level critical on ${UPS_NAME}! Final Tier 4-6 shutdown initiated." 10
logger -t nut-notify "CRITICAL: UPS battery low. Enforcing emergency shutdown."
;;
FSD)
send_gotify "🛑 Forced Shutdown Engaged" "[$TIMESTAMP] NUT Master has commanded immediate power cutoff across all clusters." 10
logger -t nut-notify "FSD signal received. Halting systems."
;;
*)
logger -t nut-notify "Unhandled UPS event: ${EVENT_TYPE}"
;;
esac
C. Orchestrated Tiered Shutdown Script (/usr/local/bin/homelab_power_shutdown.sh)
This script is invoked by upsmon on 14.4.4.44 when an outage exceeds the battery threshold:
#!/bin/bash
# /usr/local/bin/homelab_power_shutdown.sh
# Staged graceful homelab shutdown orchestrator
set -uo pipefail
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [POWER-SHUTDOWN] $*" | tee -a /var/log/power-shutdown.log; }
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=3 -o BatchMode=yes"
log "=== INITIATING HOMELAB TIERED SHUTDOWN ==="
# ------------------------------------------------------------------
# TIER 1: Non-Essential Compute & AI Pis
# ------------------------------------------------------------------
log "Stage 1: Halting AI Raspberry Pi stack (140.44.4.7, 140.44.4.8)..."
ssh $SSH_OPTS root@140.44.4.7 "shutdown -h now" 2>/dev/null || true
ssh $SSH_OPTS root@140.44.4.8 "shutdown -h now" 2>/dev/null || true
# ------------------------------------------------------------------
# TIER 2: Docker Swarm NFS Workloads (NPM & Peanut)
# ------------------------------------------------------------------
log "Stage 2: Stopping Swarm services mounting Synology NFS (NPM, Peanut)..."
ssh $SSH_OPTS root@140.44.4.71 "docker service scale nginx-proxy-manager_app=0 nginx-proxy-manager_backup=0 peanut_peanut=0" 2>/dev/null || true
sleep 5
# ------------------------------------------------------------------
# TIER 3: Harvester Guest VMs & Kubernetes Worker Nodes
# ------------------------------------------------------------------
log "Stage 3: Shutting down Harvester guest VMs and K3s workers..."
# Gracefully signal Harvester VMs
ssh $SSH_OPTS root@140.44.4.81 "virsh list --name | xargs -r -n1 virsh shutdown" 2>/dev/null || true
# Stop K3s agent on Aerodrome workers (clean Longhorn PVC detach)
for ip in 140.44.4.22 140.44.4.23 140.44.4.24 140.44.4.25; do
ssh $SSH_OPTS root@$ip "systemctl stop k3s-agent && sync && shutdown -h now" 2>/dev/null || true
done
# Stop K3s agent on Spaceport workers
for ip in 140.44.4.51 140.44.4.52 140.44.4.53 140.44.4.54 140.44.4.55 140.44.4.56; do
ssh $SSH_OPTS root@$ip "systemctl stop k3s-agent && sync && shutdown -h now" 2>/dev/null || true
done
sleep 15
# ------------------------------------------------------------------
# TIER 4: Storage Arrays (Synology DSM Safe Mode)
# ------------------------------------------------------------------
log "Stage 4: Triggering Synology NAS Safe Mode (14.10.10.71, 14.10.10.72)..."
# Synology enters safe mode automatically via NUT upsd client signal (port 3493)
# Explicit API / SSH command to flush pools if configured
ssh $SSH_OPTS admin@14.10.10.71 "synopoweroff" 2>/dev/null || true
ssh $SSH_OPTS admin@14.10.10.72 "synopoweroff" 2>/dev/null || true
sleep 20
# ------------------------------------------------------------------
# TIER 5: Control Planes & Swarm Managers
# ------------------------------------------------------------------
log "Stage 5: Halting K3s control planes and Swarm managers..."
# Spaceport Masters (140.44.4.41-44)
for ip in 140.44.4.41 140.44.4.42 140.44.4.43 140.44.4.44; do
ssh $SSH_OPTS root@$ip "k3s etcd-snapshot save && systemctl stop k3s && shutdown -h now" 2>/dev/null || true
done
# Aerodrome Masters (140.44.4.11-13)
for ip in 140.44.4.11 140.44.4.12 140.44.4.13; do
ssh $SSH_OPTS root@$ip "k3s etcd-snapshot save && systemctl stop k3s && shutdown -h now" 2>/dev/null || true
done
# Swarm Managers (140.44.4.71, 72, 74)
for ip in 140.44.4.71 140.44.4.72 140.44.4.74; do
ssh $SSH_OPTS root@$ip "sync && shutdown -h now" 2>/dev/null || true
done
# Harvester Baremetal Hypervisors (140.44.4.81-83)
for ip in 140.44.4.81 140.44.4.82 140.44.4.83; do
ssh $SSH_OPTS root@$ip "sync && shutdown -h now" 2>/dev/null || true
done
# ------------------------------------------------------------------
# TIER 6: Master Node Self-Cutoff (FSD)
# ------------------------------------------------------------------
log "Stage 6: Executing final local halt and arming killpower..."
touch /etc/killpower
/sbin/shutdown -h +0
D. Synology NAS Integration (14.10.10.71 & 14.10.10.72)
- Log into DSM Console
\rightarrowControl Panel\rightarrowHardware & Power\rightarrowUPS. - Check Enable UPS Support.
- Select UPS Type: Synology UPS Server / Network NUT Server.
- Network UPS server IP address:
14.4.4.44. - Set Time before Synology NAS enters Standby Mode:
Customize time\rightarrow6 minutes(guaranteeing that Tier 2 Swarm NFS workloads have cleanly scaled down before disk pools unmount). - Check Shut down UPS when the system enters Standby Mode (disabled, managed centrally by
14.4.4.44).
E. Peanut Web Dashboard Configuration
Peanut runs on Docker Swarm (140.44.4.70:8050). It connects directly to the NUT master server over internal TCP 3493:
services:
peanut:
image: brandawg93/peanut:latest
ports:
- "8050:8050"
environment:
- TZ=America/New_York
- NUT_HOST=14.4.4.44
- NUT_PORT=3493
- NUT_USER=peanut
- NUT_PASSWORD=PEANUT_MONITOR_PASS
volumes:
- peanut_config:/config
volumes:
peanut_config:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/swarm_shared/legacy_volumes/peanut/
5. Automated Power Restoration & Boot Sequence
When utility grid power returns, the homelab automatically recovers in a strict bottom-up reverse sequence to guarantee all storage and network prerequisites are ready before compute services attempt to start.
flowchart TD
G_On["1. Grid AC Restored\nUPS Battery Reaches >= 30%"] --> P1["2. Phase 1: Core Network & Edge\nUDM Pro (14.44.4.1) & Core PoE Switches\nEstablish VLANs, DHCP, Inter-VLAN Routing"]
P1 --> P2["3. Phase 2: NUT Master\nNode at 14.4.4.44 Boots (BIOS: Always On)\nupsd daemon starts, monitors line quality"]
P2 --> P3["4. Phase 3: Storage Foundation\nSynology SN-NAS (14.10.10.71) & SnarfNAS (14.10.10.72) Boot\nMount Btrfs pools, start NFS & iSCSI daemons"]
P3 --> P4["5. Phase 4: Control Planes & Hypervisors\n- Harvester spacedock (140.44.4.81-83) boots via IPMI/WOL\n- Aerodrome (140.44.4.11-13) & Spaceport (140.44.4.41-44) K3s masters boot\n- etcd clusters re-establish quorum"]
P4 --> P5["6. Phase 5: Storage CSI & Compute Workers\n- Aerodrome & Spaceport worker nodes boot\n- Longhorn CSI engine initializes, verifies volume health, mounts PVCs"]
P5 --> P6["7. Phase 6: Ingress & Workloads\n- Docker Swarm (140.44.4.71-75) boots\n- NPM host-mode container binds 80/443/81 on 140.44.4.74\n- Authentik SSO & Forward Auth come online\n- Applications (Peanut, Gotify, Homebox, etc.) become reachable"]
Critical Health Verification Gates:
- Storage Gate (Phase 3
\rightarrowPhase 4):- Synology NAS NFS daemons must be responding on
14.10.10.71:2049before Docker Swarm starts, ensuring NPM does not encounter stale NFS mounts or SQLite locking.
- Synology NAS NFS daemons must be responding on
- Database Gate (Phase 5
\rightarrowPhase 6):- Longhorn volume
data-authentik-postgresql-0on Aerodrome must reachHealthystate before Authentik server pods start.
- Longhorn volume
- Ingress Gate (Phase 6):
- Nginx Proxy Manager on
snshpd4lnxswm04(140.44.4.74) confirms host-mode ports80and443are bound and forwards traffic to internal VIPs.
- Nginx Proxy Manager on