diff --git a/.gitignore b/.gitignore index d4c7bc5..2df1f6c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ linux-server/backup/status/ # Forgejo runner status JSON + last-state written at runtime (homepage card) linux-server/forgejo/runner-status/ + +# PeaNUT runtime settings written by its UI +linux-server/ups/peanut-config/ diff --git a/linux-server/HTTPS.md b/linux-server/HTTPS.md index 979a44e..d05c96f 100644 --- a/linux-server/HTTPS.md +++ b/linux-server/HTTPS.md @@ -224,6 +224,7 @@ side of the `ports:` mapping (`host:container`), not the host side. | filebrowser | 80 | ✅ done | none — works at root; proxy to container :80, **not** the old 8080 host map | | syncthing | 8384 | ✅ done | set `STGUIADDRESS=127.0.0.1:8384` (disables Syncthing's Host-header check, else `Host check error`); publish sync `:22000`/`:21027` on the **sidecar** (raw TCP/UDP, not via serve) | | glances | 61208 | ✅ done | **host-networked variant** — keep `network_mode: host`, sidecar proxies via `host.docker.internal`; widget url stays localhost | +| peanut (UPS) | 8097 | ✅ done | host-networked variant like glances — PeaNUT must reach the loopback-only `upsd:3493`, sidecar proxies via `host.docker.internal`; widget url stays localhost | | adguard | 80 | ✅ done | UI at container :80 (not the 8083 host map); publish DNS `:53` tcp+udp on the **sidecar** (raw DNS, not via serve); no :443 so no DoH/serve conflict | | atvloadly | 80 | ✅ done | no `hostname:` on the app container — conflicts with `network_mode: service:...`; Apple TV discovery is unaffected by the shared netns since it goes through the host's avahi-daemon via bind-mounted sockets, not this container's own network | | nginx-proxy-mgr | 81 | ✅ done | host edge (binds `:80/:443/:81`); its **admin UI** is fronted by a host-gateway sidecar at `npm.`, while NPM itself stays the non-tailnet trusted-cert edge (see section below) | diff --git a/linux-server/README.md b/linux-server/README.md index e90f646..db6009e 100644 --- a/linux-server/README.md +++ b/linux-server/README.md @@ -339,18 +339,32 @@ In NPM admin (`http://:81`): ``` 3. The status card on Homepage shows last-run time, status, and repo size; failures push to ntfy -19. pi-hole | [GitHub](https://github.com/pi-hole/pi-hole) | [Docs](https://docs.pi-hole.net) +19. UPS | [NUT](https://networkupstools.org/) | [Docs](https://networkupstools.org/docs/man/) + 1. Battery-backup monitoring for the CyberPower PR1500LCDRT2U over USB — ntfy alerts on power events, clean shutdown on low battery, auto-restart when wall power returns. Full runbook in [`ups/README.md`](ups/README.md) + 2. Deploy: + ```sh + sudo apt install nut # or rerun the root setup.sh --profile server + cd linux-server/ups + cp .env.example .env # set UPSMON_PASSWORD (openssl rand -hex 16) + ntfy + sudo bash setup.sh + upsc cyberpower ups.status # expect: OL + docker compose up -d # PeaNUT dashboard + homepage widget + ``` + 3. Dashboard (charge/load/runtime graphs) at `https://peanut..ts.net/` (set `TS_AUTHKEY` in `ups/.env` for the sidecar); the homepage **ups** card reads it via the `peanut` widget on localhost + 4. Set BIOS **Restore on AC Power Loss → Power On** so the server boots unattended after an outage + +20. pi-hole | [GitHub](https://github.com/pi-hole/pi-hole) | [Docs](https://docs.pi-hole.net) 1. Network-wide DNS ad blocker — alternative to AdGuard Home 2. Not yet configured -20. Immich | [GitHub](https://github.com/immich-app/immich) | [Docs](https://immich.app/docs) +21. Immich | [GitHub](https://github.com/immich-app/immich) | [Docs](https://immich.app/docs) 1. Self-hosted photo and video backup — Google Photos alternative with mobile apps, face recognition, and timeline view 2. Not yet configured -21. Jellyfin | [GitHub](https://github.com/jellyfin/jellyfin) | [Docs](https://jellyfin.org/docs/) +22. Jellyfin | [GitHub](https://github.com/jellyfin/jellyfin) | [Docs](https://jellyfin.org/docs/) 1. Self-hosted media server — stream your own movies, TV shows, and music to any device 2. Not yet configured -22. Home Assistant | [GitHub](https://github.com/home-assistant/core) | [Docs](https://www.home-assistant.io/docs/) +23. Home Assistant | [GitHub](https://github.com/home-assistant/core) | [Docs](https://www.home-assistant.io/docs/) 1. Open source smart home hub — integrates with thousands of devices and services 2. Not yet configured diff --git a/linux-server/apt_packages.md b/linux-server/apt_packages.md index c974c6d..42df832 100644 --- a/linux-server/apt_packages.md +++ b/linux-server/apt_packages.md @@ -64,7 +64,10 @@ Tested on Ubuntu Server LTS. Most packages work on other Debian-based distros. 1. Web-based server admin UI — system metrics, journal logs, network config, storage, and service management 2. Enabled automatically via systemd socket activation on install; access at `https://:9090` 3. Login with your Linux username and password -19. sshpass | [apt](https://packages.ubuntu.com/search?keywords=sshpass) | [Homepage](https://sourceforge.net/projects/sshpass/) +19. nut | [apt](https://packages.ubuntu.com/search?keywords=nut) | [Homepage](https://networkupstools.org/) + 1. Network UPS Tools — monitors the CyberPower PR1500LCDRT2U over USB; ntfy alerts + clean shutdown on low battery + 2. Needs one-time config after install: `sudo bash linux-server/ups/setup.sh` (see [`ups/README.md`](ups/README.md)) +20. sshpass | [apt](https://packages.ubuntu.com/search?keywords=sshpass) | [Homepage](https://sourceforge.net/projects/sshpass/) 1. Non-interactive SSH password authentication — used by `SSH_and_GPG/add_remote_host.sh` ## Optional diff --git a/linux-server/homepage/.env.example b/linux-server/homepage/.env.example index 737557f..ea29675 100644 --- a/linux-server/homepage/.env.example +++ b/linux-server/homepage/.env.example @@ -45,6 +45,9 @@ HOMEPAGE_VAR_SYNCTHING_DOMAIN=syncthing..ts.net # Glances — its own Tailscale sidecar name (glances..ts.net), per HTTPS.md HOMEPAGE_VAR_GLANCES_DOMAIN=glances..ts.net +# PeaNUT (UPS dashboard) — its own Tailscale sidecar name (peanut..ts.net), per HTTPS.md +HOMEPAGE_VAR_PEANUT_DOMAIN=peanut..ts.net + # AdGuard Home — its own Tailscale sidecar name (adguard..ts.net), per HTTPS.md HOMEPAGE_VAR_ADGUARD_DOMAIN=adguard..ts.net diff --git a/linux-server/homepage/config/services.yaml b/linux-server/homepage/config/services.yaml index a2794e8..c37fc97 100644 --- a/linux-server/homepage/config/services.yaml +++ b/linux-server/homepage/config/services.yaml @@ -53,6 +53,18 @@ - field: repo_size_bytes label: Repo size format: bytes + - ups: + icon: mdi-battery-charging-high + href: https://{{HOMEPAGE_VAR_PEANUT_DOMAIN}}/ + description: NUT · CyberPower 1500VA + server: my-docker + container: peanut + widget: + type: peanut + # peanut is host-networked (upsd is loopback-only); homepage is too, + # so localhost reaches it. + url: http://localhost:8097 + key: cyberpower - glances: icon: si-glances href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ diff --git a/linux-server/post-install.md b/linux-server/post-install.md index aed52fe..e6ebf52 100644 --- a/linux-server/post-install.md +++ b/linux-server/post-install.md @@ -252,6 +252,24 @@ git remote set-url origin ssh://git@forgejo..ts.net:22//.ts.net/`, and the homepage + **ups** card goes live +- [ ] Subscribe to the `server-ups` ntfy topic on your phone +- [ ] Set BIOS **Restore on AC Power Loss → Power On** + --- ## 8. Service reference @@ -270,6 +288,7 @@ git remote set-url origin ssh://git@forgejo..ts.net:22//:8384 | | | AdGuard Home | http://\:8083 | Run setup wizard at :3003 first | | Cockpit | https://\:9090 | | +| PeaNUT (UPS) | https://peanut.\.ts.net/ | Tailscale sidecar; homepage ups card reads it via localhost :8097 | | Tailscale Web UI | http://localhost:8088 | After `tailscale up` | | Tailscale proxy | http://localhost:8089 | Internal — used by Homepage widget | | Forgejo | https://forgejo.\.ts.net/ | Tailscale sidecar (HTTPS via serve); Git over SSH on port 22 | diff --git a/linux-server/ups/.env.example b/linux-server/ups/.env.example new file mode 100644 index 0000000..699dbaf --- /dev/null +++ b/linux-server/ups/.env.example @@ -0,0 +1,17 @@ +# Copy this file to .env and set the values. The .env is gitignored. + +# Local NUT password for the upsmon user (upsd listens on loopback only, but +# keep it out of git anyway). Generate with: openssl rand -hex 16 +# Alphanumeric only — setup.sh renders it into /etc/nut configs with sed. +UPSMON_PASSWORD= + +# ntfy alerts — base URL of your ntfy server (tailnet domain works from the +# host) plus the topic. NTFY_TOKEN only needed if the topic requires auth. +# Leave NTFY_URL empty to disable notifications. +NTFY_URL=https://ntfy..ts.net +NTFY_TOPIC=server-ups +NTFY_TOKEN= + +# Tailscale auth for the PeaNUT sidecar (peanut..ts.net) — same OAuth +# client secret as the other sidecars (Auth Keys scope + tag:container in the ACL). +TS_AUTHKEY= diff --git a/linux-server/ups/README.md b/linux-server/ups/README.md new file mode 100644 index 0000000..3ccea4d --- /dev/null +++ b/linux-server/ups/README.md @@ -0,0 +1,92 @@ +# UPS (NUT) + +[Network UPS Tools](https://networkupstools.org/) monitoring for the CyberPower +PR1500LCDRT2U connected over USB (`usbhid-ups` driver, standalone mode). On +power loss it pushes ntfy alerts; when the battery runs low it shuts the server +down cleanly and tells the UPS to cut its outlets so everything restarts when +wall power returns. + +## Setup + +1. Install NUT (the root `setup.sh --profile server` installs it via + `packages.json`, or): + ```sh + sudo apt install nut + ``` +2. Configure: + ```sh + cd linux-server/ups + cp .env.example .env + # set UPSMON_PASSWORD (openssl rand -hex 16) + the ntfy URL/topic + ``` +3. Deploy configs to `/etc/nut` and enable the services: + ```sh + sudo bash setup.sh # add --dry-run to preview + ``` +4. Verify: + ```sh + upsc cyberpower ups.status # expect: OL (on line power) + upsc cyberpower # full variable dump — charge, runtime, load + journalctl -u nut-monitor -f + ``` +5. Test a notification (safe — no shutdown involved): + ```sh + sudo -u nut NOTIFYTYPE=ONBATT /etc/nut/ups-notify.sh "test event" + ``` +6. In BIOS, set **Restore on AC Power Loss → Power On** so the machine boots + when the UPS re-energizes its outlets after an outage. + +## What happens during an outage + +1. Wall power drops → upsmon logs `ONBATT` and pushes an urgent ntfy alert. +2. Power returns before the battery runs low → `ONLINE` alert, nothing else. +3. Battery hits the low threshold → upsmon runs `SHUTDOWNCMD` + (`shutdown -h +0`), sets `/etc/killpower`, and the Debian shutdown hook + tells the UPS to power off its outlets after the OS halts. +4. Wall power returns → the UPS re-energizes, and with the BIOS set to + power-on the server boots unattended. + +## Tuning + +- **Low-battery threshold**: CyberPower firmware fires LOWBATT late (~10% + charge). For more headroom, uncomment in `ups.conf` and re-run `setup.sh`: + ``` + override.battery.charge.low = 25 # shut down at 25% charge + override.battery.runtime.low = 300 # or at 5 minutes runtime left + ``` +- Poll/timing knobs (`POLLFREQ`, `HOSTSYNC`, `DEADTIME`, `FINALDELAY`) are at + conventional values in `upsmon.conf.template`. +- Brief blips send an ONBATT + ONLINE alert pair. If that gets noisy, + `upssched` can debounce (only alert after N seconds on battery) — not wired + up; layer it in later if needed. + +## Dashboard (PeaNUT) + +[PeaNUT](https://github.com/Brandawg93/PeaNUT) serves a web dashboard with +charge/load/runtime graphs at `https://peanut..ts.net/` (Tailscale +sidecar per [`../HTTPS.md`](../HTTPS.md), host-networked variant like glances — +PeaNUT itself must stay on the host network to reach the loopback-only `upsd`) +and feeds the homepage **ups** card (`type: peanut` widget via localhost). +Auth is disabled (read-only stats on a trusted network, same posture as +glances). Its runtime settings dir (`peanut-config/`) is gitignored. + +```sh +cd linux-server/ups +# set TS_AUTHKEY in .env (same OAuth client secret as the other sidecars) +docker compose up -d +``` + +## Files + +| Repo file | Deployed to | Purpose | +|---|---|---| +| `nut.conf` | `/etc/nut/nut.conf` | `MODE=standalone` (turns NUT on) | +| `ups.conf` | `/etc/nut/ups.conf` | `usbhid-ups` driver for the CyberPower | +| `upsd.conf` | `/etc/nut/upsd.conf` | upsd listens on loopback only | +| `upsd.users.template` | `/etc/nut/upsd.users` | upsmon user (password from `.env`) | +| `upsmon.conf.template` | `/etc/nut/upsmon.conf` | shutdown + notification policy | +| `ups-notify.sh` | `/etc/nut/ups-notify.sh` | NOTIFYCMD → ntfy | +| (rendered from `.env`) | `/etc/nut/ups-notify.env` | ntfy settings for the hook | + +Everything in `/etc/nut` is `root:nut 640` (the notify script `750`); secrets +live only in the gitignored `.env` and the rendered `/etc/nut` files. diff --git a/linux-server/ups/docker-compose.yml b/linux-server/ups/docker-compose.yml new file mode 100644 index 0000000..5ea0413 --- /dev/null +++ b/linux-server/ups/docker-compose.yml @@ -0,0 +1,46 @@ +services: + # Tailscale sidecar — joins the tailnet as `peanut`, terminating HTTPS for + # https://peanut..ts.net. Host-networked variant per ../HTTPS.md: + # peanut stays on the host network (it must reach the loopback-only upsd), + # so the sidecar runs in its own netns and proxies back to the host's :8097 + # via host.docker.internal. + peanut-ts: + image: tailscale/tailscale:latest + container_name: peanut-ts + hostname: peanut + environment: + - TS_AUTHKEY=${TS_AUTHKEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_SERVE_CONFIG=/config/serve.json + # Requires TS_AUTHKEY to be allowed to apply tag:container (OAuth client, or + # a tag-scoped auth key). Drop this line if using an untagged personal key. + - TS_EXTRA_ARGS=--advertise-tags=tag:container + volumes: + - ./ts-state:/var/lib/tailscale + - ./ts-serve.json:/config/serve.json:ro + devices: + - /dev/net/tun:/dev/net/tun + cap_add: + - NET_ADMIN + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped + + # NUT web dashboard (charge/load/runtime graphs) — also feeds the homepage + # "ups" card's peanut widget. Host-networked because upsd listens on + # loopback only; the web UI binds 0.0.0.0:8097 so both the sidecar (via the + # docker bridge gateway) and the LAN can reach it. + peanut: + image: brandawg93/peanut:latest + container_name: peanut + restart: unless-stopped + network_mode: host + environment: + - NUT_HOST=127.0.0.1 + - NUT_PORT=3493 + - WEB_HOST=0.0.0.0 + - WEB_PORT=8097 + # Read-only UPS stats on a trusted LAN — same posture as glances. + - AUTH_DISABLED=true + volumes: + - ./peanut-config:/config diff --git a/linux-server/ups/nut.conf b/linux-server/ups/nut.conf new file mode 100644 index 0000000..6529f5e --- /dev/null +++ b/linux-server/ups/nut.conf @@ -0,0 +1 @@ +MODE=standalone diff --git a/linux-server/ups/setup.sh b/linux-server/ups/setup.sh new file mode 100755 index 0000000..9b6be76 --- /dev/null +++ b/linux-server/ups/setup.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Deploys NUT (Network UPS Tools) config for the CyberPower PR1500LCDRT2U to +# /etc/nut and enables the services. Idempotent — re-runs are no-ops unless a +# config changed. Run as root: sudo bash setup.sh [--dry-run] + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" + +DRY_RUN=false +[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true + +log() { printf '==> %s\n' "$*"; } +die() { printf 'error: %s\n' "$*" >&2; exit 1; } +run() { + if [[ "$DRY_RUN" == true ]]; then + printf '[dry-run] %s\n' "$*" + else + "$@" + fi +} + +if [[ "$DRY_RUN" == false && $EUID -ne 0 ]]; then + die "must run as root: sudo bash $0" +fi + +[[ -f "$SCRIPT_DIR/.env" ]] || die "no .env — cp .env.example .env, then set UPSMON_PASSWORD (openssl rand -hex 16)" +set -a +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/.env" +set +a + +[[ -n "${UPSMON_PASSWORD:-}" ]] || die "UPSMON_PASSWORD is empty in .env — generate with: openssl rand -hex 16" +[[ "$UPSMON_PASSWORD" =~ ^[[:alnum:]]+$ ]] || die "UPSMON_PASSWORD must be alphanumeric (it is rendered into configs with sed)" + +if ! command -v upsdrvctl >/dev/null; then + if [[ "$DRY_RUN" == true ]]; then + printf 'warning: NUT is not installed yet — run: sudo apt install nut\n' >&2 + else + die "NUT is not installed — run: sudo apt install nut (or rerun the root setup.sh)" + fi +fi + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +cp "$SCRIPT_DIR/nut.conf" "$SCRIPT_DIR/ups.conf" "$SCRIPT_DIR/upsd.conf" "$SCRIPT_DIR/ups-notify.sh" "$tmp/" +sed "s|@UPSMON_PASSWORD@|$UPSMON_PASSWORD|" "$SCRIPT_DIR/upsd.users.template" > "$tmp/upsd.users" +sed "s|@UPSMON_PASSWORD@|$UPSMON_PASSWORD|" "$SCRIPT_DIR/upsmon.conf.template" > "$tmp/upsmon.conf" +{ + printf 'NTFY_URL=%s\n' "${NTFY_URL:-}" + printf 'NTFY_TOPIC=%s\n' "${NTFY_TOPIC:-server-ups}" + printf 'NTFY_TOKEN=%s\n' "${NTFY_TOKEN:-}" +} > "$tmp/ups-notify.env" + +changed=false +deploy() { + local src="$1" dest="$2" mode="$3" + if [[ -f "$dest" ]] && cmp -s "$src" "$dest"; then + printf ' ✓ %s\n' "$dest" + return 0 + fi + run install -o root -g nut -m "$mode" "$src" "$dest" + changed=true + printf ' installed %s\n' "$dest" +} + +log "Deploying NUT configs to /etc/nut..." +deploy "$tmp/nut.conf" /etc/nut/nut.conf 640 +deploy "$tmp/ups.conf" /etc/nut/ups.conf 640 +deploy "$tmp/upsd.conf" /etc/nut/upsd.conf 640 +deploy "$tmp/upsd.users" /etc/nut/upsd.users 640 +deploy "$tmp/upsmon.conf" /etc/nut/upsmon.conf 640 +deploy "$tmp/ups-notify.env" /etc/nut/ups-notify.env 640 +deploy "$tmp/ups-notify.sh" /etc/nut/ups-notify.sh 750 + +log "Enabling NUT services..." +run systemctl enable --now nut-server.service nut-monitor.service + +if [[ "$changed" == true ]]; then + log "Configs changed — restarting NUT..." + run systemctl restart nut-server.service nut-monitor.service +fi + +if [[ "$DRY_RUN" == false ]]; then + log "Verifying (driver can take a few seconds to settle)..." + sleep 3 + if status="$(upsc cyberpower@localhost ups.status 2>/dev/null)"; then + log "ups.status: $status (OL = on line power)" + else + printf 'warning: upsc could not reach the UPS yet — check: journalctl -u nut-driver@cyberpower -u nut-server\n' >&2 + fi + log "Test a notification with:" + printf ' sudo -u nut NOTIFYTYPE=ONBATT /etc/nut/ups-notify.sh "test event"\n' +fi diff --git a/linux-server/ups/ts-serve.json b/linux-server/ups/ts-serve.json new file mode 100644 index 0000000..5b9607a --- /dev/null +++ b/linux-server/ups/ts-serve.json @@ -0,0 +1,8 @@ +{ + "TCP": { "443": { "HTTPS": true } }, + "Web": { + "${TS_CERT_DOMAIN}:443": { + "Handlers": { "/": { "Proxy": "http://host.docker.internal:8097" } } + } + } +} diff --git a/linux-server/ups/ups-notify.sh b/linux-server/ups/ups-notify.sh new file mode 100755 index 0000000..881d979 --- /dev/null +++ b/linux-server/ups/ups-notify.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# upsmon NOTIFYCMD hook — pushes UPS power events to ntfy. Deployed to +# /etc/nut/ups-notify.sh by setup.sh; runs as the nut user with the event +# message as $1 and the event type in $NOTIFYTYPE. Reads its ntfy settings +# from /etc/nut/ups-notify.env (root:nut 640, rendered from ../.env). +set -u + +[[ -f /etc/nut/ups-notify.env ]] && source /etc/nut/ups-notify.env +[[ -n "${NTFY_URL:-}" ]] || exit 0 + +msg="${1:-UPS event}" +case "${NOTIFYTYPE:-}" in + ONBATT) title="UPS on battery"; priority=urgent; tags=battery,warning ;; + LOWBATT) title="UPS battery LOW"; priority=urgent; tags=rotating_light ;; + FSD) title="UPS forced shutdown"; priority=urgent; tags=rotating_light ;; + SHUTDOWN) title="Server shutting down (UPS)"; priority=urgent; tags=rotating_light ;; + ONLINE) title="UPS back on line power"; priority=default; tags=electric_plug ;; + COMMBAD|NOCOMM) title="UPS communication lost"; priority=high; tags=warning ;; + COMMOK) title="UPS communication restored"; priority=default; tags=white_check_mark ;; + REPLBATT) title="UPS battery needs replacement"; priority=high; tags=battery ;; + *) title="UPS event (${NOTIFYTYPE:-unknown})"; priority=default; tags=zap ;; +esac + +args=(-fsS -H "Title: $title" -H "Priority: $priority" -H "Tags: $tags" -d "$msg") +[[ -n "${NTFY_TOKEN:-}" ]] && args+=(-H "Authorization: Bearer $NTFY_TOKEN") +curl "${args[@]}" "$NTFY_URL/${NTFY_TOPIC:-server-ups}" >/dev/null 2>&1 || true diff --git a/linux-server/ups/ups.conf b/linux-server/ups/ups.conf new file mode 100644 index 0000000..64dc2c4 --- /dev/null +++ b/linux-server/ups/ups.conf @@ -0,0 +1,10 @@ +[cyberpower] + driver = usbhid-ups + port = auto + vendorid = 0764 + productid = 0601 + desc = "CyberPower PR1500LCDRT2U" + # CyberPower firmware fires LOWBATT late (~10% charge). Uncomment to shut + # down with more headroom, by charge or by remaining runtime (seconds): + # override.battery.charge.low = 25 + # override.battery.runtime.low = 300 diff --git a/linux-server/ups/upsd.conf b/linux-server/ups/upsd.conf new file mode 100644 index 0000000..7a6d86d --- /dev/null +++ b/linux-server/ups/upsd.conf @@ -0,0 +1,2 @@ +LISTEN 127.0.0.1 3493 +LISTEN ::1 3493 diff --git a/linux-server/ups/upsd.users.template b/linux-server/ups/upsd.users.template new file mode 100644 index 0000000..55c95a6 --- /dev/null +++ b/linux-server/ups/upsd.users.template @@ -0,0 +1,3 @@ +[upsmon] + password = @UPSMON_PASSWORD@ + upsmon primary diff --git a/linux-server/ups/upsmon.conf.template b/linux-server/ups/upsmon.conf.template new file mode 100644 index 0000000..a816c38 --- /dev/null +++ b/linux-server/ups/upsmon.conf.template @@ -0,0 +1,24 @@ +MONITOR cyberpower@localhost 1 upsmon @UPSMON_PASSWORD@ primary + +MINSUPPLIES 1 +SHUTDOWNCMD "/sbin/shutdown -h +0" +POWERDOWNFLAG /etc/killpower +NOTIFYCMD /etc/nut/ups-notify.sh + +POLLFREQ 5 +POLLFREQALERT 5 +HOSTSYNC 15 +DEADTIME 15 +FINALDELAY 5 +RBWARNTIME 43200 +NOCOMMWARNTIME 300 + +NOTIFYFLAG ONLINE SYSLOG+EXEC +NOTIFYFLAG ONBATT SYSLOG+EXEC +NOTIFYFLAG LOWBATT SYSLOG+EXEC +NOTIFYFLAG FSD SYSLOG+EXEC +NOTIFYFLAG COMMOK SYSLOG+EXEC +NOTIFYFLAG COMMBAD SYSLOG+EXEC +NOTIFYFLAG NOCOMM SYSLOG+EXEC +NOTIFYFLAG SHUTDOWN SYSLOG+EXEC +NOTIFYFLAG REPLBATT SYSLOG+EXEC diff --git a/packages.json b/packages.json index 48e7d5c..a9f1792 100644 --- a/packages.json +++ b/packages.json @@ -409,6 +409,14 @@ "optional": false, "description": "Web-based server admin UI — system metrics, journal logs, network config, storage, and service management; access at https://:9090" }, + { + "name": "nut", + "tags": ["system-monitoring"], + "package_manager": { "server": "apt" }, + "priority": "medium", + "optional": false, + "description": "Network UPS Tools — monitors the CyberPower PR1500LCDRT2U over USB (usbhid-ups, standalone); graceful shutdown on low battery; configure via linux-server/ups/setup.sh" + }, { "name": "tailscale", "tags": ["networking"],