Initial commit
This commit is contained in:
11
.env.example
Normal file
11
.env.example
Normal 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
23
.gitignore
vendored
Normal 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
53
backup/etc/config/dhcp
Normal 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
60
backup/etc/config/network
Normal 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'
|
||||
|
||||
8
backup/etc/config/nut_cgi
Normal file
8
backup/etc/config/nut_cgi
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
config upsset
|
||||
|
||||
config host 'tripplite'
|
||||
option upsname 'tripplite'
|
||||
option hostname 'localhost'
|
||||
option displayname 'Local UPS'
|
||||
|
||||
17
backup/etc/config/nut_monitor
Normal file
17
backup/etc/config/nut_monitor
Normal 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'
|
||||
|
||||
24
backup/etc/config/nut_server
Normal file
24
backup/etc/config/nut_server
Normal 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
139
backup/etc/config/snmpd
Normal 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'
|
||||
|
||||
17
backup/etc/config/wireless
Normal file
17
backup/etc/config/wireless
Normal 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'
|
||||
|
||||
21
backup/etc/hotplug.d/usb/99-nut-ups
Normal file
21
backup/etc/hotplug.d/usb/99-nut-ups
Normal 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
8
backup/etc/rc.local
Normal 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
|
||||
BIN
backup/openwrt-backup.tar.gz
Normal file
BIN
backup/openwrt-backup.tar.gz
Normal file
Binary file not shown.
100
backup/www/cgi-bin/wifi.cgi
Executable file
100
backup/www/cgi-bin/wifi.cgi
Executable 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 & Apply</button>
|
||||
</form>
|
||||
<p class="note">Changes apply within ~5 seconds</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
9
deploy.sh
Executable file
9
deploy.sh
Executable 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
323
openwrt/install.sh
Executable 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 & 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
116
readme.md
Normal 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
|
||||
Reference in New Issue
Block a user