Skip to content

JuanCF/nutwatch

Repository files navigation

NutWatch — NUT Web Administration Panel

License: MIT

A standalone web administration panel for NUT (Network UPS Tools), installable on a Raspberry Pi or any Linux system, with Wake on LAN support and detailed UPS monitoring.

This repository also includes vm/nut-vm.sh, a bash script to automatically create an Ubuntu 24.04 VM on Proxmox VE with NUT pre-configured in netserver mode and the NutWatch web UI installed.

Why a VM instead of LXC? NUT cannot run reliably in LXC containers due to kernel driver detachment restrictions. The VM script creates a lightweight VM specifically for NUT.


Features

Web Administration UI (NutWatch)

  • Dashboard — System overview with stat cards (UPS count, users, active services, system health), CPU/memory/disk resource gauges, and quick-lists for all UPS devices and NUT services; one-click system actions (restart NutWatch, reboot, shutdown) with confirmation prompts
  • UPS Devices — CRUD management with card-grid view showing real-time telemetry (battery charge bar, load bar, runtime, voltage), per-card driver start/stop/restart, USB scan integration, and recommended config defaults
  • UPS Detail Telemetry — Deep-dive view grouped by subsystem (Battery, Input, Output, UPS, Device, Driver) with color-coded charge/load bars, unit-formatted values (V, Hz, W, VA, °C, A), runtime formatting, and raw variable dump; D3 gauge visualizations for battery charge and load
  • Historical Data & Charts — Per-UPS time-series data collected every 60 seconds into SQLite, displayed as D3 line charts with selectable ranges (1h, 24h, 7d, 30d) and per-variable filtering; configurable poll interval and 90-day retention
  • NUT Users — CRUD for NUT daemon users (upsd.users) with password masking and per-user roles (master/slave/admin); distinct from dashboard Accounts (see below)
  • Notifications — Full upsmon.conf editor with monitor line management (add/remove/edit per UPS), global commands (MINSUPPLIES, SHUTDOWNCMD, NOTIFYCMD, POWERDOWNFLAG), timing parameters grid (POLLFREQ, POLLFREQALERT, HOSTSYNC, DEADTIME, etc.), and notification message/flag matrix for all 9 events with SYSLOG/WALL/EXEC/IGNORE checkboxes
  • Per-UPS Event Hooks — Fine-grained script hooks per UPS per event (ONLINE, ONBATT, LOWBATT, COMMOK, COMMBAD, SHUTDOWN, REPLBATT, NOCOMM, NOPARENT) with in-browser script editor (Tab support), status badges, and instant save/delete
  • Live Log Streaming — Real-time SSE log viewer tailing nut-server, nut-monitor, and nut-driver journals with pause/resume, auto-scroll, color-coded lines (error/warn/info), and configurable recent log loading
  • Config Files — Raw in-browser editor for ups.conf, upsd.conf, upsmon.conf, and upsd.users (read-only via this endpoint)
  • Wake on LAN — Manage WOL targets (MAC, broadcast, description), create event-to-target mappings for automatic wake on UPS events (ONLINE, ONBATT, etc.), manual "Wake Now" and "Wake All" buttons, and non-destructive auto-dispatch via notifycmd.sh; MAC address field shows a dropdown of hosts discovered from the ARP cache (with hostnames via reverse DNS) and auto-suggests the target name from the hostname
  • Service Management — One-click restart (nut-server, nut-monitor, or both) and per-UPS driver start/stop/restart with multi-fallback cleanup (upsdrvctl, systemctl, PID kill, pkill); system-level actions: restart NutWatch (with automatic page reload), reboot, and shutdown
  • Accounts & API Keys — Optional session login (admin/viewer roles) for the dashboard and per-user API keys (Authorization: Bearer <key>) for scripts; a key inherits its owner's role. Fully open until the first admin account is created via the first-run Setup page (or skipped, keeping the app open) or manage.py create-admin
  • Atomic Config Writes — All file writes use tempfile + os.replace to prevent corruption
  • Input Validation — Identifier regex, newline injection prevention, type checking on all inputs

