commit 918ff903f0316e5bf6b51dcc93af52effb952f44 Author: monoadmin Date: Fri Apr 10 15:36:36 2026 -0700 Initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6522e53 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# OpenWrt UPS SNMP configuration +# Copy to .env and fill in your values + +# UPS SNMP host +SNMP_HOST=192.168.1.1 +SNMP_COMMUNITY=public + +# OpenWrt host +OPENWRT_HOST=192.168.1.1 +OPENWRT_USER=root +OPENWRT_PASSWORD=change_me diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..487dd4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Environment variables +.env +.env.* +!.env.example + +# Dependencies +node_modules/ +vendor/ + +# Build output +.next/ +dist/ +build/ +*.pyc +__pycache__/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + diff --git a/backup/etc/config/dhcp b/backup/etc/config/dhcp new file mode 100644 index 0000000..2d402a0 --- /dev/null +++ b/backup/etc/config/dhcp @@ -0,0 +1,53 @@ + +config dnsmasq + option domainneeded '1' + option boguspriv '1' + option filterwin2k '0' + option localise_queries '1' + option rebind_protection '1' + option rebind_localhost '1' + option local '/lan/' + option domain 'lan' + option expandhosts '1' + option nonegcache '0' + option cachesize '1000' + option authoritative '1' + option readethers '1' + option leasefile '/tmp/dhcp.leases' + option resolvfile '/tmp/resolv.conf.d/resolv.conf.auto' + option nonwildcard '1' + option localservice '1' + option ednspacket_max '1232' + option filter_aaaa '0' + option filter_a '0' + +config dhcp 'lan' + option interface 'lan' + option start '100' + option limit '150' + option leasetime '12h' + option dhcpv4 'server' + option dhcpv6 'server' + option ra 'server' + option ra_slaac '1' + list ra_flags 'managed-config' + list ra_flags 'other-config' + option ignore '1' + +config dhcp 'wan' + option interface 'wan' + option ignore '1' + +config odhcpd 'odhcpd' + option maindhcp '0' + option leasefile '/tmp/hosts/odhcpd' + option leasetrigger '/usr/sbin/odhcpd-update' + option loglevel '4' + option piofolder '/tmp/odhcpd-piofolder' + +config dhcp 'wifi_ap' + option interface 'wifi_ap' + option start '100' + option limit '100' + option leasetime '12h' + diff --git a/backup/etc/config/network b/backup/etc/config/network new file mode 100644 index 0000000..16de8f6 --- /dev/null +++ b/backup/etc/config/network @@ -0,0 +1,60 @@ + +config interface 'loopback' + option device 'lo' + option proto 'static' + option ipaddr '127.0.0.1' + option netmask '255.0.0.0' + +config globals 'globals' + option ula_prefix 'fd6b:e811:df87::/48' + option packet_steering '1' + +config device + option name 'br-lan' + option type 'bridge' + list ports 'eth0.1' + list ports 'eth1' + +config device + option name 'eth0.1' + option macaddr '4c:5e:0c:b5:35:f3' + +config interface 'lan' + option device 'br-lan' + option proto 'static' + option ipaddr '20.0.0.26' + option netmask '255.255.255.0' + option gateway '20.0.0.1' + list dns '64.126.133.1' + +config switch + option name 'switch0' + option reset '1' + option enable_vlan '1' + +config switch_vlan + option device 'switch0' + option vlan '1' + option ports '1 2 3 4 0t' + +config interface 'wifi_ap' + option proto 'static' + option ipaddr '192.168.88.1' + option netmask '255.255.255.0' + +config device 'br_lan_dev' + option name 'br-lan' + option type 'bridge' + list ports 'eth0.1' + list ports 'eth1' + +config switch 'switch' + option name 'switch0' + option reset '1' + option enable_vlan '1' + +config switch_vlan 'switch_vlan1' + option device 'switch0' + option vlan '1' + option ports '1 2 3 4 0t' + diff --git a/backup/etc/config/nut_cgi b/backup/etc/config/nut_cgi new file mode 100644 index 0000000..191ae2c --- /dev/null +++ b/backup/etc/config/nut_cgi @@ -0,0 +1,8 @@ + +config upsset + +config host 'tripplite' + option upsname 'tripplite' + option hostname 'localhost' + option displayname 'Local UPS' + diff --git a/backup/etc/config/nut_monitor b/backup/etc/config/nut_monitor new file mode 100644 index 0000000..9a927f6 --- /dev/null +++ b/backup/etc/config/nut_monitor @@ -0,0 +1,17 @@ + +config upsmon 'upsmon' + option minsupplies '1' + option shutdowncmd '/sbin/halt' + option pollfreq '5' + option pollfreqalert '5' + option hostsync '15' + option deadtime '15' + option finaldelay '5' + +config master 'tripplite' + option upsname 'tripplite' + option hostname 'localhost' + option powervalue '1' + option username 'upsmon' + option password 'nutlocal' + diff --git a/backup/etc/config/nut_server b/backup/etc/config/nut_server new file mode 100644 index 0000000..0108940 --- /dev/null +++ b/backup/etc/config/nut_server @@ -0,0 +1,24 @@ + +config driver 'tripplite' + option driver 'usbhid-ups' + option port 'auto' + option desc 'Local UPS' + option pollinterval '5' + option vendorid '09ae' + option productid '3015' + +config listen_address 'listen1' + option address '127.0.0.1' + +config user 'upsmon_user' + option username 'upsmon' + option password 'nutlocal' + option upsmon 'master' + +config user + option username 'monitor' + option upsmon 'slave' + +config listen_address + option address '0.0.0.0' + diff --git a/backup/etc/config/snmpd b/backup/etc/config/snmpd new file mode 100644 index 0000000..6b7e188 --- /dev/null +++ b/backup/etc/config/snmpd @@ -0,0 +1,139 @@ + +config agent + option agentaddress 'UDP:161,UDP6:161' + +config agentx + option agentxsocket '/var/run/agentx.sock' + +config com2sec 'public' + option secname 'ro' + option source 'default' + option community 'fsr' + +config com2sec 'private' + option secname 'rw' + option source 'localhost' + option community 'private' + +config com2sec6 'public6' + option secname 'ro' + option source 'default' + option community 'fsr' + +config com2sec6 'private6' + option secname 'rw' + option source 'localhost' + option community 'private' + +config group 'public_v1' + option group 'public' + option version 'v1' + option secname 'ro' + +config group 'public_v2c' + option group 'public' + option version 'v2c' + option secname 'ro' + +config group 'public_usm' + option group 'public' + option version 'usm' + option secname 'ro' + +config group 'private_v1' + option group 'private' + option version 'v1' + option secname 'rw' + +config group 'private_v2c' + option group 'private' + option version 'v2c' + option secname 'rw' + +config group 'private_usm' + option group 'private' + option version 'usm' + option secname 'rw' + +config view 'all' + option viewname 'all' + option type 'included' + option oid '.1' + +config access 'public_access' + option group 'public' + option context 'none' + option version 'any' + option level 'noauth' + option prefix 'exact' + option read 'all' + option write 'none' + option notify 'none' + +config access 'private_access' + option group 'private' + option context 'none' + option version 'any' + option level 'noauth' + option prefix 'exact' + option read 'all' + option write 'all' + option notify 'all' + +config system + option sysLocation 'office' + option sysContact 'bofh@example.com' + option sysName 'HeartOfGold' + +config exec + option name 'filedescriptors' + option prog '/bin/cat' + option args '/proc/sys/fs/file-nr' + +config engineid + option engineidtype '3' + option engineidnic 'eth0' + +config snmpd 'general' + option enabled '1' + +config extend 'battery_charge' + option name 'battery_charge' + option prog '/usr/bin/upsc' + option args 'tripplite@localhost battery.charge' + +config extend 'battery_runtime' + option name 'battery_runtime' + option prog '/usr/bin/upsc' + option args 'tripplite@localhost battery.runtime' + +config extend 'ups_load' + option name 'ups_load' + option prog '/usr/bin/upsc' + option args 'tripplite@localhost ups.load' + +config extend 'input_voltage' + option name 'input_voltage' + option prog '/usr/bin/upsc' + option args 'tripplite@localhost input.voltage' + +config extend 'output_voltage' + option name 'output_voltage' + option prog '/usr/bin/upsc' + option args 'tripplite@localhost output.voltage' + +config extend 'ups_status' + option name 'ups_status' + option prog '/usr/bin/upsc' + option args 'tripplite@localhost ups.status' + +config extend 'battery_temp' + option name 'battery_temp' + option prog '/usr/bin/upsc' + option args 'tripplite@localhost battery.temperature' + +config extend 'battery_voltage' + option name 'battery_voltage' + option prog '/usr/bin/upsc' + option args 'tripplite@localhost battery.voltage' + diff --git a/backup/etc/config/wireless b/backup/etc/config/wireless new file mode 100644 index 0000000..a3ca564 --- /dev/null +++ b/backup/etc/config/wireless @@ -0,0 +1,17 @@ + +config wifi-device 'radio0' + option type 'mac80211' + option path 'platform/ahb/18100000.wmac' + option band '2g' + option channel '1' + option htmode 'HT20' + option disabled '0' + +config wifi-iface 'default_radio0' + option device 'radio0' + option network 'wifi_ap' + option mode 'ap' + option ssid 'First Step Internet' + option encryption 'psk2' + option key '12345678' + diff --git a/backup/etc/hotplug.d/usb/99-nut-ups b/backup/etc/hotplug.d/usb/99-nut-ups new file mode 100644 index 0000000..66df784 --- /dev/null +++ b/backup/etc/hotplug.d/usb/99-nut-ups @@ -0,0 +1,21 @@ +#!/bin/sh +# Auto-restart NUT when Tripp Lite UPS is plugged/unplugged +UPS_VENDOR="09ae" + +case "$ACTION" in + add) + if echo "$PRODUCT" | grep -qi "^${UPS_VENDOR}"; then + # Fix USB device permissions so NUT driver (nut user) can access it + [ -n "$DEVNAME" ] && chmod 0660 "/dev/$DEVNAME" && chown root:nut "/dev/$DEVNAME" + logger -t NUT "Tripp Lite UPS connected (PRODUCT=$PRODUCT) - restarting NUT" + sleep 3 + /etc/init.d/nut-server restart + fi + ;; + remove) + if echo "$PRODUCT" | grep -qi "^${UPS_VENDOR}"; then + logger -t NUT "Tripp Lite UPS disconnected - restarting NUT" + /etc/init.d/nut-server restart + fi + ;; +esac diff --git a/backup/etc/rc.local b/backup/etc/rc.local new file mode 100644 index 0000000..f6e042d --- /dev/null +++ b/backup/etc/rc.local @@ -0,0 +1,8 @@ +# Put your custom commands here that should be executed once +# the system init finished. By default this file does nothing. + +mkdir -p /var/etc/nut +chgrp nut /var/etc/nut +chmod 750 /var/etc/nut + +exit 0 diff --git a/backup/openwrt-backup.tar.gz b/backup/openwrt-backup.tar.gz new file mode 100644 index 0000000..870c874 Binary files /dev/null and b/backup/openwrt-backup.tar.gz differ diff --git a/backup/www/cgi-bin/wifi.cgi b/backup/www/cgi-bin/wifi.cgi new file mode 100755 index 0000000..6110ccb --- /dev/null +++ b/backup/www/cgi-bin/wifi.cgi @@ -0,0 +1,100 @@ +#!/bin/sh + +urldecode() { + echo -e "$(echo "$1" | sed "s/+/ /g; s/%\([0-9A-Fa-f][0-9A-Fa-f]\)/\\\\x\1/g")" +} + +if [ "$REQUEST_METHOD" = "POST" ]; then + read -n "$CONTENT_LENGTH" POST_DATA 2>/dev/null + action=$(echo "$POST_DATA" | tr "&" "\n" | grep "^action=" | cut -d= -f2-) + new_ssid=$(echo "$POST_DATA" | tr "&" "\n" | grep "^ssid=" | cut -d= -f2-) + new_key=$(echo "$POST_DATA" | tr "&" "\n" | grep "^key=" | cut -d= -f2-) + new_ssid=$(urldecode "$new_ssid") + new_key=$(urldecode "$new_key") + + case "$action" in + toggle) + disabled=$(uci get wireless.radio0.disabled 2>/dev/null || echo 0) + if [ "$disabled" = "1" ]; then + uci set wireless.radio0.disabled="0" + uci commit wireless + wifi up & + else + uci set wireless.radio0.disabled="1" + uci commit wireless + wifi down & + fi + ;; + update) + [ -n "$new_ssid" ] && uci set wireless.default_radio0.ssid="$new_ssid" + [ -n "$new_key" ] && [ ${#new_key} -ge 8 ] && uci set wireless.default_radio0.key="$new_key" + uci commit wireless + wifi reload & + ;; + esac +fi + +disabled=$(uci get wireless.radio0.disabled 2>/dev/null || echo 0) +ssid=$(uci get wireless.default_radio0.ssid 2>/dev/null) +[ "$disabled" = "1" ] && wstatus="Disabled" && wcolor="#dc2626" && toggle_lbl="Enable WiFi" \ + || wstatus="Enabled" && wcolor="#16a34a" && toggle_lbl="Disable WiFi" + +echo "Content-Type: text/html" +echo "" +cat << HTML + + + + + + WiFi Control + + + +
+

