Initial commit

This commit is contained in:
monoadmin
2026-04-10 15:36:36 -07:00
commit 918ff903f0
16 changed files with 929 additions and 0 deletions

11
.env.example Normal file
View File

@@ -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

23
.gitignore vendored Normal file
View File

@@ -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

53
backup/etc/config/dhcp Normal file
View File

@@ -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'

60
backup/etc/config/network Normal file
View File

@@ -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'

View File

@@ -0,0 +1,8 @@
config upsset
config host 'tripplite'
option upsname 'tripplite'
option hostname 'localhost'
option displayname 'Local UPS'

View File

@@ -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'

View File

@@ -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'

139
backup/etc/config/snmpd Normal file
View File

@@ -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'

View File

@@ -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'

View File

@@ -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

8
backup/etc/rc.local Normal file
View File

@@ -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

Binary file not shown.

100
backup/www/cgi-bin/wifi.cgi Executable file
View File

@@ -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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WiFi Control</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}
.card{background:#fff;border-radius:12px;box-shadow:0 4px 6px rgba(0,0,0,.1);padding:32px;width:100%;max-width:400px}
h1{font-size:1.3rem;color:#0f172a;margin-bottom:20px}
.status{display:flex;align-items:center;gap:10px;background:#f8fafc;border-radius:8px;padding:12px 16px;margin-bottom:20px}
.dot{width:10px;height:10px;border-radius:50%;background:${wcolor};flex-shrink:0}
.slabel{font-size:.8rem;color:#64748b}
.sval{font-weight:700;color:${wcolor}}
.ssid{font-size:.85rem;color:#475569;margin-top:2px}
.btn{width:100%;padding:11px;border:none;border-radius:8px;font-size:.9rem;font-weight:600;cursor:pointer}
.btn-toggle{background:#0f172a;color:#fff;margin-bottom:20px}
.btn-toggle:hover{background:#1e293b}
.divider{border:none;border-top:1px solid #e2e8f0;margin-bottom:20px}
label{display:block;font-size:.75rem;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px}
input{width:100%;padding:9px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:.9rem;margin-bottom:14px;outline:none}
input:focus{border-color:#0284c7;box-shadow:0 0 0 2px rgba(2,132,199,.15)}
.btn-save{background:#0284c7;color:#fff}
.btn-save:hover{background:#0369a1}
.note{font-size:.75rem;color:#94a3b8;margin-top:12px;text-align:center}
</style>
</head>
<body>
<div class="card">
<h1>WiFi Control</h1>
<div class="status">
<div class="dot"></div>
<div>
<div class="slabel">Status: <span class="sval">${wstatus}</span></div>
<div class="ssid">SSID: ${ssid}</div>
</div>
</div>
<form method="POST">
<input type="hidden" name="action" value="toggle">
<button class="btn btn-toggle">${toggle_lbl}</button>
</form>
<hr class="divider">
<form method="POST">
<input type="hidden" name="action" value="update">
<label>SSID</label>
<input type="text" name="ssid" value="${ssid}" placeholder="Network name">
<label>Password <span style="font-weight:400;text-transform:none">(min 8 chars — blank = no change)</span></label>
<input type="password" name="key" placeholder="••••••••">
<button class="btn btn-save">Save &amp; Apply</button>
</form>
<p class="note">Changes apply within ~5 seconds</p>
</div>
</body>
</html>
HTML

9
deploy.sh Executable file
View File

@@ -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"

323
openwrt/install.sh Executable file
View File

@@ -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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WiFi Control</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}
.card{background:#fff;border-radius:12px;box-shadow:0 4px 6px rgba(0,0,0,.1);padding:32px;width:100%;max-width:400px}
h1{font-size:1.3rem;color:#0f172a;margin-bottom:20px}
.status{display:flex;align-items:center;gap:10px;background:#f8fafc;border-radius:8px;padding:12px 16px;margin-bottom:20px}
.dot{width:10px;height:10px;border-radius:50%;background:${wcolor};flex-shrink:0}
.slabel{font-size:.8rem;color:#64748b}
.sval{font-weight:700;color:${wcolor}}
.ssid{font-size:.85rem;color:#475569;margin-top:2px}
.btn{width:100%;padding:11px;border:none;border-radius:8px;font-size:.9rem;font-weight:600;cursor:pointer}
.btn-toggle{background:#0f172a;color:#fff;margin-bottom:20px}
.btn-toggle:hover{background:#1e293b}
.divider{border:none;border-top:1px solid #e2e8f0;margin-bottom:20px}
label{display:block;font-size:.75rem;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px}
input{width:100%;padding:9px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:.9rem;margin-bottom:14px;outline:none}
input:focus{border-color:#0284c7;box-shadow:0 0 0 2px rgba(2,132,199,.15)}
.btn-save{background:#0284c7;color:#fff}
.btn-save:hover{background:#0369a1}
.note{font-size:.75rem;color:#94a3b8;margin-top:12px;text-align:center}
</style>
</head>
<body>
<div class="card">
<h1>WiFi Control</h1>
<div class="status">
<div class="dot"></div>
<div>
<div class="slabel">Status: <span class="sval">${wstatus}</span></div>
<div class="ssid">SSID: ${ssid}</div>
</div>
</div>
<form method="POST">
<input type="hidden" name="action" value="toggle">
<button class="btn btn-toggle">${toggle_lbl}</button>
</form>
<hr class="divider">
<form method="POST">
<input type="hidden" name="action" value="update">
<label>SSID</label>
<input type="text" name="ssid" value="${ssid}" placeholder="Network name">
<label>Password <span style="font-weight:400;text-transform:none">(min 8 chars — blank = no change)</span></label>
<input type="password" name="key" placeholder="••••••••">
<button class="btn btn-save">Save &amp; Apply</button>
</form>
<p class="note">Changes apply within ~5 seconds</p>
</div>
</body>
</html>
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"

116
readme.md Normal file
View File

@@ -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.<name-length>.<ascii-bytes-of-name>`
---
## 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