Proxmox VM Automation (vm/nut-vm.sh)

  • One-Command VM Creation — Downloads Ubuntu 24.04 minimal cloud image (SHA-256 verified), creates a Proxmox VM with EFI boot, virtio-scsi, and QEMU Guest Agent
  • Offline Disk Customization — Uses virt-customize to install packages, write NUT configs, and set up NutWatch directly into the disk image before VM creation
  • USB UPS Detection — Scans lsusb output, cross-references known vendor IDs (APC 051d, CyberPower 0764, Eaton 0463, Tripp Lite 09ae, Liebert 10af), handles duplicate models via bus-port notation, and configures USB passthrough
  • First-Boot Driver Auto-Detectionnut-detect oneshot systemd service runs nut-scanner -U on first boot and rewrites ups.conf with the detected driver/vendor/product IDs
  • Cloud-Init — Network configuration, rootfs resize, SSH host keys, and VM password via Proxmox cloud-init
  • Guest Agent IP Detection — 5-minute retry loop querying network-get-interfaces; manual IP entry fallback
  • Interactive Configuration — Default or Advanced setup modes with whiptail prompts for VM settings (ID, hostname, storage, bridge, RAM, CPU, disk) and NUT settings (UPS name, driver, users, listen address/port)
  • Auto-Generated Passwords — Optional secure password generation via openssl rand with fallback to /dev/urandom
  • Graceful Failure Handling — NutWatch install failure inside virt-customize is non-fatal; NUT service enablement handles multiple distro systemd variants (nut-driver-enumerator, nut-driver@, nut-driver) with || true
  • Signal Safetytrap handlers for ERR, EXIT, SIGINT, SIGTERM with cleanup and API status reporting
  • Resume Support — Partial cloud image downloads use wget -c
  • Debian 13 Compatibility — Auto-installs dhcpcd-base when missing for virt-customize network support

Notify Script & Hook Samples

  • notifycmd.sh — Central notify dispatcher that logs all UPS events, executes per-UPS per-event hook scripts from /etc/nut/notify.d/<UPSNAME>_<EVENT>.sh, and then triggers WOL auto-dispatch for any matching event mappings
  • Hook Samples (hook-samples/):
    • 01-test-marker.sh — Write a marker file when an event fires
    • 02-wall-notification.sh — Broadcast a wall message to all logged-in users
    • 03-remote-ssh-shutdown.sh — SSH-shutdown another machine when UPS goes on battery
    • 04-webhook-alert.sh — Send Discord/Slack webhook alerts with JSON payload

CI/CD & Developer Tooling

  • GitHub Actions — Lint (shellcheck, shfmt), Python syntax check (py_compile), pytest, frontend tests (Vitest), and automated release workflow on tag push
  • Makefilecheck, lint, fmt, fmt-fix, lint-python, test-python, tsc-check, lint-frontend, build-frontend, build-tarball, install-tools
  • build-tarball — Creates nutwatch.tar.gz for release distribution (git-ignored)
  • Backend Tests — Pytest suite covering parsers, service layer (UPS, users, upsmon, hooks, WOL, system), auth, route handlers, and utility functions
  • Frontend Tests — Vitest suite covering components (Badge, ConfirmDialog, Dashboard, ErrorBoundary, Gauge, HistoryChart, Modal, RestartPromptModal, Skeleton, theme, UpsCard, UpsDetail, WakeOnLan) and utilities (alerts, api, directives, format, logs, service)
  • TypeScript — Strict mode (strict: true, noUnusedLocals, noUnusedParameters) with ESLint flat config; tsc --noEmit and eslint . run as part of make check

Screenshots

Dashboard UPS Devices UPS Detail (Info)
Dashboard UPS Devices UPS Detail
UPS Charts (Input Voltage) UPS Charts (Runtime) Hooks
Input Voltage Runtime Hooks
Users Notifications Logs
Users Notifications Logs
Config Files Wake on LAN Edit UPS
Config Files Wake on LAN Edit UPS
Setup (First Admin) Accounts Add Account
Setup Accounts Add Account