WiFi Control

+
+
+
+
Status: ${wstatus}
+
SSID: ${ssid}
+
+
+
+ + +
+
+
+ + + + + + +
+

Changes apply within ~5 seconds

+
+ + +HTML diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..6758c66 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Run the install script on the OpenWrt device. +# Usage: ./deploy.sh [user@host] +set -e + +HOST="${1:-root@20.0.0.26}" +echo "==> Deploying to $HOST" +scp -O openwrt/install.sh "$HOST":/tmp/ups-install.sh +ssh "$HOST" "chmod +x /tmp/ups-install.sh && /tmp/ups-install.sh" diff --git a/openwrt/install.sh b/openwrt/install.sh new file mode 100755 index 0000000..f01605e --- /dev/null +++ b/openwrt/install.sh @@ -0,0 +1,323 @@ +#!/bin/sh +# Run on the OpenWrt device (20.0.0.26) to install and configure NUT + snmpd +# Also configures: bridge LAN ports, static IP, WiFi AP, DHCP on WiFi only +# Usage: sh /tmp/ups-install.sh +set -e + +echo "==> Updating package lists..." +opkg update + +echo "==> Installing NUT..." +opkg install nut nut-server nut-upsmon +opkg install nut-driver-usbhid-ups || opkg install nut-usbhid-ups || true +opkg install kmod-usb-hid 2>/dev/null || true + +echo "==> Installing LuCI NUT config page and web CGI status..." +opkg install luci-app-nut + +echo "==> Installing snmpd (full NET-SNMP with extend support)..." +/etc/init.d/mini_snmpd stop 2>/dev/null || true +/etc/init.d/mini_snmpd disable 2>/dev/null || true +opkg remove mini_snmpd 2>/dev/null || true +opkg install snmpd luci-app-snmpd + +echo "==> Configuring NUT via UCI..." +uci set nut_server.tripplite=driver +uci set nut_server.tripplite.driver='usbhid-ups' +uci set nut_server.tripplite.port='auto' +uci set nut_server.tripplite.desc='Local UPS' +uci set nut_server.tripplite.vendorid='09ae' +# No productid filter — matches any Tripp Lite USB UPS (SMART1500RMXL2UA, SU1500RTXL2Ua, etc.) +uci set nut_server.tripplite.pollinterval='5' + +uci set nut_server.listen1=listen_address +uci set nut_server.listen1.address='127.0.0.1' + +uci set nut_server.upsmon_user=user +uci set nut_server.upsmon_user.username='upsmon' +uci set nut_server.upsmon_user.password='nutlocal' +uci set nut_server.upsmon_user.upsmon='master' + +# Read-only passwordless monitor account for CGI stats page +uci set nut_server.monitor_ro=user +uci set nut_server.monitor_ro.username='monitor' +uci set nut_server.monitor_ro.upsmon='slave' + +uci commit nut_server + +uci set nut_monitor.upsmon=upsmon +uci set nut_monitor.upsmon.minsupplies='1' +uci set nut_monitor.upsmon.shutdowncmd='/sbin/halt' +uci set nut_monitor.upsmon.pollfreq='5' +uci set nut_monitor.upsmon.pollfreqalert='5' +uci set nut_monitor.upsmon.hostsync='15' +uci set nut_monitor.upsmon.deadtime='15' +uci set nut_monitor.upsmon.finaldelay='5' + +uci set nut_monitor.tripplite=master +uci set nut_monitor.tripplite.upsname='tripplite' +uci set nut_monitor.tripplite.hostname='localhost' +uci set nut_monitor.tripplite.powervalue='1' +uci set nut_monitor.tripplite.username='upsmon' +uci set nut_monitor.tripplite.password='nutlocal' + +uci commit nut_monitor + +uci set nut_cgi.tripplite=host +uci set nut_cgi.tripplite.upsname='tripplite' +uci set nut_cgi.tripplite.hostname='localhost' +uci set nut_cgi.tripplite.displayname='Local UPS' +uci commit nut_cgi + +echo "==> Configuring snmpd community and UPS extend entries via UCI..." +uci set snmpd.public.community='fsr' +uci set snmpd.public6.community='fsr' + +for name in battery_charge battery_runtime ups_load input_voltage output_voltage ups_status battery_temp battery_voltage; do + # Map UCI name back to NUT variable (underscores -> dots, battery_temp -> battery.temperature) + nut_var=$(echo "$name" | tr '_' '.') + [ "$name" = "battery_temp" ] && nut_var="battery.temperature" + uci set snmpd.${name}=extend + uci set snmpd.${name}.name="$name" + uci set snmpd.${name}.prog='/usr/bin/upsc' + uci set snmpd.${name}.args="tripplite@localhost $nut_var" +done +uci commit snmpd + +echo "==> Fixing /var/etc/nut directory permissions (persists via rc.local)..." +mkdir -p /var/etc/nut +chgrp nut /var/etc/nut +chmod 750 /var/etc/nut + +grep -q 'var/etc/nut' /etc/rc.local 2>/dev/null || \ + sed -i 's|^exit 0|mkdir -p /var/etc/nut\nchgrp nut /var/etc/nut\nchmod 750 /var/etc/nut\n\nexit 0|' /etc/rc.local + +echo "==> Creating /etc/nut symlinks to UCI-generated configs..." +for f in ups.conf upsd.conf upsd.users nut.conf; do + rm -f /etc/nut/$f + ln -sf /var/etc/nut/$f /etc/nut/$f +done + +echo "==> Enabling services..." +/etc/init.d/nut-server enable +/etc/init.d/nut-monitor enable +/etc/init.d/nut-cgi enable +/etc/init.d/snmpd enable + +echo "==> Writing USB hotplug script for automatic NUT restart on UPS connect/disconnect..." +cat > /etc/hotplug.d/usb/99-nut-ups << 'HOTPLUG' +#!/bin/sh +# Auto-restart NUT when Tripp Lite UPS is plugged/unplugged +UPS_VENDOR="09ae" + +case "$ACTION" in + add) + if echo "$PRODUCT" | grep -qi "^${UPS_VENDOR}"; then + # Fix USB device permissions so NUT driver (nut user) can access it + [ -n "$DEVNAME" ] && chmod 0660 "/dev/$DEVNAME" && chown root:nut "/dev/$DEVNAME" + logger -t NUT "Tripp Lite UPS connected (PRODUCT=$PRODUCT) - restarting NUT" + sleep 3 + /etc/init.d/nut-server restart + fi + ;; + remove) + if echo "$PRODUCT" | grep -qi "^${UPS_VENDOR}"; then + logger -t NUT "Tripp Lite UPS disconnected - restarting NUT" + /etc/init.d/nut-server restart + fi + ;; +esac +HOTPLUG +chmod +x /etc/hotplug.d/usb/99-nut-ups + +echo "==> Removing HTTP basic auth from NUT CGI path (upsstats.cgi accessible without login)..." +# nut-web-cgi installs /etc/httpd.conf with auth on /cgi-bin/nut — clear it so upsstats.cgi +# is viewable without a username/password (upsset.cgi still requires upsd credentials) +> /etc/httpd.conf + +echo "==> Starting services..." +/etc/init.d/nut-server restart +sleep 10 +/etc/init.d/nut-monitor restart +sleep 2 +/etc/init.d/nut-cgi restart +/etc/init.d/snmpd restart + +echo "==> Configuring network (bridge eth0.1+eth1, static 20.0.0.26/24, WiFi AP 192.168.88.1/24)..." +uci batch << 'EOF' +delete network.wan +delete network.wan6 +set network.loopback=interface +set network.loopback.device='lo' +set network.loopback.proto='static' +set network.loopback.ipaddr='127.0.0.1' +set network.loopback.netmask='255.0.0.0' +set network.globals=globals +set network.globals.ula_prefix='fd6b:e811:df87::/48' +set network.br_lan_dev=device +set network.br_lan_dev.name='br-lan' +set network.br_lan_dev.type='bridge' +del_list network.br_lan_dev.ports='eth0.1' +del_list network.br_lan_dev.ports='eth1' +add_list network.br_lan_dev.ports='eth0.1' +add_list network.br_lan_dev.ports='eth1' +set network.lan=interface +set network.lan.device='br-lan' +set network.lan.proto='static' +set network.lan.ipaddr='20.0.0.26' +set network.lan.netmask='255.255.255.0' +set network.lan.gateway='20.0.0.1' +set network.lan.dns='20.0.0.1' +set network.wifi_ap=interface +set network.wifi_ap.proto='static' +set network.wifi_ap.ipaddr='192.168.88.1' +set network.wifi_ap.netmask='255.255.255.0' +EOF +uci commit network + +echo "==> Configuring switch VLAN (all wired ports on VLAN 1)..." +uci set network.switch=switch +uci set network.switch.name='switch0' +uci set network.switch.reset='1' +uci set network.switch.enable_vlan='1' +uci set network.switch_vlan1=switch_vlan +uci set network.switch_vlan1.device='switch0' +uci set network.switch_vlan1.vlan='1' +uci set network.switch_vlan1.ports='1 2 3 4 0t' +uci commit network + +echo "==> Configuring wireless (SSID: fsr, WPA2, AP on wifi_ap interface)..." +uci set wireless.radio0.disabled='0' +uci set wireless.default_radio0=wifi-iface +uci set wireless.default_radio0.device='radio0' +uci set wireless.default_radio0.network='wifi_ap' +uci set wireless.default_radio0.mode='ap' +uci set wireless.default_radio0.ssid='First Step Internet' +uci set wireless.default_radio0.encryption='psk2' +uci set wireless.default_radio0.key='12345678' +uci commit wireless + +echo "==> Configuring DHCP (disabled on lan, enabled on wifi_ap 192.168.88.100-199)..." +uci set dhcp.lan.ignore='1' +uci set dhcp.wifi_ap=dhcp +uci set dhcp.wifi_ap.interface='wifi_ap' +uci set dhcp.wifi_ap.start='100' +uci set dhcp.wifi_ap.limit='100' +uci set dhcp.wifi_ap.leasetime='12h' +uci commit dhcp + +echo "==> Writing WiFi control CGI page..." +mkdir -p /www/cgi-bin +cat > /www/cgi-bin/wifi.cgi << 'CGISCRIPT' +#!/bin/sh + +urldecode() { + echo -e "$(echo "$1" | sed "s/+/ /g; s/%\([0-9A-Fa-f][0-9A-Fa-f]\)/\\\\x\1/g")" +} + +if [ "$REQUEST_METHOD" = "POST" ]; then + read -n "$CONTENT_LENGTH" POST_DATA 2>/dev/null + action=$(echo "$POST_DATA" | tr "&" "\n" | grep "^action=" | cut -d= -f2-) + new_ssid=$(echo "$POST_DATA" | tr "&" "\n" | grep "^ssid=" | cut -d= -f2-) + new_key=$(echo "$POST_DATA" | tr "&" "\n" | grep "^key=" | cut -d= -f2-) + new_ssid=$(urldecode "$new_ssid") + new_key=$(urldecode "$new_key") + + case "$action" in + toggle) + disabled=$(uci get wireless.radio0.disabled 2>/dev/null || echo 0) + if [ "$disabled" = "1" ]; then + uci set wireless.radio0.disabled="0" + uci commit wireless + wifi up & + else + uci set wireless.radio0.disabled="1" + uci commit wireless + wifi down & + fi + ;; + update) + [ -n "$new_ssid" ] && uci set wireless.default_radio0.ssid="$new_ssid" + [ -n "$new_key" ] && [ ${#new_key} -ge 8 ] && uci set wireless.default_radio0.key="$new_key" + uci commit wireless + wifi reload & + ;; + esac +fi + +disabled=$(uci get wireless.radio0.disabled 2>/dev/null || echo 0) +ssid=$(uci get wireless.default_radio0.ssid 2>/dev/null) +[ "$disabled" = "1" ] && wstatus="Disabled" && wcolor="#dc2626" && toggle_lbl="Enable WiFi" \ + || wstatus="Enabled" && wcolor="#16a34a" && toggle_lbl="Disable WiFi" + +echo "Content-Type: text/html" +echo "" +cat << HTML + + + + + + WiFi Control + + + +
+

