From 918ff903f0316e5bf6b51dcc93af52effb952f44 Mon Sep 17 00:00:00 2001 From: monoadmin Date: Fri, 10 Apr 2026 15:36:36 -0700 Subject: [PATCH] Initial commit --- .env.example | 11 + .gitignore | 23 ++ backup/etc/config/dhcp | 53 +++++ backup/etc/config/network | 60 ++++++ backup/etc/config/nut_cgi | 8 + backup/etc/config/nut_monitor | 17 ++ backup/etc/config/nut_server | 24 +++ backup/etc/config/snmpd | 139 ++++++++++++ backup/etc/config/wireless | 17 ++ backup/etc/hotplug.d/usb/99-nut-ups | 21 ++ backup/etc/rc.local | 8 + backup/openwrt-backup.tar.gz | Bin 0 -> 9637 bytes backup/www/cgi-bin/wifi.cgi | 100 +++++++++ deploy.sh | 9 + openwrt/install.sh | 323 ++++++++++++++++++++++++++++ readme.md | 116 ++++++++++ 16 files changed, 929 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 backup/etc/config/dhcp create mode 100644 backup/etc/config/network create mode 100644 backup/etc/config/nut_cgi create mode 100644 backup/etc/config/nut_monitor create mode 100644 backup/etc/config/nut_server create mode 100644 backup/etc/config/snmpd create mode 100644 backup/etc/config/wireless create mode 100644 backup/etc/hotplug.d/usb/99-nut-ups create mode 100644 backup/etc/rc.local create mode 100644 backup/openwrt-backup.tar.gz create mode 100755 backup/www/cgi-bin/wifi.cgi create mode 100755 deploy.sh create mode 100755 openwrt/install.sh create mode 100644 readme.md 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 0000000000000000000000000000000000000000..870c8745015c5f075dbb5491cd2e50662c21908b GIT binary patch literal 9637 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 b>XDF(r@G-5SYUw#Uvc;!bDYg(0OkMyHDWYW literal 0 HcmV?d00001 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