Supported UPS Vendors

Vendor USB ID Driver
APC 051d usbhid-ups
CyberPower 0764 usbhid-ups
Eaton 0463 usbhid-ups
Tripp Lite 09ae usbhid-ups
Liebert 10af usbhid-ups

Other USB UPS devices can be configured manually.


Architecture

Proxmox Host
├── USB UPS Device
│   └── USB Passthrough ──┐
│                          ▼
├── vm/nut-vm.sh   VM (Ubuntu 24.04 minimal)
│   ├── Downloads         │   ├── NUT Server
│   ├── virt-customize ──►│   │   ├── nut-driver (usbhid-ups)
│   ├── Creates VM        │   │   ├── upsd (port 3493)
│   ├── Detects UPS       │   │   ├── upsmon (with notifycmd hooks + WOL dispatch)
│   └── Configures        │   ├── NutWatch (port 8081)
│       (offline disk     │   └── cloud-init (network, resize)
│        modification)    │
                           └── First boot: nut-detect scans USB,
                                auto-configures driver in ups.conf

NutWatch Backend Module Layout

src/backend/
├── app.py               # Flask application factory & entry point
├── auth.py              # Principal resolution (session cookie / Bearer key) + role checks
├── manage.py            # CLI: create-admin, reset-password, list-accounts (bootstrap/recovery)
├── config.py            # Constants (NUT_DIR, regex, env vars)
├── utils.py             # Helpers (atomic write, run_cmd, upsc queries, driver stop)
├── parsers/             # Config file parsers (parse + serialize roundtrip)
│   ├── ups_conf.py
│   ├── upsd_users.py
│   ├── upsmon_conf.py
│   ├── monitor.py       # MONITOR line manipulation, MINSUPPLIES
│   └── nut_scanner.py   # nut-scanner -U output parser
├── services/            # Business logic layer
│   ├── ups.py           # UPS CRUD, auto-add to upsmon, scan
│   ├── users.py         # User CRUD with password masking
│   ├── upsmon.py        # Full upsmon.conf read/write with validation
│   ├── hooks.py         # Per-UPS event hook file management
│   ├── system.py        # Service/driver restart, config file raw I/O
│   ├── wol.py           # WOL target/event registry, magic packet dispatch
│   ├── history.py       # SQLite collection, retention, and range queries
│   ├── resources.py     # CPU, memory, and disk monitoring via psutil
│   └── auth_db.py       # Accounts + API keys SQLite store (auth.db)
├── routes/              # Flask blueprints (API endpoints)
│   ├── ups.py
│   ├── users.py
│   ├── upsmon.py
│   ├── hooks.py
│   ├── system.py
│   ├── logs.py          # SSE log streaming + recent log fetch
│   ├── wol.py           # WOL target and event-mapping CRUD endpoints
│   ├── history.py       # Historical time-series data endpoints
│   └── auth.py          # Setup/login/logout/me, accounts CRUD, API key CRUD
├── static/              # Built React SPA (index.html + assets/)
├── tests/
│   ├── test_parsers.py           # Parser roundtrip tests
│   ├── test_auth.py              # Principal resolution, role gating tests
│   ├── test_routes.py            # API endpoint integration tests
│   ├── test_routes_auth.py       # Setup/login/accounts/apikeys route tests
│   ├── test_manage_cli.py        # CLI create-admin/reset-password/list-accounts tests
│   ├── test_services_hooks.py    # Hook file CRUD tests
│   ├── test_services_system.py   # Service/driver/config tests
│   ├── test_services_ups.py      # UPS CRUD tests
│   ├── test_services_upsmon.py   # Upsmon config tests
│   ├── test_services_users.py    # User CRUD tests
│   ├── test_services_wol.py      # WOL target/mapping tests
│   ├── test_services_history.py  # History collection/query tests
│   ├── test_services_auth_db.py  # Account/API key store tests
│   └── test_utils.py             # Utility function tests
├── scripts/
│   ├── notifycmd.sh          # UPS event notify dispatcher (hooks + WOL)
│   └── nutwatch-wol-dispatch # WOL auto-dispatch called by notifycmd.sh
├── nutwatch.service     # systemd unit file
└── requirements.txt     # flask, psutil, pytest, wakeonlan