WiFi Control

+
+
+
+
Status: ${wstatus}
+
SSID: ${ssid}
+
+
+
+ + +
+
+
+ + + + + + +
+

Changes apply within ~5 seconds

+
+ + +HTML +CGISCRIPT +chmod +x /www/cgi-bin/wifi.cgi + +echo "==> Applying network config (router will be reachable at 20.0.0.26 in ~5s)..." +( sleep 3 && /etc/init.d/network reload && wifi up ) & + +echo "" +echo "==> Done!" +echo " UPS status page: http://20.0.0.26/cgi-bin/nut/upsstats.cgi" +echo " WiFi control: http://20.0.0.26/cgi-bin/wifi.cgi" +echo " CLI: upsc tripplite@localhost" +echo " SNMP test: snmpwalk -v2c -c fsr 20.0.0.26 .1.3.6.1.4.1.8072.1.3.2" diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..022a8e4 --- /dev/null +++ b/readme.md @@ -0,0 +1,116 @@ +# OpenWrt UPS SNMP + +Sets up NUT (Network UPS Tools) on a MikroTik 951Ui-2HnD running OpenWrt (20.0.0.26) +to monitor a Tripp Lite SMART1500 UPS connected via USB. + +- **View UPS status** in the LuCI web interface (Services → UPS) +- **SNMP exposure** so the UPS data is queryable from the network like the RouterOS device at 10.223.135.242 + +SNMP community: `fsr` + +--- + +## How it works + +1. **NUT `usbhid-ups` driver** talks to the Tripp Lite over USB +2. **`upsd`** serves UPS data locally on `127.0.0.1:3493` +3. **`luci-app-nut`** adds a UPS status page to the LuCI web UI +4. **`net-snmp` extend** entries call `upsc` to expose each UPS metric as an SNMP OID + +This mirrors what RouterOS does natively for its connected UPS. + +--- + +## Deploy + +```bash +./deploy.sh root@20.0.0.26 +``` + +Or manually: + +```bash +scp -r openwrt/etc/nut root@20.0.0.26:/etc/ +scp -r openwrt/etc/snmp root@20.0.0.26:/etc/ +scp openwrt/install.sh root@20.0.0.26:/tmp/ups-install.sh +ssh root@20.0.0.26 "sh /tmp/ups-install.sh" +``` + +--- + +## Viewing UPS status + +**UPS status web page (nut-web-cgi):** +``` +http://20.0.0.26/cgi-bin/nut/upsstats.cgi +``` + +**LuCI — NUT configuration only** (not live status): +http://20.0.0.26 → Services → NUT Server / NUT Monitor + +**CLI on the router:** +```bash +upsc tripplite@localhost +``` + +**SNMP from the network:** +```bash +snmpwalk -v2c -c fsr 20.0.0.26 .1.3.6.1.4.1.8072.1.3.2 +``` + +--- + +## SNMP OIDs + +UPS data is exposed under the NET-SNMP extend tree. Query with `snmpwalk` above or use +individual OIDs for polling tools (Zabbix, LibreNMS, PRTG, etc.). + +| Extend name | UPS metric | Notes | +|-------------|-----------|-------| +| `battery_charge` | battery.charge | % | +| `battery_runtime` | battery.runtime | seconds | +| `ups_load` | ups.load | % | +| `input_voltage` | input.voltage | V | +| `output_voltage` | output.voltage | V | +| `ups_status` | ups.status | `OL`=online `OB`=on battery `LB`=low battery | +| `battery_temp` | battery.temperature | °C (may not be reported by all units) | +| `battery_voltage` | battery.voltage | V | + +Each OID follows the pattern: +`.1.3.6.1.4.1.8072.1.3.2.3.1.2..` + +--- + +## Packages installed + +| Package | Purpose | +|---------|---------| +| `nut` | Core NUT tools (`upsc`, `upsmon`) | +| `nut-server` | `upsd` daemon | +| `nut-driver-usbhid-ups` | USB HID driver for Tripp Lite | +| `luci-app-nut` | UPS status page in LuCI | +| `net-snmp` | Full snmpd with extend support | + +--- + +## Troubleshooting + +**UPS not found:** +```bash +lsusb | grep -i tripp # Tripp Lite vendor ID is 09AE +dmesg | grep -i hid +``` + +**`upsc` returns "connection failure":** +```bash +upsdrvctl stop && upsdrvctl start && upsd +``` + +**SNMP returns "No Such Object" for extend OIDs:** +- Make sure `net-snmp` is installed, not `mini-snmpd` +- Confirm `/etc/snmp/snmpd.conf` has the `extend` lines +- Test the extend command directly: `upsc tripplite@localhost battery.charge` + +**SNMP not responding at all:** +- Check firewall allows UDP 161 inbound on OpenWrt +- Network → Firewall → Traffic Rules → add rule for SNMP