324 lines
12 KiB
Bash
Executable File
324 lines
12 KiB
Bash
Executable File
#!/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"
|