Frontend Module Layout

src/frontend/src/
├── App.tsx              # Root component with section routing + auth gating
├── api.ts               # Fetch wrapper for /api/* (generic typed, 401 handling)
├── AuthProvider.tsx      # Session/account context (status, login, logout, setup)
├── types.ts             # Shared TypeScript interfaces and types
├── constants/index.ts   # Section IDs, API paths, event lists, defaults
├── theme.tsx            # Light/dark theme provider
├── styles/
│   ├── base.css         # Global resets and base styles
│   ├── components.css   # Shared component styles
│   └── variables.css    # CSS custom properties (colors, spacing)
├── components/
│   ├── Dashboard.tsx    # Stat cards + UPS/services overview, resource gauges, system actions
│   ├── ErrorBoundary.tsx # Error boundary with fallback UI
│   ├── UpsDevices.tsx   # UPS card grid + scan/add/edit/delete
│   ├── UpsCard.tsx      # Individual UPS card with metrics & actions
│   ├── UpsDetail.tsx    # Deep-dive telemetry grouped by subsystem
│   ├── UpsModal.tsx     # Add/edit UPS form with recommended defaults
│   ├── Gauge.tsx        # D3 gauge for battery charge and load
│   ├── HistoryChart.tsx # D3 line charts with time-range selector
│   ├── Users.tsx        # User table with CRUD
│   ├── UserModal.tsx    # Add/edit user form
│   ├── Notifications.tsx # Full upsmon.conf editor (monitors, messages, flags, timing)
│   ├── RestartPromptModal.tsx # Restart prompt after config saves
│   ├── HooksSection.tsx # Per-UPS event hook table
│   ├── HookEditor.tsx   # In-browser script editor with Tab support
│   ├── Logs.tsx         # Live SSE log viewer with pause/auto-scroll
│   ├── WakeOnLan.tsx    # WOL target registry + event mapping management
│   ├── ConfigFiles.tsx  # Raw config file editor
│   ├── ServiceStatus.tsx # Service active/inline status bar
│   ├── Skeleton.tsx     # Loading skeleton placeholder
│   ├── Sidebar.tsx      # Navigation sidebar
│   ├── Badge.tsx        # Status badge (online/onbatt/offline/unknown)
│   ├── Modal.tsx        # Reusable modal dialog system
│   ├── ConfirmDialog.tsx # Confirm/alert/dangerConfirm dialog system
│   ├── ThemeSettings.tsx # Theme toggle UI
│   ├── Setup.tsx        # First-run admin creation (with Skip)
│   ├── Login.tsx        # Session login form
│   ├── ApiKeys.tsx      # Self-service API key list/create/revoke
│   ├── ApiKeyModal.tsx  # Create-key modal (shows raw key once)
│   ├── Accounts.tsx     # Admin-only account table
│   └── AccountModal.tsx # Add/edit account form with role selector
└── utils/
    ├── alerts.ts        # System alert/resource status helpers
    ├── directives.ts    # Key=value directive parsing/formatting
    ├── format.ts        # Runtime seconds → "Xh Ym" formatter
    ├── logs.ts          # Log line color classification
    ├── metrics.ts       # UPS variable selection and chart data helpers
    └── service.ts       # Service status → badge class mapping

API Endpoints

UPS Management

Method Path Description
GET /api/ups List all UPS devices with status
POST /api/ups Add a new UPS (auto-adds to upsmon.conf)
GET /api/ups/<name> Get single UPS config
GET /api/ups/<name>/detail Get live telemetry via upsc
PUT /api/ups/<name> Update UPS config
DELETE /api/ups/<name> Delete UPS (stops driver, cleans hooks)
POST /api/ups/scan Run nut-scanner -U to detect USB UPS devices

User Management

Method Path Description
GET /api/users List all users (passwords masked)
POST /api/users Add a new user
PUT /api/users/<name> Update user
DELETE /api/users/<name> Delete user

Notifications

Method Path Description
GET /api/upsmon/config Read full upsmon.conf
PUT /api/upsmon/config Write upsmon.conf (with validation)

Per-UPS Hooks

Method Path Description
GET /api/hooks/<upsname> List existing hooks for a UPS
GET /api/hooks/<upsname>/<event> Get hook script content
PUT /api/hooks/<upsname>/<event> Create/update hook script
DELETE /api/hooks/<upsname>/<event> Delete hook script

Service & Driver Control

Method Path Description
POST /api/service/restart-server Restart nut-server
POST /api/service/restart-monitor Restart nut-monitor
POST /api/service/restart-all Restart both services
POST /api/service/status Combined nut-server + nut-monitor status
GET /api/service/status-detailed Per-service active state (nut-driver, nut-server, nut-monitor)
POST /api/driver/<name>/start Start UPS driver
POST /api/driver/<name>/stop Stop UPS driver (multi-fallback cleanup)
POST /api/driver/<name>/restart Restart UPS driver

System

Method Path Description
GET /api/system/resources CPU%, memory usage/GB, and disk usage/GB
POST /api/system/restart-nutwatch Restart the NutWatch web service (page reloads automatically)
POST /api/system/reboot Reboot the entire system (requires an admin account to be configured)
POST /api/system/shutdown Shut down the system (requires an admin account to be configured)

Auth (Accounts & API Keys)

Method Path Access Description
GET /api/auth/status public Bootstrap/authentication state
POST /api/auth/setup first-run only Create the first admin account (blocked once any account exists)
POST /api/auth/login public Username/password → session cookie (rate-limited)
POST /api/auth/logout session Clear session
GET /api/auth/me session or key Current identity + role
GET /api/accounts admin List accounts
POST /api/accounts admin Create an account
PUT /api/accounts/<id> admin Edit role/password/active status
DELETE /api/accounts/<id> admin Deactivate an account (soft delete)
GET /api/apikeys any account List the caller's own API keys (metadata only)
POST /api/apikeys any account Create a key — returns the raw key once
DELETE /api/apikeys/<id> owner Revoke one of the caller's own keys

Config Files

Method Path Description
GET /api/config/<filename> Read config file content
PUT /api/config/<filename> Write config file (read-only for upsd.users)

Allowed files: ups.conf, upsd.conf, upsmon.conf, upsd.users

Logs

Method Path Description
GET /api/logs/recent?lines=N Recent N lines from NUT journals
GET /api/logs/stream SSE stream tailing nut-server + nut-monitor + nut-driver

History

Method Path Description
GET /api/history/<ups>?range=<range>&variables=<vars> Time-series snapshots (ranges: 1h, 24h, 7d, 30d)
GET /api/history/<ups>/variables Available tracked variables for a UPS

Wake on LAN

Method Path Description
GET /api/wol/targets List all WOL targets
POST /api/wol/targets Create a WOL target
PUT /api/wol/targets/<name> Update a WOL target
DELETE /api/wol/targets/<name> Delete a WOL target
POST /api/wol/targets/<name>/wake Send magic packet to target
POST /api/wol/wake-all Send magic packet to all targets
GET /api/wol/mappings List all event mappings
POST /api/wol/mappings Create an event mapping
DELETE /api/wol/mappings/<id> Delete an event mapping
GET /api/wol/network-hosts List ARP-cache hosts (IP, MAC, hostname) for MAC suggestions

Deployment Options

Standalone Install (Raspberry Pi / Any Linux)

Option A — Full NUT + NutWatch setup (recommended)

Installs and configures NUT in netserver mode and the NutWatch web UI in one step:

sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/JuanCF/nutwatch/main/scripts/setup.sh)"

Non-interactive mode with auto-generated passwords:

curl -fsSL https://raw.githubusercontent.com/JuanCF/nutwatch/main/scripts/setup.sh | sudo AUTO=1 bash

NutWatch-only mode — Use --install-only on a machine that already has NUT configured:

sudo bash scripts/setup.sh --install-only

Update mode — Use --update to upgrade only the NutWatch application code in an existing installation (VM or standalone), preserving NUT configs, hooks, and the Python venv:

sudo bash scripts/setup.sh --update

Override any setting via environment variables:

sudo NUT_UPS_NAME="myups" NUT_ADMIN_PASS="securepass" AUTO=1 bash scripts/setup.sh

What it does (fresh install):

  • Installs nut-server, nut-client, usbutils
  • Writes all NUT config files (nut.conf, ups.conf, upsd.conf, upsd.users, upsmon.conf)
  • Scans USB for UPS devices (first-boot auto-detection service available)
  • Installs notifycmd.sh with per-UPS per-event hook support and WOL auto-dispatch
  • Installs nutwatch-wol-dispatch to /usr/local/bin/ for event-driven WOL
  • Installs NutWatch web UI on port 8081 with systemd service
  • Enables and starts all NUT services
  • Configures firewall (ufw) rules
Environment Variable Default Description
AUTO (unset) Set to 1 for non-interactive mode with defaults
NUT_UPS_NAME ups UPS identifier
NUT_UPS_DESC My UPS UPS description
NUT_DRIVER usbhid-ups NUT driver
NUT_ADMIN_USER admin NUT daemon admin username
NUT_ADMIN_PASS (auto-gen) NUT daemon admin password
NUT_MONITOR_USER monuser NUT monitor username
NUT_MONITOR_PASS (auto-gen) NUT monitor password
NUT_LISTEN_ADDR 0.0.0.0 NUT listen address
NUT_LISTEN_PORT 3493 NUT listen port
NUTWATCH_REF v1.2.0 NutWatch release tag
NUTWATCH_URL_PREFIX (unset) Override tarball URL for local testing

After install, the app is fully open (no login). Create the first admin with manage.py create-admin <username> (run from the NutWatch install directory, e.g. venv/bin/python manage.py create-admin admin) to turn on session login and per-user API keys — or create it from the dashboard's first-run Setup page.

Option B — NutWatch only (existing NUT setup)

Install just the NutWatch web UI on a machine that already has NUT configured:

curl -fsSL https://raw.githubusercontent.com/JuanCF/nutwatch/main/scripts/setup.sh | sudo bash -s -- --install-only

Set the NUTWATCH_REF env var to pin a specific release version.

Proxmox VM (One-Liner)

bash -c "$(curl -fsSL https://raw.githubusercontent.com/JuanCF/nutwatch/main/vm/nut-vm.sh)"

Manual Download

# Download the script
curl -fsSL https://raw.githubusercontent.com/JuanCF/nutwatch/main/vm/nut-vm.sh -o nut-vm.sh

# Or clone the repository
git clone https://github.com/JuanCF/nutwatch.git
cd nutwatch

# Run from source
bash vm/nut-vm.sh

Usage

# Run on Proxmox host as root (from cloned repo)
bash vm/nut-vm.sh

# Or if downloaded directly
bash nut-vm.sh

Environment Variables

vm/nut-vm.sh

Variable Default Description
VERBOSE (unset) Set to yes to show full command output
NUTWATCH_URL_PREFIX (unset) Override the GitHub Releases URL for the nutwatch tarball
COMMUNITY_SCRIPTS_URL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main Base URL for sourcing helper functions
VERBOSE=yes bash vm/nut-vm.sh                    # Verbose output
NUTWATCH_URL_PREFIX=https://example.com/my-fork bash vm/nut-vm.sh
COMMUNITY_SCRIPTS_URL=https://my-mirror.example.com bash vm/nut-vm.sh

NutWatch Web App

Variable Default Description
NUTWATCH_HOST 0.0.0.0 Listen address for the web server
NUTWATCH_PORT 8081 Listen port for the web server
NUTWATCH_HISTORY_INTERVAL 60 Seconds between UPS variable snapshots
NUTWATCH_HISTORY_RETENTION_DAYS 90 Days of history to retain in SQLite
NUTWATCH_AUTH_DB /var/lib/nutwatch/auth.db SQLite path for accounts + API keys
NUTWATCH_SECRET_KEY (auto-generated) Flask session signing key; auto-generated and persisted in the auth DB on first run if unset
NUTWATCH_SESSION_COOKIE_SECURE false Set to true to mark the session cookie Secure (only if serving over HTTPS — plain-HTTP LAN deployments must leave this false)

scripts/setup.sh (--install-only mode)

Variable Default Description
NUTWATCH_REF v1.2.0 Git tag for release download URL
NUTWATCH_URL_PREFIX (unset) Override URL for testing local builds

Interactive VM Prompts

  1. VM Configuration — VM ID, hostname, storage pool, network bridge, RAM, CPU cores, disk size, VM username/password
  2. UPS Detection — Auto-scan for USB UPS devices, duplicate model handling, manual entry fallback
  3. NUT Configuration — UPS name/description, driver, admin/monitor users, listen address/port

Deployment & Releases

Creating a Release

git tag v1.2.3
git push origin v1.2.3

The GitHub Actions workflow will run lint checks, build nutwatch.tar.gz, and create a GitHub Release.

Testing a Local Build

make build-tarball
python3 -m http.server 8080 --directory .
NUTWATCH_URL_PREFIX="http://<your-ip>:8080" bash vm/nut-vm.sh

Developer Commands

make check          # Full CI suite: lint + format check + Python lint + pytest + frontend tests
make lint           # shellcheck only
make fmt            # shfmt -d -i 2 (check only)
make fmt-fix        # shfmt -w -i 2 (auto-fix)
make lint-python    # py_compile check on all Python files
make test-python    # pytest on src/backend/tests/
make test-frontend  # Vitest on src/frontend/src/__tests__/
make build-frontend # npm ci + npm run build
make build-tarball  # Create nutwatch.tar.gz for distribution
make install-tools  # Install dev dependencies

Hook Samples

Place scripts in /etc/nut/notify.d/<UPSNAME>_<EVENT>.sh:

Sample Description
01-test-marker.sh Write a marker file to /tmp/ups-test.log
02-wall-notification.sh Broadcast wall message and syslog alert
03-remote-ssh-shutdown.sh SSH into another machine and shut it down
04-webhook-alert.sh Send Discord/Slack webhook JSON payload

Each hook receives $UPSNAME and $NOTIFYTYPE environment variables from upsmon.


Verification

# Check VM status
qm list

# Verify USB passthrough
qm config <vmid> | grep usb

# Test NUT from Proxmox host
upsc ups@<VM_IP>

# Test from another machine
upsc ups@<VM_IP>:3493

# Check NUT services inside VM
systemctl status nut-server nut-monitor

# Access NutWatch web UI
http://<VM_IP>:8081

Security Notes

  • NUT passwords should be strong and unique
  • The netserver listens on all interfaces by default (0.0.0.0)
  • Consider firewall rules to restrict NUT port (3493) access
  • The VM password is set via Proxmox's built-in cloud-init (qm set --cipassword)
  • Hook scripts are owned root:nut with 750 permissions for secure upsmon execution
  • WOL registry files (wol.json, wol-events.json) are owned root:nut with 640 permissions
  • Config file writes use atomic tempfile + os.replace to prevent partial writes

License

MIT License — See LICENSE file for details.

Contributing

Contributions welcome! Please feel free to submit a Pull Request.

Acknowledgments

Support


Disclaimer: This script modifies your Proxmox configuration. Always review scripts before running them as root. Test in a non-production environment first.

About

Self-hosted web dashboard to monitor and manage NUT (Network UPS Tools) UPS devices

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors