💋 Sync 2026-10-04 04:16:09

This commit is contained in:
github-actions[bot] committed 2026-10-04 04:16:09 +08:00
1 parent f6b2df73ec
commit f62992fd80
745 files changed
+212416 -1173

No files matched your search

@@ -0,0 +1,52 @@
package quickactions
config settings 'global'
option poll_interval '5'
option design_style '3d'
config mapping
option keyword 'reboot'
option service 'system'
option dangerous '1'
option icon '⚠️'
config essential 'reboot'
option label 'Reboot Router'
option icon '🔄'
option command '/sbin/reboot'
option confirm '1'
config essential 'shutdown'
option label 'Shutdown'
option icon '⏻'
option command '/sbin/poweroff'
option confirm '1'
config essential 'wifi_toggle'
option label 'WiFi Off/On'
option icon '📶'
option command '/usr/bin/quickactions-wifi-toggle'
option confirm '1'
config essential 'cache_clear'
option label 'Clear LuCI Cache'
option icon '🧹'
option command 'rm -rf /tmp/luci-indexcache /tmp/luci-modulecache*; /etc/init.d/rpcd restart; /etc/init.d/uhttpd restart'
option confirm '0'
config shortcut 'openvpn'
option label 'OpenVPN'
option icon '🔒'
option path 'admin/services/openvpn'
config shortcut 'firewall'
option label 'Firewall'
option icon '🛡️'
option path 'admin/network/firewall'
config essential 'backup'
option order '14'
option label 'Download Config Backup'
option icon '💾'
option command '/sbin/sysupgrade -b /tmp/backup-$(date +%Y%m%d-%H%M%S).tar.gz && ls -la /tmp/backup-*.tar.gz | tail -1'
option confirm '0'
@@ -0,0 +1,3 @@
config global 'global'
option customscript ' '
option customscript2 ' '
@@ -0,0 +1 @@
config wizard 'default'
@@ -0,0 +1,25 @@
#!/bin/sh
# Flush conntrack when a watched WAN interface goes up or down.
# Watched list = primary + backups from /etc/config/quickactions failover.
[ "$ACTION" = "ifup" ] || [ "$ACTION" = "ifdown" ] || exit 0
. /lib/functions.sh
config_load quickactions
config_get WATCH failover primary ""
config_get BACKUPS failover backups ""
IFACE="$INTERFACE"
for i in $WATCH $BACKUPS; do
if [ "$i" = "$IFACE" ]; then
if [ -e /proc/net/nf_conntrack ]; then
echo f > /proc/net/nf_conntrack
elif [ -e /proc/net/ip_conntrack ]; then
echo f > /proc/net/ip_conntrack
fi
logger -t quickactions-hotplug "IFACE $IFACE $ACTION; conntrack flushed"
break
fi
done
exit 0
@@ -0,0 +1,30 @@
#!/bin/sh /etc/rc.common
START=95
STOP=05
USE_PROCD=0
HP=/etc/hotplug.d/iface/99-quickactions-conntrack-flush
CRON_LINE="* * * * * /usr/bin/wan-watchdog.sh"
start() {
en=$(uci -q get quickactions.failover.enabled)
[ -z "$en" ] && en=1
if [ "$en" = "1" ]; then
[ -f "$HP" ] && chmod 755 "$HP"
(crontab -l 2>/dev/null | grep -v "wan-watchdog.sh"; echo "$CRON_LINE") | crontab -
else
[ -f "$HP" ] && chmod 644 "$HP"
crontab -l 2>/dev/null | grep -v "wan-watchdog.sh" | crontab -
fi
/etc/init.d/cron restart
}
stop() {
crontab -l 2>/dev/null | grep -v "wan-watchdog.sh" | crontab -
/etc/init.d/cron restart
}
restart() {
stop
start
}
+67
View File
@@ -0,0 +1,67 @@
#!/bin/sh /etc/rc.common
START=90
STOP=10
# Unique path prefix so this coexists with the original hotplug-generator
IFACE_PATH="/etc/hotplug.d/iface/91-qa-hp"
add_iface_rule() {
local enabled action command description interface target
config_get enabled "$1" enabled
[ "$enabled" = "0" ] && return
config_get action "$1" action
config_get description "$1" description
config_get interface "$1" interface
[ -z "$action" ] && return
[ -z "$interface" ] && return
target="$IFACE_PATH-$1"
{
echo "#!/bin/sh"
echo "# Generated by luci-app-quickactions - do not edit directly"
echo "# Source: /etc/config/hotplug section [$1]"
description=$(echo $description | tr '\n' ' ')
echo "# Description: $description"
echo ""
echo "if [ \"\$ACTION\" = \"$action\" ]; then"
echo " if [ \"\$INTERFACE\" = \"$interface\" ]; then"
} > "$target"
command_list=$(uci -q get "hotplug.$1.command")
[ "${command_list:0:1}" = "'" ] && command_list="${command_list:1}"
[ "${command_list: -1}" = "'" ] && command_list="${command_list%?}"
i=0
while true ; do
i=$((i+1))
command=$(echo "$command_list" | awk -v i=$i -F "' '" '{print $i}')
[ -z "$command" ] && break
echo " $command" >> "$target"
done
{
echo " fi"
echo "fi"
echo "exit 0"
} >> "$target"
chmod 755 "$target"
}
start() {
mkdir -p /etc/hotplug.d/iface
rm -f "${IFACE_PATH}-"* 2>/dev/null
config_load hotplug
config_foreach add_iface_rule iface
logger -t quickactions-hotplug "Handlers regenerated from /etc/config/hotplug"
}
stop() {
rm -f "${IFACE_PATH}-"* 2>/dev/null
logger -t quickactions-hotplug "Handlers removed"
}
restart() {
stop
start
}
@@ -0,0 +1,145 @@
#!/bin/sh /etc/rc.common
START=99
STOP=10
USE_PROCD=1
SHORTCUT_APPLIER=/usr/libexec/quickactions-netwizard/apply-shortcuts.sh
clean_shortcuts() {
local sec
for sec in $(uci -q show nginx 2>/dev/null | sed -n "s/^nginx\.\(_sc_[^=.]*\)=server$/\1/p"); do
uci -q delete nginx."$sec"
done
for sec in $(uci -q show dhcp 2>/dev/null | sed -n "s/^dhcp\.\(_sc_[^=.]*\)=domain$/\1/p"); do
uci -q delete dhcp."$sec"
done
}
process_shortcut() {
local shortcut to_url lanaddr cfg="$1"
lanaddr="$(uci -q get network.lan.ipaddr | tr ' ' '\n' | head -n1)"
lanaddr="${lanaddr%%/*}"
[ -z "$lanaddr" ] && lanaddr="10.0.0.1"
config_get shortcut "$cfg" shortcut
config_get to_url "$cfg" to_url
[ -z "$shortcut" ] || [ -z "$to_url" ] && return 0
if [ -f /etc/nginx/uci.conf ]; then
SHORTCUTS_TOUCHED=1
uci -q set nginx._sc_$shortcut=server
uci -q set nginx._sc_$shortcut.server_name="$shortcut"
uci -q add_list nginx._sc_$shortcut.listen="80"
uci -q set nginx._sc_$shortcut.return="302 $to_url"
uci -q set dhcp._sc_$shortcut=domain
uci -q set dhcp._sc_$shortcut.name="$shortcut"
uci -q set dhcp._sc_$shortcut.ip="$lanaddr"
fi
}
sethttps() {
local cfg="${1:-default}" https
config_get https "$cfg" https 0
# Safety: only touch redirect_https if wizard.default.https is explicitly set
local explicit
explicit=$(uci -q get wizard.default.https)
if [ -z "$explicit" ]; then
return 0
fi
if [ -x /etc/init.d/nginx ] && [ -f /etc/nginx/uci.conf ]; then
if [ "x${https}" = "x1" ]; then
[ -z "$(uci -q get nginx._redirect2ssl)" ] && {
uci -q set nginx._redirect2ssl=server
uci -q set nginx._redirect2ssl.server_name='_redirect2ssl'
uci -q set nginx._redirect2ssl.return='302 https://$host$request_uri'
uci -q set nginx._redirect2ssl.access_log='off;'
}
uci -q del nginx.default_server.listen
uci -q add_list nginx.default_server.listen='80'
uci -q add_list nginx.default_server.listen='[::]:80'
uci -q del nginx._redirect2ssl.listen
uci -q add_list nginx._redirect2ssl.listen='80 default_server'
uci -q add_list nginx._redirect2ssl.listen='[::]:80 default_server'
else
uci -q delete nginx._redirect2ssl
uci -q del nginx.default_server.listen
uci -q add_list nginx.default_server.listen='80 default_server'
uci -q add_list nginx.default_server.listen='[::]:80 default_server'
fi
uci -q commit nginx
/etc/init.d/nginx reload >/dev/null 2>&1
elif [ -x /etc/init.d/lighttpd ] && [ -f /etc/lighttpd/conf.d/82-https.conf ]; then
if [ "x${https}" = "x1" ]; then
sed -i "s/^[ \t]*#*[ \t]*url\.redirect[ \t]*=.*/\turl.redirect =/" /etc/lighttpd/conf.d/82-https.conf 2>/dev/null
else
sed -i "s/^[ \t]*url\.redirect[ \t]*=.*/\t# url.redirect =/" /etc/lighttpd/conf.d/82-https.conf 2>/dev/null
fi
/etc/init.d/lighttpd reload >/dev/null 2>&1
elif [ -x /etc/init.d/uhttpd ]; then
if [ "x${https}" = "x1" ]; then
uci -q set uhttpd.main.redirect_https='1'
else
uci -q set uhttpd.main.redirect_https='0'
fi
uci -q commit uhttpd
/etc/init.d/uhttpd reload >/dev/null 2>&1
fi
}
reload_services() {
# Only commit if we actually wrote to dhcp/nginx (shortcuts changed)
if [ "$SHORTCUTS_TOUCHED" = "1" ]; then
uci -q commit dhcp
[ -x /etc/init.d/dnsmasq ] && /etc/init.d/dnsmasq reload >/dev/null 2>&1
if [ -f /etc/nginx/uci.conf ] && [ -x /etc/init.d/nginx ]; then
uci -q commit nginx
/etc/init.d/nginx reload >/dev/null 2>&1
fi
fi
}
SHORTCUTS_TOUCHED=0
start_service() {
config_load wizard
# Cookie persist → luci.sgi (only if wizard.default.persistent_cookies is explicitly set)
local explicit_pc
explicit_pc=$(uci -q get wizard.default.persistent_cookies)
if [ -n "$explicit_pc" ]; then
[ -z "$(uci -q get luci.sgi)" ] && uci -q set luci.sgi=sgi
uci -q set luci.sgi.persistent_cookies="$explicit_pc"
uci -q commit luci
fi
# Shortcuts (nginx + dnsmasq)
clean_shortcuts
config_foreach process_shortcut shortcuts
# HTTPS redirect
sethttps default
reload_services
}
stop_service() {
clean_shortcuts
reload_services
}
service_triggers() {
procd_add_reload_trigger "wizard"
}
boot() {
start "$@"
# Background coremark if enabled and not yet run
local cm
cm=$(uci -q get wizard.@wizard[0].coremark)
[ "$cm" = "1" ] || cm=$(uci -q get wizard.default.coremark)
if [ "$cm" = "1" ] && [ ! -f /etc/bench.log ]; then
(sleep 5; /sbin/coremark >/dev/null 2>&1) &
fi
}
+135
View File
@@ -0,0 +1,135 @@
#!/bin/sh /etc/rc.common
# Merged from luci-app-taskplan — full independence edition
# Author: sirpdboy (original) / quickactions merge
START=99
STOP=10
TMP=/etc/quickactions-taskplan
LOG=$TMP/taskplan.log
TT=$TMP/taskplanrun
CR=/etc/crontabs/root
HANDLER=/usr/bin/quickactions-taskplanhandler
[ ! -d $TMP ] && mkdir -p $TMP 2>/dev/null
[ ! -s $LOG ] && echo '' > $LOG
run_taskplan()
{
[ -n "$(uci -q get taskplan.@global[0].customscript)" ] && \
uci -q get taskplan.@global[0].customscript > $TMP/taskplancustomscript && \
sed -i 's/\r$//' $TMP/taskplancustomscript
[ -n "$(uci -q get taskplan.@global[0].customscript2)" ] && \
uci -q get taskplan.@global[0].customscript2 > $TMP/taskplancustomscript2 && \
sed -i 's/\r$//' $TMP/taskplancustomscript2
ssum=$(uci -q show taskplan | grep -c "stime\[[0-9]\]" || echo 0)
lsum=$(uci -q show taskplan | grep -c "ltime\[[0-9]\]" || echo 0)
sed -i '/quickactions-taskplanhandler/d' $CR >/dev/null 2>&1
for i in $(seq 0 $((ssum-1)) 2>/dev/null); do
enable=$(uci -q get taskplan.@stime[$i].enable)
if [ "x$enable" = "x1" ]; then
month=$(uci -q get taskplan.@stime[$i].month || echo "*")
stype=$(uci -q get taskplan.@stime[$i].stype)
week=$(uci -q get taskplan.@stime[$i].week || echo "*")
minute=$(uci -q get taskplan.@stime[$i].minute || echo "0")
hour=$(uci -q get taskplan.@stime[$i].hour || echo "*")
[ "$hour" = "0" ] && hour="0"
[ "$minute" = "0" ] && minute="0"
case "$stype" in
1) cmd="$minute $hour * $month $week $HANDLER reboot Scheduled_task" ;;
2) cmd="$minute $hour * $month $week $HANDLER poweroff Scheduled_task" ;;
3) cmd="$minute $hour * $month $week $HANDLER network Scheduled_task" ;;
4) cmd="$minute $hour * $month $week $HANDLER restartsamba Scheduled_task" ;;
5) cmd="$minute $hour * $month $week $HANDLER restartwan Scheduled_task" ;;
6) cmd="$minute $hour * $month $week $HANDLER closewan Scheduled_task" ;;
7) cmd="$minute $hour * $month $week $HANDLER clearmem Scheduled_task" ;;
8) cmd="$minute $hour * $month $week $HANDLER sysfree Scheduled_task" ;;
9) cmd="$minute $hour * $month $week $HANDLER disreconn Scheduled_task" ;;
10) cmd="$minute $hour * $month $week $HANDLER disrereboot Scheduled_task" ;;
11) cmd="$minute $hour * $month $week $HANDLER restartmwan3 Scheduled_task" ;;
12) cmd="$minute $hour * $month $week $HANDLER customscript Scheduled_task" ;;
13) cmd="$minute $hour * $month $week $HANDLER upwifi Scheduled_task" ;;
14) cmd="$minute $hour * $month $week $HANDLER downwifi Scheduled_task" ;;
15) cmd="$minute $hour * $month $week $HANDLER customscript2 Scheduled_task" ;;
16) cmd="$minute $hour * $month $week $HANDLER restartlan Scheduled_task" ;;
esac
echo "$cmd" >> $CR
fi
done
echo "#!/bin/sh" > $TT
chmod +x $TT
for i in $(seq 0 $((lsum-1)) 2>/dev/null); do
enable=$(uci -q get taskplan.@ltime[$i].enable)
if [ "x$enable" = "x1" ]; then
stype=$(uci -q get taskplan.@ltime[$i].stype)
delay=$(uci -q get taskplan.@ltime[$i].delay || echo 10)
case "$stype" in
1) echo "(sleep $delay && $HANDLER reboot Startup_task) &" >>$TT ;;
2) echo "(sleep $delay && $HANDLER poweroff Startup_task) &" >>$TT ;;
3) echo "(sleep $delay && $HANDLER network Startup_task) &" >>$TT ;;
4) echo "(sleep $delay && $HANDLER restartsamba Startup_task) &" >>$TT ;;
5) echo "(sleep $delay && $HANDLER restartwan Startup_task) &" >>$TT ;;
6) echo "(sleep $delay && $HANDLER closewan Startup_task) &" >>$TT ;;
7) echo "(sleep $delay && $HANDLER clearmem Startup_task) &" >>$TT ;;
8) echo "(sleep $delay && $HANDLER sysfree Startup_task) &" >>$TT ;;
9) echo "(sleep $delay && $HANDLER disreconn Startup_task) &" >>$TT ;;
10) echo "(sleep $delay && $HANDLER disrereboot Startup_task) &" >>$TT ;;
11) echo "(sleep $delay && $HANDLER restartmwan3 Startup_task) &" >>$TT ;;
12) echo "(sleep $delay && $HANDLER customscript Startup_task) &" >>$TT ;;
13) echo "(sleep $delay && $HANDLER upwifi Startup_task) &" >>$TT ;;
14) echo "(sleep $delay && $HANDLER downwifi Startup_task) &" >>$TT ;;
15) echo "(sleep $delay && $HANDLER customscript2 Startup_task) &" >>$TT ;;
16) echo "(sleep $delay && $HANDLER restartlan Startup_task) &" >>$TT ;;
esac
fi
done
echo 'exit 0' >> $TT
}
start()
{
del_cru
run_taskplan
/etc/init.d/cron reload
echo "$(date): quickactions-taskplan started" >> $LOG
}
boot()
{
del_cru
run_taskplan
/etc/init.d/cron reload
if [ -f $TT ] && [ -s $TT ]; then
echo "$(date +'%Y-%m-%d %H:%M:%S'): Executing taskplanrun at boot" >> $LOG
if command -v bash >/dev/null 2>&1; then bash $TT >> $LOG 2>&1 & else sh $TT >> $LOG 2>&1 & fi
fi
}
stop()
{
del_cru
/etc/init.d/cron reload
}
restart()
{
stop
sleep 2
start
}
del_cru()
{
sed -i '/quickactions-taskplanhandler/d' $CR >/dev/null 2>&1 || true
echo "#!/bin/sh" > $TT
chmod +x $TT
}
@@ -0,0 +1,34 @@
#!/bin/sh
# Seed failover config on first install + append our scripts to sysupgrade.conf.
# ---- 1. Failover config ----
have=0
[ -f /etc/config/quickactions ] && have=$(uci -q get quickactions.failover.primary)
if [ -z "$have" ]; then
uci -q set quickactions.failover=failover
uci -q set quickactions.failover.enabled=1
uci -q set quickactions.failover.primary=wan
uci -q set quickactions.failover.backups="wanb wanbpppoe usbwan wandhcp"
uci -q set quickactions.failover.check_ips="1.1.1.1 8.8.8.8"
uci -q set quickactions.failover.check_interval=60
uci -q set quickactions.failover.ping_timeout=5
uci -q set quickactions.failover.fail_threshold=2
uci -q set quickactions.failover.recover_threshold=1
uci -q set quickactions.failover.tailscale_restart=0
uci -q set quickactions.failover.firewall_reset=1
uci -q set quickactions.failover.log_healthy=0
uci -q commit quickactions
fi
# ---- 2. Persist scripts through sysupgrade ----
for f in /usr/bin/wan-watchdog.sh \
/usr/bin/quickactions-failover \
/etc/hotplug.d/iface/99-quickactions-conntrack-flush
do
grep -qxF "$f" /etc/sysupgrade.conf 2>/dev/null || echo "$f" >> /etc/sysupgrade.conf
done
# ---- 3. Activate the failover subsystem ----
/etc/init.d/quickactions-failover start >/dev/null 2>&1
exit 0
@@ -0,0 +1,9 @@
#!/bin/sh
# Seed /etc/config/guestwifi on first install only.
# Never overwrites an existing config → survives upgrades cleanly.
[ -f /etc/config/guestwifi ] && exit 0
cat > /etc/config/guestwifi << 'CONF'
# Guest WiFi configuration managed by luci-app-quickactions
CONF
exit 0
@@ -0,0 +1,16 @@
#!/bin/sh
# Seed /etc/config/hotplug if the user has no rules yet.
# The Hotplug tab in luci-app-quickactions writes to this config.
[ -f /etc/config/hotplug ] && exit 0
cat > /etc/config/hotplug << 'CONF'
# Hotplug rules managed by luci-app-quickactions
#
# iface sections - run commands on network interface events.
# Example:
# config iface 'wan_up'
# option action 'ifup'
# option enabled '1'
# option interface 'wan'
# list command '/etc/init.d/firewall restart'
CONF
exit 0
@@ -0,0 +1,13 @@
#!/bin/sh
# Create seed configs only if missing. Do NOT touch network, firewall, dhcp, wireless.
[ -f /etc/config/wizard ] || echo "config wizard 'default'" > /etc/config/wizard
[ -f /etc/config/netwizard ] || printf "config netwizard 'default'\n\toption lan_netmask '255.255.255.0'\n" > /etc/config/netwizard
# Ensure scripts are executable
chmod +x /usr/libexec/quickactions-netwizard/*.sh 2>/dev/null
# Enable the init so shortcuts/https/coremark hooks work
/etc/init.d/quickactions-netwizard enable 2>/dev/null
/etc/init.d/quickactions-netwizard start 2>/dev/null
exit 0
@@ -0,0 +1,4 @@
#!/bin/sh
mkdir -p /etc/quickactions/scripts
chmod 755 /etc/quickactions/scripts
exit 0
@@ -0,0 +1,14 @@
#!/bin/sh
[ -f /etc/config/taskplan ] || cat > /etc/config/taskplan << 'EOF'
config global 'global'
option customscript ' '
option customscript2 ' '
EOF
mkdir -p /etc/quickactions-taskplan
touch /etc/quickactions-taskplan/taskplan.log
chmod 600 /etc/quickactions-taskplan/taskplan.log
chmod +x /usr/bin/quickactions-taskplanhandler
chmod +x /etc/init.d/quickactions-taskplan
chmod +x /usr/libexec/rpcd/luci.quickactions-taskplan
exit 0
@@ -0,0 +1,33 @@
#!/bin/sh
# Seed /etc/config/taskplan with one sample stime + ltime entry
# so the Task Plan tab is not empty on first install.
[ -f /etc/config/taskplan ] || touch /etc/config/taskplan
# Only seed if there are no stime/ltime sections yet
if ! uci -q show taskplan | grep -q "taskplan.@stime\|taskplan.@ltime"; then
# Ensure global exists
uci -q get taskplan.@global[0] >/dev/null 2>&1 || \
uci set taskplan.@global[0]=global
# Sample scheduled task: daily reboot at 04:00
sec=$(uci add taskplan stime)
uci set taskplan.$sec.remarks='Daily Reboot'
uci set taskplan.$sec.enable='0'
uci set taskplan.$sec.stype='1'
uci set taskplan.$sec.month='*'
uci set taskplan.$sec.week='*'
uci set taskplan.$sec.hour='4'
uci set taskplan.$sec.minute='0'
# Sample startup task: clear memory 30s after boot
sec=$(uci add taskplan ltime)
uci set taskplan.$sec.remarks='Startup: Clear Memory'
uci set taskplan.$sec.enable='0'
uci set taskplan.$sec.stype='7'
uci set taskplan.$sec.delay='30'
uci commit taskplan
fi
exit 0
@@ -0,0 +1,15 @@
#!/bin/sh
# Seed the VLAN working directories on first install.
mkdir -p /etc/quickactions/vlan/templates
mkdir -p /etc/quickactions/vlan/generated
chmod 755 /etc/quickactions/vlan /etc/quickactions/vlan/templates /etc/quickactions/vlan/generated
# Default config section
have=$(uci -q get quickactions.vlan.tier)
if [ -z "$have" ]; then
uci -q set quickactions.vlan=vlan
uci -q set quickactions.vlan.tier=auto
uci -q set quickactions.vlan.snapshot_keep=20
uci -q commit quickactions
fi
exit 0
+45
View File
@@ -0,0 +1,45 @@
#!/bin/sh
# Master switch for the quickactions failover system.
# quickactions-failover on|off|status|test
LOCK=/tmp/quickactions_wan_down.lock
FAILS=/tmp/quickactions_wan_fails.count
OKS=/tmp/quickactions_wan_oks.count
HP=/etc/hotplug.d/iface/99-quickactions-conntrack-flush
CRON_LINE="* * * * * /usr/bin/wan-watchdog.sh"
case "$1" in
on)
uci -q set quickactions.failover.enabled=1
uci -q commit quickactions
[ -f "$HP" ] && chmod 755 "$HP"
(crontab -l 2>/dev/null | grep -v "wan-watchdog.sh"; echo "$CRON_LINE") | crontab -
/etc/init.d/cron restart
echo "ENABLED"
logger -t quickactions-failover "master switch: enabled"
;;
off)
uci -q set quickactions.failover.enabled=0
uci -q commit quickactions
[ -f "$HP" ] && chmod 644 "$HP"
crontab -l 2>/dev/null | grep -v "wan-watchdog.sh" | crontab -
rm -f "$LOCK" "$FAILS" "$OKS"
/etc/init.d/cron restart
echo "DISABLED"
logger -t quickactions-failover "master switch: disabled"
;;
status)
en=$(uci -q get quickactions.failover.enabled)
[ -z "$en" ] && en=1
[ "$en" = "1" ] && echo "STATUS: [ON]" || echo "STATUS: [OFF]"
[ -f "$LOCK" ] && echo "MODE: [BACKUP]" || echo "MODE: [PRIMARY]"
;;
test)
/usr/bin/wan-watchdog.sh
echo "manual run complete"
;;
*)
echo "Usage: quickactions-failover [on|off|status|test]"
;;
esac
exit 0
@@ -0,0 +1,126 @@
#!/bin/sh
# Merged from luci-app-taskplan — quickactions edition
# Use bash if available, else fall back to busybox sh
if command -v bash >/dev/null 2>&1; then
SHELL_BIN=/bin/bash
else
SHELL_BIN=/bin/sh
fi
# Author: sirpdboy (original) / quickactions merge
TMP_T=/var/quickactions-taskplan/taskplan.tmp
LOG=/etc/quickactions-taskplan/taskplan.log
CUSTOM_DIR=/etc/quickactions-taskplan
log(){
echo "$(date +'%Y-%m-%d %H:%M:%S') $*" >> $LOG
}
limit_log() {
local logf=$1
[ ! -f "$logf" ] && return
local sc=100
[ -n "$2" ] && sc=$2
local count=$(grep -c "" $logf 2>/dev/null || echo 0)
if [ $count -gt $sc ]; then
let count=count-$sc
sed -i "1,$count d" $logf 2>/dev/null
fi
}
remwan3() {
while true; do
status=$(/usr/sbin/mwan3 status 2>/dev/null | grep -i error)
if [ -z "$status" ]; then
log "Mwan3 is OK"
exit 0
else
log "mwan3 restarting ..."
/usr/sbin/mwan3 restart
fi
sleep 60
done
}
disreconnwan() {
i=0
PingA=114.114.114.114
PingB=223.5.5.5
PingErr=1
while [ $i -lt 5 ]; do
if ping -c 1 -W 3 $PingA >/dev/null 2>&1; then
PingErr=0
break
elif ping -c 1 -W 3 $PingB >/dev/null 2>&1; then
PingErr=0
break
else
i=$((i + 1))
sleep 10
fi
done
}
trigger() {
case "$1" in
reboot) reboot ;;
poweroff) poweroff ;;
network) /etc/init.d/network restart ;;
clearmem) sync && echo 3 > /proc/sys/vm/drop_caches ;;
sysfree)
cd /overlay
rm -rf $(ls | egrep -v '(upper|.fs_state)') 2>/dev/null
cd upper 2>/dev/null
rm -rf $(ls | egrep -v '(etc)') 2>/dev/null
cd etc 2>/dev/null
rm -rf $(ls | egrep -v '(config|ssr|bench.log|shadow)') 2>/dev/null
rm -rf /var/luci-modulecache 2>/dev/null
rm -rf /var/luci-indexcache 2>/dev/null
rm -rf /tmp/cache/* 2>/dev/null
reboot ;;
restartsamba)
/etc/init.d/ksmdb restart 2>/dev/null
/etc/init.d/samba restart 2>/dev/null
/etc/init.d/samba4 restart 2>/dev/null
log "Samba restarted" ;;
restartlan) ifdown lan && ifup lan; log "restart lan succeeded!" ;;
restartwan) ifup wan; log "restart wan succeeded!" ;;
closewan) ifdown wan ;;
disreconn)
disreconnwan
if [ $PingErr = 1 ]; then ifup wan; log "Redial succeeded!"; fi ;;
disrereboot)
disreconnwan
if [ $PingErr = 1 ]; then log "Reboot succeeded!"; reboot; fi ;;
restartmwan3) [ -x /usr/sbin/mwan3 ] && remwan3 ;;
customscript)
if [ -s $CUSTOM_DIR/taskplancustomscript ]; then
$SHELL_BIN $CUSTOM_DIR/taskplancustomscript
log "CustomScript succeeded!"
else log "CustomScript run Error!"; fi ;;
customscript2)
if [ -s $CUSTOM_DIR/taskplancustomscript2 ]; then
$SHELL_BIN $CUSTOM_DIR/taskplancustomscript2
log "CustomScript2 succeeded!"
else log "CustomScript2 run Error!"; fi ;;
upwifi)
ifconfig wlan0 up 2>/dev/null
uci set wireless.radio0.disabled=0 2>/dev/null
uci set wireless.radio1.disabled=0 2>/dev/null
uci commit wireless 2>/dev/null
wifi up ;;
downwifi)
ifconfig wlan0 down 2>/dev/null
uci set wireless.radio0.disabled=1 2>/dev/null
uci set wireless.radio1.disabled=1 2>/dev/null
uci commit wireless 2>/dev/null
wifi down ;;
esac
}
limit_log $LOG 500
PingErr=0
log "Scheduled operation $2: $1" && trigger $1
@@ -0,0 +1,12 @@
#!/bin/sh
# Quick Actions — toggle all WiFi radios on/off
if wifi status 2>/dev/null | grep -qE '"up":[[:space:]]*true'; then
wifi down
logger -t quickactions "WiFi turned OFF"
echo "WiFi turned OFF"
else
wifi up
logger -t quickactions "WiFi turned ON"
echo "WiFi turned ON"
fi
exit 0
+115
View File
@@ -0,0 +1,115 @@
#!/bin/sh
# quickactions WAN failover watchdog.
# Runs from cron every N seconds. Reads config from
# /etc/config/quickactions section 'failover'. Zero background RAM when idle.
. /lib/functions.sh
config_load quickactions
cfg() {
local v
config_get v failover "$1" "$2"
echo "$v"
}
ENABLED=$(cfg enabled 1)
[ "$ENABLED" = "0" ] && exit 0
PRIMARY=$(cfg primary "")
[ -z "$PRIMARY" ] && exit 0
CHECK_IPS=$(cfg check_ips "1.1.1.1 8.8.8.8")
PING_TIMEOUT=$(cfg ping_timeout 5)
FAIL_AT=$(cfg fail_threshold 2)
REC_AT=$(cfg recover_threshold 1)
TAILSCALE_RESTART=$(cfg tailscale_restart 0)
FIREWALL_RESET=$(cfg firewall_reset 1)
LOG_HEALTHY=$(cfg log_healthy 0)
LOCK=/tmp/quickactions_wan_down.lock
FAILS=/tmp/quickactions_wan_fails.count
OKS=/tmp/quickactions_wan_oks.count
# Logical interface name -> kernel device name
phys_iface() {
local log="$1" dev
# pppoe-* style kernel interface
if [ -e "/sys/class/net/pppoe-$log" ]; then echo "pppoe-$log"; return; fi
# interface with pppoe in the name
case "$log" in
*pppoe*) [ -e "/sys/class/net/$log" ] && { echo "$log"; return; } ;;
esac
# read device from UCI network
dev=$(uci -q get network.$log.device)
[ -n "$dev" ] && [ -e "/sys/class/net/$dev" ] && { echo "$dev"; return; }
# fallback
echo "$log"
}
ping_ok() {
local iface="$1" ip
for ip in $CHECK_IPS; do
ping -I "$iface" -c 1 -W "$PING_TIMEOUT" "$ip" >/dev/null 2>&1 && return 0
done
return 1
}
flush_ct() {
if [ -e /proc/net/nf_conntrack ]; then
echo f > /proc/net/nf_conntrack
elif [ -e /proc/net/ip_conntrack ]; then
echo f > /proc/net/ip_conntrack
fi
}
PRIMARY_DEV=$(phys_iface "$PRIMARY")
# Recovery mode: we believe primary is down; check if it's back
if [ -f "$LOCK" ]; then
OKS=0
[ -f "$OKS" ] && OKS=$(cat "$OKS" 2>/dev/null)
if ping_ok "$PRIMARY_DEV"; then
OKS=$((OKS + 1))
if [ "$OKS" -ge "$REC_AT" ]; then
rm -f "$LOCK" "$FAILS" "$OKS"
flush_ct
logger -t quickactions-failover "PRIMARY $PRIMARY recovered; conntrack flushed"
[ "$TAILSCALE_RESTART" = "1" ] && /etc/init.d/tailscale restart 2>/dev/null
else
echo "$OKS" > "$OKS"
fi
else
echo 0 > "$OKS"
fi
exit 0
fi
# Normal mode: is primary healthy?
if ping_ok "$PRIMARY_DEV"; then
rm -f "$FAILS"
[ "$LOG_HEALTHY" = "1" ] && logger -t quickactions-failover "PRIMARY $PRIMARY HEALTHY"
exit 0
fi
# Primary failed once
FAILS=0
[ -f "$FAILS" ] && FAILS=$(cat "$FAILS" 2>/dev/null)
FAILS=$((FAILS + 1))
echo "$FAILS" > "$FAILS"
if [ "$FAILS" -ge "$FAIL_AT" ]; then
touch "$LOCK"
rm -f "$FAILS"
flush_ct
logger -t quickactions-failover "PRIMARY $PRIMARY DOWN after $FAIL_AT failures; failover engaged"
if [ "$FIREWALL_RESET" = "1" ]; then
/etc/init.d/firewall restart 2>/dev/null
fi
if [ "$TAILSCALE_RESTART" = "1" ]; then
sleep 30
/etc/init.d/tailscale restart 2>/dev/null
fi
fi
exit 0
@@ -0,0 +1,77 @@
#!/bin/sh
# Aggressive rebuild — matches source netwizard behaviour. Called only if user opts in.
MODE="$1"; LAN_IP="$2"; PPPOE_USER="$3"; PPPOE_PASS="$4"; WAN_IFACE="$5"
[ -z "$MODE" ] && MODE="dhcp"
[ -z "$LAN_IP" ] && LAN_IP="192.168.10.1"
BACKUP_DIR="/etc/config.bak.$(date +%s)"
mkdir -p "$BACKUP_DIR"
for f in network firewall dhcp wireless system wizard netwizard; do
[ -f /etc/config/$f ] && cp /etc/config/$f "$BACKUP_DIR/"
done
echo "Backup saved to $BACKUP_DIR"
if [ -z "$WAN_IFACE" ]; then
WAN_IFACE=$(uci -q get network.wan.device)
[ -z "$WAN_IFACE" ] && WAN_IFACE=$(ls /sys/class/net/ 2>/dev/null | grep -E '^(eth[01]|wan)' | head -n1)
[ -z "$WAN_IFACE" ] && WAN_IFACE="eth1"
fi
uci -q delete network.wan 2>/dev/null
uci -q delete network.wan6 2>/dev/null
uci -q delete network.lan6 2>/dev/null
WAN_IDX=$(uci show firewall | sed -n "s/^firewall\.@zone\[\([0-9]*\)\]\.name='wan'$/\1/p")
[ -n "$WAN_IDX" ] && uci -q delete firewall.@zone[$WAN_IDX] 2>/dev/null
if [ "$MODE" = "siderouter" ]; then
LAN_IDX=$(uci show firewall | sed -n "s/^firewall\.@zone\[\([0-9]*\)\]\.name='lan'$/\1/p")
[ -n "$LAN_IDX" ] && uci -q delete firewall.@zone[$LAN_IDX].masq 2>/dev/null
uci set network.lan=interface
uci set network.lan.proto='static'
uci set network.lan.ipaddr="$LAN_IP"
uci set network.lan.netmask='255.255.255.0'
else
uci set network.wan=interface
uci set network.wan.device="$WAN_IFACE"
if [ "$MODE" = "pppoe" ]; then
uci set network.wan.proto='pppoe'
[ -n "$PPPOE_USER" ] && uci set network.wan.username="$PPPOE_USER"
[ -n "$PPPOE_PASS" ] && uci set network.wan.password="$PPPOE_PASS"
else
uci set network.wan.proto='dhcp'
fi
uci set network.wan6=interface
uci set network.wan6.device="@wan"
uci set network.wan6.proto='dhcpv6'
uci add firewall zone >/dev/null
uci set firewall.@zone[-1].name='wan'
uci set firewall.@zone[-1].input='REJECT'
uci set firewall.@zone[-1].output='ACCEPT'
uci set firewall.@zone[-1].forward='REJECT'
uci set firewall.@zone[-1].masq='1'
uci set firewall.@zone[-1].mtu_fix='1'
uci add_list firewall.@zone[-1].network='wan'
uci add_list firewall.@zone[-1].network='wan6'
uci add firewall forwarding >/dev/null
uci set firewall.@forwarding[-1].src='lan'
uci set firewall.@forwarding[-1].dest='wan'
uci set network.lan.ipaddr="$LAN_IP"
uci set network.lan.netmask='255.255.255.0'
fi
uci -q commit network
uci -q commit firewall
uci -q commit dhcp
/etc/init.d/network restart >/dev/null 2>&1 &
sleep 2
/etc/init.d/firewall reload >/dev/null 2>&1 &
/etc/init.d/dnsmasq reload >/dev/null 2>&1 &
echo "Aggressive apply complete. Mode: $MODE, WAN: $WAN_IFACE, LAN: $LAN_IP"
echo "Backup: $BACKUP_DIR"
@@ -0,0 +1,45 @@
#!/bin/sh
# Regenerate nginx shortcut server blocks + dnsmasq domain entries from /etc/config/wizard shortcuts.
# Called after the user adds/removes a shortcut in the UI.
clean() {
local sec
for sec in $(uci -q show nginx 2>/dev/null | sed -n "s/^nginx\.\(_sc_[^=.]*\)=server$/\1/p"); do
uci -q delete nginx."$sec"
done
for sec in $(uci -q show dhcp 2>/dev/null | sed -n "s/^dhcp\.\(_sc_[^=.]*\)=domain$/\1/p"); do
uci -q delete dhcp."$sec"
done
}
process_one() {
local shortcut to_url lanaddr cfg="$1"
lanaddr="$(uci -q get network.lan.ipaddr | tr ' ' '\n' | head -n1)"
lanaddr="${lanaddr%%/*}"
[ -z "$lanaddr" ] && lanaddr="10.0.0.1"
config_get shortcut "$cfg" shortcut
config_get to_url "$cfg" to_url
[ -z "$shortcut" ] || [ -z "$to_url" ] && return 0
if [ -f /etc/nginx/uci.conf ]; then
uci -q set nginx._sc_$shortcut=server
uci -q set nginx._sc_$shortcut.server_name="$shortcut"
uci -q add_list nginx._sc_$shortcut.listen="80"
uci -q set nginx._sc_$shortcut.return="302 $to_url"
uci -q set dhcp._sc_$shortcut=domain
uci -q set dhcp._sc_$shortcut.name="$shortcut"
uci -q set dhcp._sc_$shortcut.ip="$lanaddr"
fi
}
clean
config_load wizard
config_foreach process_one shortcuts
uci -q commit nginx 2>/dev/null
uci -q commit dhcp 2>/dev/null
[ -x /etc/init.d/nginx ] && /etc/init.d/nginx reload >/dev/null 2>&1
[ -x /etc/init.d/dnsmasq ] && /etc/init.d/dnsmasq reload >/dev/null 2>&1
echo "Shortcuts applied."
@@ -0,0 +1,12 @@
#!/bin/sh
BACKUP_DIR="/etc/config.bak.$(date +%s)"
mkdir -p "$BACKUP_DIR"
COUNT=0
for f in network firewall dhcp wireless system wizard netwizard taskplan; do
if [ -f /etc/config/$f ]; then
cp /etc/config/$f "$BACKUP_DIR/"
COUNT=$((COUNT + 1))
fi
done
echo "Backed up $COUNT files to $BACKUP_DIR"
ls -1 "$BACKUP_DIR"
@@ -0,0 +1,2 @@
#!/bin/sh
ls -1d /etc/config.bak.* 2>/dev/null | sort -r
@@ -0,0 +1,23 @@
#!/bin/sh
DIR="$1"
[ -z "$DIR" ] && { echo "Usage: restore.sh <backup-dir>"; exit 1; }
[ ! -d "$DIR" ] && { echo "Directory not found: $DIR"; exit 1; }
SAFETY="/etc/config.bak.pre-restore.$(date +%s)"
mkdir -p "$SAFETY"
for f in "$DIR"/*; do
[ -f "$f" ] || continue
base=$(basename "$f")
[ -f "/etc/config/$base" ] && cp "/etc/config/$base" "$SAFETY/"
cp "$f" "/etc/config/$base"
done
for c in network firewall dhcp wireless wizard netwizard taskplan; do
uci -q commit $c 2>/dev/null
done
/etc/init.d/network reload >/dev/null 2>&1 &
/etc/init.d/firewall reload >/dev/null 2>&1 &
echo "Restored from $DIR"
echo "Previous state saved to $SAFETY"
@@ -0,0 +1,82 @@
#!/bin/sh
# ubus RPC module for Quick Actions Failover
. /usr/share/libubox/jshn.sh
list_methods() {
cat <<'EOF'
{
"status": {},
"interfaces": {},
"action": { "action": "string" }
}
EOF
}
method_status() {
cat >/dev/null
local en lock
en=$(uci -q get quickactions.failover.enabled); [ -z "$en" ] && en=1
[ -f /tmp/quickactions_wan_down.lock ] && lock=1 || lock=0
json_init
json_add_int "enabled" "$en"
json_add_int "on_backup" "$lock"
json_add_string "primary" "$(uci -q get quickactions.failover.primary)"
json_add_string "backups" "$(uci -q get quickactions.failover.backups)"
json_add_string "check_ips" "$(uci -q get quickactions.failover.check_ips)"
json_add_string "interval" "$(uci -q get quickactions.failover.check_interval)"
json_add_string "last_log" "$(logread 2>/dev/null | grep -E 'quickactions-(failover|hotplug)' | tail -1)"
json_dump
}
method_interfaces() {
cat >/dev/null
local list
list=$(
{
uci show network 2>/dev/null | grep '=interface$' | cut -d. -f2 | cut -d= -f1
ls -1 /sys/class/net/ 2>/dev/null | grep -E '^(pppoe-|eth|usb)'
} | sort -u | tr '\n' ' '
)
json_init
json_add_string "interfaces" "$list"
json_dump
}
method_action() {
local payload action out
payload=$(cat)
json_load "$payload"
json_get_var action "action"
case "$action" in
on|off|status|test)
out=$(/usr/bin/quickactions-failover "$action" 2>&1)
;;
*)
out="unknown action: [$action]"
;;
esac
json_init
json_add_string "output" "$out"
json_dump
}
call_method() {
local method="$1"
case "$method" in
action) method_action ;;
status) method_status ;;
interfaces) method_interfaces ;;
*) cat >/dev/null
json_init
json_add_string "error" "unknown_method"
json_dump ;;
esac
}
case "$1" in
list) list_methods ;;
call) call_method "$2" ;;
*) echo '{"error":"usage: '$0' {list|call <method>}"}' ;;
esac
@@ -0,0 +1,338 @@
#!/bin/sh
# ubus RPC module for Quick Actions Scripts
. /usr/share/libubox/jshn.sh
SCRIPTS_DIR=/etc/quickactions/scripts
mkdir -p "$SCRIPTS_DIR"
list_methods() {
cat <<'EOF'
{
"list": {},
"read": { "sid": "string" },
"write": { "sid": "string", "name": "string", "category": "string", "content": "string", "args": "string", "timeout": "string", "enabled": "string" },
"delete": { "sid": "string" },
"run": { "sid": "string" },
"chmod": { "sid": "string", "mode": "string" },
"install_preset": { "preset": "string" },
"detail": { "sid": "string" }
}
EOF
}
method_list() {
cat >/dev/null
json_init
json_add_array "scripts"
# No pipe into while → no subshell → json_add works
for sid in $(uci show quickactions 2>/dev/null | grep '=script$' | cut -d. -f2 | cut -d= -f1); do
name=$(uci -q get "quickactions.$sid.name")
category=$(uci -q get "quickactions.$sid.category")
path=$(uci -q get "quickactions.$sid.path")
[ -z "$path" ] && path="$SCRIPTS_DIR/$sid.sh"
enabled=$(uci -q get "quickactions.$sid.enabled")
[ -z "$enabled" ] && enabled="1"
sz=0
[ -f "$path" ] && sz=$(wc -c < "$path" 2>/dev/null)
[ -z "$sz" ] && sz=0
json_add_object
json_add_string "sid" "$sid"
json_add_string "name" "$name"
json_add_string "category" "$category"
json_add_string "path" "$path"
json_add_string "enabled" "$enabled"
json_add_int "size" "$sz"
json_close_object
done
json_close_array
json_dump
}
method_read() {
local sid=""
json_load "$(cat)"
json_get_var sid "sid"
[ -z "$sid" ] && { json_init; json_add_string "error" "no sid"; json_dump; return; }
local path
path=$(uci -q get "quickactions.$sid.path")
[ -z "$path" ] && path="$SCRIPTS_DIR/$sid.sh"
json_init
if [ -f "$path" ]; then
json_add_string "content" "$(cat "$path")"
else
json_add_string "content" ""
fi
json_dump
}
method_write() {
local payload sid name category content args timeout enabled path
payload=$(cat)
json_load "$payload"
json_get_var sid "sid"
json_get_var name "name"
json_get_var category "category"
json_get_var content "content"
json_get_var args "args"
json_get_var timeout "timeout"
json_get_var enabled "enabled"
[ -z "$sid" ] && sid="script_$(date +%s)"
[ -z "$name" ] && name="$sid"
[ -z "$category" ] && category="custom"
[ -z "$timeout" ] && timeout="60"
[ -z "$enabled" ] && enabled="1"
path=$(uci -q get "quickactions.$sid.path")
[ -z "$path" ] && path="$SCRIPTS_DIR/$sid.sh"
mkdir -p "$(dirname "$path")"
printf '%s' "$content" > "$path"
chmod 755 "$path" 2>/dev/null
uci -q set "quickactions.$sid=script"
uci -q set "quickactions.$sid.name=$name"
uci -q set "quickactions.$sid.category=$category"
uci -q set "quickactions.$sid.path=$path"
uci -q set "quickactions.$sid.enabled=$enabled"
uci -q set "quickactions.$sid.timeout=$timeout"
[ -n "$args" ] && uci -q set "quickactions.$sid.args=$args"
uci -q commit quickactions
json_init
json_add_string "sid" "$sid"
json_add_boolean "ok" 1
json_dump
}
method_delete() {
local sid=""
json_load "$(cat)"
json_get_var sid "sid"
[ -z "$sid" ] && { json_init; json_add_boolean "ok" 0; json_dump; return; }
local path
path=$(uci -q get "quickactions.$sid.path")
[ -z "$path" ] && path="$SCRIPTS_DIR/$sid.sh"
[ -f "$path" ] && rm -f "$path"
uci -q delete "quickactions.$sid"
uci -q commit quickactions
json_init
json_add_boolean "ok" 1
json_dump
}
method_run() {
local sid=""
json_load "$(cat)"
json_get_var sid "sid"
[ -z "$sid" ] && { json_init; json_add_string "error" "no sid"; json_dump; return; }
local path timeout args
path=$(uci -q get "quickactions.$sid.path")
[ -z "$path" ] && path="$SCRIPTS_DIR/$sid.sh"
timeout=$(uci -q get "quickactions.$sid.timeout")
[ -z "$timeout" ] && timeout=60
args=$(uci -q get "quickactions.$sid.args")
if [ ! -f "$path" ]; then
json_init
json_add_string "output" "Script not found: $path"
json_add_int "exit_code" 127
json_dump
return
fi
[ -x "$path" ] || chmod 755 "$path" 2>/dev/null
local start_sec end_sec out rc
start_sec=$(date +%s 2>/dev/null)
out=$(cd / && timeout "$timeout" "$path" $args 2>&1)
rc=$?
end_sec=$(date +%s 2>/dev/null)
json_init
json_add_string "output" "$out"
json_add_int "exit_code" "$rc"
json_add_int "duration" "$((end_sec - start_sec))"
json_dump
}
method_chmod() {
local payload sid mode
payload=$(cat)
json_load "$payload"
json_get_var sid "sid"
json_get_var mode "mode"
[ -z "$mode" ] && mode="755"
local path
path=$(uci -q get "quickactions.$sid.path")
[ -z "$path" ] && path="$SCRIPTS_DIR/$sid.sh"
[ -f "$path" ] && chmod "$mode" "$path" 2>/dev/null
json_init
json_add_boolean "ok" 1
json_dump
}
method_detail() {
local sid=""
json_load "$(cat)"
json_get_var sid "sid"
[ -z "$sid" ] && { json_init; json_add_string "error" "no sid"; json_dump; return; }
local path
path=$(uci -q get "quickactions.$sid.path")
[ -z "$path" ] && path="$SCRIPTS_DIR/$sid.sh"
json_init
json_add_string "sid" "$sid"
json_add_string "name" "$(uci -q get quickactions.$sid.name)"
json_add_string "category" "$(uci -q get quickactions.$sid.category)"
json_add_string "path" "$path"
json_add_string "enabled" "$(uci -q get quickactions.$sid.enabled)"
json_add_string "timeout" "$(uci -q get quickactions.$sid.timeout)"
json_add_string "args" "$(uci -q get quickactions.$sid.args)"
json_add_string "description" "$(uci -q get quickactions.$sid.description)"
if [ -f "$path" ]; then
json_add_string "content" "$(cat "$path")"
json_add_int "size" "$(wc -c < "$path")"
json_add_string "permissions" "$(ls -l "$path" | awk '{print $1}')"
else
json_add_string "content" ""
json_add_int "size" 0
json_add_string "permissions" ""
fi
json_dump
}
method_install_preset() {
local preset=""
json_load "$(cat)"
json_get_var preset "preset"
local sid="failover-lite"
local path="$SCRIPTS_DIR/$sid.sh"
mkdir -p "$SCRIPTS_DIR"
if [ "$preset" = "failover" ] || [ -z "$preset" ]; then
cat > "$path" << 'FO_EOF'
#!/bin/sh
# Failover-lite — MWAN3 substitute for low-RAM routers.
. /lib/functions.sh
config_load quickactions
cfg() { config_get v failover "$1" "$2"; echo "$v"; }
ENABLED=$(cfg enabled 1); [ "$ENABLED" = "0" ] && exit 0
PRIMARY=$(cfg primary ""); [ -z "$PRIMARY" ] && exit 0
CHECK_IPS=$(cfg check_ips "1.1.1.1 8.8.8.8")
PING_TIMEOUT=$(cfg ping_timeout 5)
FAIL_AT=$(cfg fail_threshold 2)
REC_AT=$(cfg recover_threshold 1)
LOCK=/tmp/qa-failover-preset.lock
FAIL_FILE=/tmp/qa-failover-preset.fails
OK_FILE=/tmp/qa-failover-preset.oks
phys_iface() {
local log="$1" dev
[ -e "/sys/class/net/pppoe-$log" ] && { echo "pppoe-$log"; return; }
case "$log" in *pppoe*) [ -e "/sys/class/net/$log" ] && { echo "$log"; return; } ;; esac
dev=$(uci -q get network.$log.device)
[ -n "$dev" ] && [ -e "/sys/class/net/$dev" ] && { echo "$dev"; return; }
echo "$log"
}
ping_ok() {
local iface="$1" ip
for ip in $CHECK_IPS; do
ping -I "$iface" -c 1 -W "$PING_TIMEOUT" "$ip" >/dev/null 2>&1 && return 0
done
return 1
}
flush_ct() {
[ -e /proc/net/nf_conntrack ] && echo f > /proc/net/nf_conntrack
[ -e /proc/net/ip_conntrack ] && echo f > /proc/net/ip_conntrack
return 0
}
DEV=$(phys_iface "$PRIMARY")
if [ -f "$LOCK" ]; then
OK_N=0; [ -f "$OK_FILE" ] && OK_N=$(cat "$OK_FILE")
if ping_ok "$DEV"; then
OK_N=$((OK_N + 1))
if [ "$OK_N" -ge "$REC_AT" ]; then
rm -f "$LOCK" "$FAIL_FILE" "$OK_FILE"
flush_ct
logger -t qa-failover-preset "PRIMARY $PRIMARY recovered"
else
echo "$OK_N" > "$OK_FILE"
fi
else
echo 0 > "$OK_FILE"
fi
exit 0
fi
if ping_ok "$DEV"; then
rm -f "$FAIL_FILE"
exit 0
fi
FAIL_N=0; [ -f "$FAIL_FILE" ] && FAIL_N=$(cat "$FAIL_FILE")
FAIL_N=$((FAIL_N + 1))
echo "$FAIL_N" > "$FAIL_FILE"
if [ "$FAIL_N" -ge "$FAIL_AT" ]; then
touch "$LOCK"; rm -f "$FAIL_FILE"; flush_ct
logger -t qa-failover-preset "PRIMARY $PRIMARY DOWN after $FAIL_AT failures"
fi
exit 0
FO_EOF
fi
chmod 755 "$path"
uci -q set "quickactions.$sid=script"
uci -q set "quickactions.$sid.name=Failover-lite"
uci -q set "quickactions.$sid.category=failover"
uci -q set "quickactions.$sid.path=$path"
uci -q set "quickactions.$sid.enabled=0"
uci -q set "quickactions.$sid.timeout=30"
uci -q set "quickactions.$sid.description=MWAN3 substitute: ping test + conntrack flush"
uci -q commit quickactions
json_init
json_add_string "sid" "$sid"
json_add_string "path" "$path"
json_add_boolean "ok" 1
json_dump
}
call_method() {
local method="$1"
case "$method" in
list) method_list ;;
read) method_read ;;
write) method_write ;;
delete) method_delete ;;
run) method_run ;;
chmod) method_chmod ;;
detail) method_detail ;;
install_preset) method_install_preset ;;
*) cat >/dev/null
json_init
json_add_string "error" "unknown_method"
json_dump ;;
esac
}
case "$1" in
list) list_methods ;;
call) call_method "$2" ;;
*) echo '{"error":"usage"}' ;;
esac
@@ -0,0 +1,117 @@
#!/bin/sh
# ubus RPC module for Quick Actions Task Plan
. /usr/share/libubox/jshn.sh
LOG_FILE="/etc/quickactions-taskplan/taskplan.log"
CONFIG_FILE="/etc/config/taskplan"
CRON_FILE="/etc/crontabs/root"
INIT_SCRIPT="/etc/init.d/quickactions-taskplan"
list_methods() {
cat <<'EOF'
{
"status": {},
"list": {},
"task_list": {},
"start": {},
"stop": {},
"restart": {},
"reload": {}
}
EOF
}
method_status() {
local running=0 cron_tasks=0 scheduled=0 startup=0 log_content=""
pgrep -f quickactions-taskplan >/dev/null 2>&1 && running=1
[ -f "$CRON_FILE" ] && cron_tasks=$(grep -c "quickactions-taskplanhandler" "$CRON_FILE" 2>/dev/null || echo 0)
[ -f "$CONFIG_FILE" ] && scheduled=$(grep -c "stime" "$CONFIG_FILE" 2>/dev/null || echo 0)
[ -f "$CONFIG_FILE" ] && startup=$(grep -c "ltime" "$CONFIG_FILE" 2>/dev/null || echo 0)
[ -f "$LOG_FILE" ] && log_content=$(tail -n 50 "$LOG_FILE" 2>/dev/null)
json_init
json_add_int "running" $running
json_add_int "cron_tasks" $cron_tasks
json_add_int "scheduled_tasks" $scheduled
json_add_int "startup_tasks" $startup
json_add_string "log" "$log_content"
json_dump
}
method_list() {
local ssum=0 lsum=0
[ -f "$CONFIG_FILE" ] && ssum=$(grep -c "stime" "$CONFIG_FILE" 2>/dev/null || echo 0)
[ -f "$CONFIG_FILE" ] && lsum=$(grep -c "ltime" "$CONFIG_FILE" 2>/dev/null || echo 0)
json_init
json_add_int "scheduled" $ssum
json_add_int "startup" $lsum
json_dump
}
method_task_list() {
json_init
json_add_array "tasks"
if [ -f "$CONFIG_FILE" ]; then
local index=0
while true; do
local section=$(uci -q get taskplan.@stime[$index] 2>/dev/null)
[ -z "$section" ] && break
json_add_object
json_add_string "type" "scheduled"
json_add_int "index" $index
json_add_int "enable" "$(uci -q get taskplan.@stime[$index].enable || echo 0)"
json_add_string "remarks" "$(uci -q get taskplan.@stime[$index].remarks || echo "")"
json_add_string "stype" "$(uci -q get taskplan.@stime[$index].stype || echo "")"
json_add_string "schedule" "$(uci -q get taskplan.@stime[$index].minute) $(uci -q get taskplan.@stime[$index].hour) $(uci -q get taskplan.@stime[$index].week) $(uci -q get taskplan.@stime[$index].month)"
json_close_object
index=$((index + 1))
done
index=0
while true; do
local section=$(uci -q get taskplan.@ltime[$index] 2>/dev/null)
[ -z "$section" ] && break
json_add_object
json_add_string "type" "startup"
json_add_int "index" $index
json_add_int "enable" "$(uci -q get taskplan.@ltime[$index].enable || echo 0)"
json_add_string "remarks" "$(uci -q get taskplan.@ltime[$index].remarks || echo "")"
json_add_string "stype" "$(uci -q get taskplan.@ltime[$index].stype || echo "")"
json_add_int "delay" "$(uci -q get taskplan.@ltime[$index].delay || echo 10)"
json_close_object
index=$((index + 1))
done
fi
json_close_array
json_dump
}
service_action() {
local action="$1"
[ -x "$INIT_SCRIPT" ] && "$INIT_SCRIPT" "$action" >/dev/null 2>&1
local ret=$?
json_init
json_add_int "result" $ret
[ $ret -eq 0 ] && json_add_string "message" "Service $action ok"
json_dump
}
call_method() {
local method="$1"
cat >/dev/null
case "$method" in
status) method_status ;;
list) method_list ;;
task_list) method_task_list ;;
start) service_action start ;;
stop) service_action stop ;;
restart) service_action restart ;;
reload) service_action reload ;;
*) json_init; json_add_string "error" "unknown_method"; json_dump ;;
esac
}
case "$1" in
list) list_methods ;;
call) call_method "$2" ;;
*) echo '{"error":"usage: '$0' {list|call <method>}"}' ;;
esac
@@ -0,0 +1,640 @@
#!/bin/sh
# ubus RPC module for Quick Actions VLAN tab.
# Params arrive on stdin as JSON. No positional args.
. /usr/share/libubox/jshn.sh
VLAN_DIR=/etc/quickactions/vlan
TPL_DIR=$VLAN_DIR/templates
GEN_DIR=$VLAN_DIR/generated
mkdir -p "$VLAN_DIR" "$TPL_DIR" "$GEN_DIR"
list_methods() {
cat <<'EOF'
{
"detect": {},
"preview": { "plan": "string" },
"apply": { "plan": "string", "safe": "string" },
"snapshot": { "note": "string" },
"list_snapshots": {},
"restore": { "path": "string" },
"confirm": {},
"generate_cli": { "plan": "string", "target": "string" },
"save_template": { "name": "string", "target": "string", "plan": "string", "notes": "string" },
"list_templates": {},
"load_template": { "name": "string" },
"delete_template": { "name": "string" },
"save_generated": { "name": "string", "target": "string", "content": "string" },
"list_generated": {},
"delete_generated": { "name": "string" },
"lldp_neighbors": {},
"snmp_query": { "ip": "string", "community": "string" },
"bridge_vlan_show": {},
"kernel_ifaces": {},
"iface_stats": { "ifaces": "string" },
"ping_test": { "iface": "string", "ip": "string" }
}
EOF
}
# read stdin and populate named vars (var=json_key pairs)
_load() {
local payload
payload=$(cat)
json_load "$payload"
while [ "$#" -ge 2 ]; do
eval "$1=\"\$(json_get_var __v '$2'; echo \$__v)\""
shift 2
done
}
detect() {
json_init
local mode="none"
uci show network 2>/dev/null | grep -q "\.type='bridge'" && mode="dsa"
uci show network 2>/dev/null | grep -q "^network\.@switch\[0\]" && mode="swconfig"
json_add_string "mode" "$mode"
json_add_array "bridges"
for s in $(uci show network 2>/dev/null | grep "=device$" | cut -d. -f2 | cut -d= -f1); do
t=$(uci -q get "network.$s.type")
[ "$t" != "bridge" ] && continue
name=$(uci -q get "network.$s.name")
[ -z "$name" ] && name="$s"
json_add_object
json_add_string "sid" "$s"
json_add_string "name" "$name"
vf=$(uci -q get "network.$s.vlan_filtering")
json_add_string "vlan_filtering" "${vf:-0}"
json_close_object
done
json_close_array
json_add_array "bridge_vlans"
for s in $(uci show network 2>/dev/null | grep "=bridge-vlan$" | cut -d. -f2 | cut -d= -f1); do
json_add_object
json_add_string "sid" "$s"
json_add_string "device" "$(uci -q get network.$s.device)"
json_add_string "vlan" "$(uci -q get network.$s.vlan)"
json_add_string "ports" "$(uci -q get network.$s.ports | tr '\n' ' ')"
json_close_object
done
json_close_array
json_add_array "interfaces"
for s in $(uci show network 2>/dev/null | grep "=interface$" | cut -d. -f2 | cut -d= -f1); do
json_add_object
json_add_string "sid" "$s"
json_add_string "device" "$(uci -q get network.$s.device)"
json_add_string "proto" "$(uci -q get network.$s.proto)"
json_add_string "ipaddr" "$(uci -q get network.$s.ipaddr)"
json_add_string "netmask" "$(uci -q get network.$s.netmask)"
json_close_object
done
json_close_array
json_add_array "dhcp"
for s in $(uci show dhcp 2>/dev/null | grep "=dhcp$" | cut -d. -f2 | cut -d= -f1); do
json_add_object
json_add_string "sid" "$s"
json_add_string "interface" "$(uci -q get dhcp.$s.interface)"
json_add_string "start" "$(uci -q get dhcp.$s.start)"
json_add_string "limit" "$(uci -q get dhcp.$s.limit)"
json_close_object
done
json_close_array
json_add_array "zones"
local i=0
while true; do
local name
name=$(uci -q get "firewall.@zone[$i].name")
[ -z "$name" ] && break
json_add_object
json_add_string "name" "$name"
json_add_string "network" "$(uci -q get firewall.@zone[$i].network | tr '\n' ' ')"
json_add_string "input" "$(uci -q get firewall.@zone[$i].input)"
json_add_string "output" "$(uci -q get firewall.@zone[$i].output)"
json_add_string "forward" "$(uci -q get firewall.@zone[$i].forward)"
json_close_object
i=$((i + 1))
done
json_close_array
json_add_array "wifi"
for s in $(uci show wireless 2>/dev/null | grep "=wifi-iface$" | cut -d. -f2 | cut -d= -f1); do
json_add_object
json_add_string "sid" "$s"
json_add_string "device" "$(uci -q get wireless.$s.device)"
json_add_string "mode" "$(uci -q get wireless.$s.mode)"
json_add_string "ssid" "$(uci -q get wireless.$s.ssid)"
json_add_string "network" "$(uci -q get wireless.$s.network)"
json_close_object
done
json_close_array
json_add_array "easymesh"
for iface in /sys/class/net/bat0.*; do
[ -e "$iface" ] || continue
json_add_string "" "$(basename "$iface")"
done
json_close_array
json_add_string "kernel" "$(uname -r)"
json_add_string "mem_mb" "$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null)"
json_add_string "hostname" "$(cat /proc/sys/kernel/hostname 2>/dev/null)"
json_dump
}
kernel_ifaces() {
json_init
json_add_array "ifaces"
for d in /sys/class/net/*; do
[ -e "$d" ] || continue
local n
n=$(basename "$d")
case "$n" in
lo|sit*|ip6tnl*|gre*|gretap*|erspan*|ifb*|veth*) continue ;;
esac
local up
up="0"; [ "$(cat "$d/operstate" 2>/dev/null)" = "up" ] && up="1"
json_add_object
json_add_string "name" "$n"
json_add_int "up" "$up"
json_close_object
done
json_close_array
json_dump
}
preview() {
local plan
_load plan plan
[ -z "$plan" ] && { json_init; json_add_string "error" "no plan"; json_dump; return; }
echo "$plan" > "$VLAN_DIR/last-plan.json"
local out
out=$(
python3 - <<'PYPREVIEW' 2>&1
import json
plan = json.load(open("/etc/quickactions/vlan/last-plan.json"))
lines = []
for v in plan.get("vlans", []):
lines.append("network.%s=bridge-vlan device=%s vlan=%s" % (
v.get("sid","vlan"), v.get("bridge",""), v.get("vlan","")))
for i in plan.get("interfaces", []):
lines.append("network.%s=interface device=%s proto=%s ip=%s" % (
i.get("name",""), i.get("device",""), i.get("proto",""), i.get("ipaddr","")))
print("\n".join(lines))
PYPREVIEW
)
json_init
json_add_string "diff" "$out"
json_dump
}
apply() {
local plan safe
_load plan plan safe safe
[ -z "$plan" ] && { json_init; json_add_string "error" "no plan"; json_dump; return; }
[ -z "$safe" ] && safe="0"
local ts
ts=$(date +%Y%m%d-%H%M%S)
cp /etc/config/network "$VLAN_DIR/network.bak.$ts" 2>/dev/null
echo "auto before apply" > "$VLAN_DIR/network.bak.$ts.note"
echo "$plan" > "$VLAN_DIR/last-plan.json"
if ! command -v python3 >/dev/null 2>&1; then
json_init
json_add_string "error" "python3 required for VLAN apply"
json_dump
return
fi
python3 - <<'PYAPPLY'
import json, subprocess
plan = json.load(open("/etc/quickactions/vlan/last-plan.json"))
def uci(*args):
try:
subprocess.run(["uci"] + list(args), check=True, capture_output=True)
except subprocess.CalledProcessError:
return False
return True
for v in plan.get("vlans", []):
name = v.get("sid") or ("vlan_%s_%s" % (v.get("bridge","br-lan"), v.get("vlan","0")))
uci("set", "network.%s=bridge-vlan" % name)
uci("set", "network.%s.device=%s" % (name, v.get("bridge","")))
uci("set", "network.%s.vlan=%s" % (name, v.get("vlan","")))
for p in v.get("tagged", []):
uci("add_list", "network.%s.ports=%s:t" % (name, p))
for p in v.get("untagged", []):
uci("add_list", "network.%s.ports=%s:u*" % (name, p))
for i in plan.get("interfaces", []):
n = i.get("name","")
if not n: continue
uci("set", "network.%s=interface" % n)
uci("set", "network.%s.device=%s" % (n, i.get("device","")))
uci("set", "network.%s.proto=%s" % (n, i.get("proto","static")))
if i.get("ipaddr"): uci("set", "network.%s.ipaddr=%s" % (n, i["ipaddr"]))
if i.get("netmask"): uci("set", "network.%s.netmask=%s" % (n, i["netmask"]))
for d in plan.get("dhcp", []):
n = d.get("name","")
if not n: continue
uci("set", "dhcp.%s=dhcp" % n)
uci("set", "dhcp.%s.interface=%s" % (n, d.get("interface","")))
uci("set", "dhcp.%s.start=%s" % (n, d.get("start","100")))
uci("set", "dhcp.%s.limit=%s" % (n, d.get("limit","150")))
uci("set", "dhcp.%s.leasetime=%s" % (n, d.get("leasetime","12h")))
for z in plan.get("zones", []):
n = z.get("name","")
if not n: continue
exists = subprocess.run(["uci","-q","get","firewall.%s" % n], capture_output=True).returncode == 0
if not exists:
uci("set", "firewall.%s=zone" % n)
uci("set", "firewall.%s.name=%s" % (n, n))
for net in z.get("network", []):
uci("add_list", "firewall.%s.network=%s" % (n, net))
uci("set", "firewall.%s.input=%s" % (n, z.get("input","REJECT")))
uci("set", "firewall.%s.output=%s" % (n, z.get("output","ACCEPT")))
uci("set", "firewall.%s.forward=%s" % (n, z.get("forward","REJECT")))
subprocess.run(["uci","commit","network"])
subprocess.run(["uci","commit","dhcp"])
subprocess.run(["uci","commit","firewall"])
PYAPPLY
if [ "$safe" = "1" ]; then
local now deadline
now=$(date +%s)
deadline=$((now + 90))
echo "$deadline" > "$VLAN_DIR/pending_rollback"
( sleep 90
if [ -f "$VLAN_DIR/pending_rollback" ]; then
dl=$(cat "$VLAN_DIR/pending_rollback" 2>/dev/null)
if [ "$dl" = "$deadline" ]; then
cp "$VLAN_DIR/network.bak.$ts" /etc/config/network
uci commit network
/etc/init.d/network reload
rm -f "$VLAN_DIR/pending_rollback"
fi
fi ) >/dev/null 2>&1 &
fi
/etc/init.d/network reload >/dev/null 2>&1
sleep 2
json_init
json_add_boolean "ok" 1
json_add_string "snapshot" "$VLAN_DIR/network.bak.$ts"
[ "$safe" = "1" ] && json_add_int "rollback_in" 90
json_dump
}
snapshot() {
local note
_load note note
[ -z "$note" ] && note="manual"
local ts path
ts=$(date +%Y%m%d-%H%M%S)
path="$VLAN_DIR/network.bak.$ts"
cp /etc/config/network "$path" 2>/dev/null || { json_init; json_add_string "error" "cp failed"; json_dump; return; }
echo "$note" > "$path.note" 2>/dev/null
ls -1t $VLAN_DIR/network.bak.* 2>/dev/null | grep -v '\.note$' | tail -n +21 | while read -r old; do
rm -f "$old" "$old.note"
done
json_init
json_add_string "path" "$path"
json_add_string "ts" "$ts"
json_dump
}
list_snapshots() {
json_init
json_add_array "snapshots"
for f in $(ls -1t $VLAN_DIR/network.bak.* 2>/dev/null | grep -v '\.note$'); do
[ -f "$f" ] || continue
local sz note
sz=$(wc -c < "$f" 2>/dev/null)
note=""
[ -f "$f.note" ] && note=$(cat "$f.note" 2>/dev/null)
json_add_object
json_add_string "path" "$f"
json_add_string "size" "${sz:-0}"
json_add_string "note" "$note"
json_close_object
done
json_close_array
json_add_string "pending" "$(cat $VLAN_DIR/pending_rollback 2>/dev/null)"
json_dump
}
restore() {
local path
_load path path
[ -z "$path" ] || [ ! -f "$path" ] && { json_init; json_add_string "error" "bad path"; json_dump; return; }
cp "$path" /etc/config/network
uci commit network
/etc/init.d/network reload >/dev/null 2>&1
json_init
json_add_boolean "ok" 1
json_dump
}
confirm() {
rm -f "$VLAN_DIR/pending_rollback"
json_init
json_add_boolean "ok" 1
json_dump
}
generate_cli() {
local plan target
_load plan plan target target
[ -z "$target" ] && target="generic"
echo "$plan" > "$VLAN_DIR/last-plan.json"
local out
out=$(
python3 - <<PYGEN
import json
plan = json.load(open("/etc/quickactions/vlan/last-plan.json"))
target = "$target"
out = []
if target == "mikrotik":
bridge = plan["vlans"][0].get("bridge","bridge1") if plan.get("vlans") else "bridge1"
pvid = {}
for v in plan.get("vlans", []):
for p in v.get("untagged", []):
pvid[p] = v.get("vlan")
out.append("/interface bridge")
out.append("add name=%s vlan-filtering=yes" % bridge)
out.append("/interface bridge port")
for p, vid in pvid.items():
out.append("add bridge=%s interface=%s pvid=%s" % (bridge, p, vid))
out.append("/interface bridge vlan")
for v in plan.get("vlans", []):
t = ",".join(v.get("tagged",[]))
u = ",".join(v.get("untagged",[]))
parts = ["bridge=%s" % bridge, "vlan-ids=%s" % v.get("vlan","")]
if t: parts.append("tagged=%s" % t)
if u: parts.append("untagged=%s" % u)
out.append("add " + " ".join(parts))
elif target == "cisco":
for v in plan.get("vlans", []):
out.append("vlan %s" % v.get("vlan",""))
out.append(" name VLAN_%s" % v.get("vlan",""))
out.append("!")
trunks = set()
for v in plan.get("vlans", []):
for p in v.get("tagged", []):
trunks.add(p)
for p in sorted(trunks):
out.append("interface %s" % p)
out.append(" switchport trunk encapsulation dot1q")
out.append(" switchport mode trunk")
allowed = []
for v in plan.get("vlans", []):
if p in v.get("tagged", []):
allowed.append(str(v.get("vlan")))
out.append(" switchport trunk allowed vlan %s" % ",".join(allowed))
out.append("!")
for v in plan.get("vlans", []):
for p in v.get("untagged", []):
out.append("interface %s" % p)
out.append(" switchport mode access")
out.append(" switchport access vlan %s" % v.get("vlan",""))
out.append("!")
elif target == "edgeos":
for v in plan.get("vlans", []):
vid = v.get("vlan","")
for p in v.get("tagged", []):
out.append("set interfaces ethernet %s vif %s description VLAN_%s" % (p, vid, vid))
for p in v.get("untagged", []):
out.append("set interfaces ethernet %s description VLAN_%s" % (p, vid))
out.append("set interfaces ethernet %s pvid %s" % (p, vid))
else:
out.append("VLAN Ports (T=tagged, U=untagged)")
for v in plan.get("vlans", []):
plist = []
for p in v.get("tagged", []): plist.append("%s T" % p)
for p in v.get("untagged", []): plist.append("%s U" % p)
out.append("%-5s %s" % (v.get("vlan",""), ", ".join(plist)))
print("\n".join(out))
PYGEN
)
json_init
json_add_string "cli" "$out"
json_add_string "target" "$target"
json_dump
}
save_template() {
local name target plan notes
_load name name target target plan plan notes notes
[ -z "$name" ] && { json_init; json_add_string "error" "name required"; json_dump; return; }
local fname
fname=$(echo "$name" | tr -c 'a-zA-Z0-9._-' '_')
printf '%s\n' "$plan" > "$TPL_DIR/$fname.json"
printf '{"target":"%s","notes":"%s"}\n' "$target" "$notes" > "$TPL_DIR/$fname.meta"
json_init
json_add_string "name" "$fname"
json_dump
}
list_templates() {
json_init
json_add_array "templates"
for f in $TPL_DIR/*.json; do
[ -f "$f" ] || continue
local b meta
b=$(basename "$f" .json)
meta="{}"
[ -f "$TPL_DIR/$b.meta" ] && meta=$(cat "$TPL_DIR/$b.meta")
json_add_object
json_add_string "name" "$b"
json_add_string "meta" "$meta"
json_close_object
done
json_close_array
json_dump
}
load_template() {
local name
_load name name
local fname
fname=$(echo "$name" | tr -c 'a-zA-Z0-9._-' '_')
if [ ! -f "$TPL_DIR/$fname.json" ]; then
json_init; json_add_string "error" "not found"; json_dump; return
fi
json_init
json_add_string "plan" "$(cat "$TPL_DIR/$fname.json")"
[ -f "$TPL_DIR/$fname.meta" ] && json_add_string "meta" "$(cat "$TPL_DIR/$fname.meta")"
json_dump
}
delete_template() {
local name
_load name name
local fname
fname=$(echo "$name" | tr -c 'a-zA-Z0-9._-' '_')
rm -f "$TPL_DIR/$fname.json" "$TPL_DIR/$fname.meta"
json_init; json_add_boolean "ok" 1; json_dump
}
save_generated() {
local name target content
_load name name target target content content
[ -z "$name" ] && name="gen-$(date +%s)"
local fname
fname=$(echo "$name" | tr -c 'a-zA-Z0-9._-' '_')
printf '%s\n' "$content" > "$GEN_DIR/$fname.txt"
echo "$target" > "$GEN_DIR/$fname.target"
ls -1t $GEN_DIR/*.txt 2>/dev/null | tail -n +51 | while read -r old; do
rm -f "$old" "$old.target"
done
json_init; json_add_string "name" "$fname"; json_dump
}
list_generated() {
json_init
json_add_array "generated"
for f in $GEN_DIR/*.txt; do
[ -f "$f" ] || continue
local b tgt
b=$(basename "$f" .txt)
tgt=""
[ -f "$GEN_DIR/$b.target" ] && tgt=$(cat "$GEN_DIR/$b.target")
json_add_object
json_add_string "name" "$b"
json_add_string "target" "$tgt"
json_close_object
done
json_close_array
json_dump
}
delete_generated() {
local name
_load name name
local fname
fname=$(echo "$name" | tr -c 'a-zA-Z0-9._-' '_')
rm -f "$GEN_DIR/$fname.txt" "$GEN_DIR/$fname.target"
json_init; json_add_boolean "ok" 1; json_dump
}
lldp_neighbors() {
if ! command -v lldpcli >/dev/null 2>&1; then
json_init; json_add_string "error" "lldpcli not installed"; json_dump; return
fi
local out
out=$(lldpcli show neighbors 2>&1)
json_init
json_add_string "output" "$out"
json_dump
}
snmp_query() {
local ip community
_load ip ip community community
[ -z "$community" ] && community="public"
if ! command -v snmpwalk >/dev/null 2>&1; then
json_init; json_add_string "error" "snmpwalk not installed"; json_dump; return
fi
local out
out=$(snmpwalk -v2c -c "$community" -t 3 -r 1 "$ip" 1.3.6.1.2.1.17.7.1.4.3.1.2 2>&1)
json_init
json_add_string "ip" "$ip"
json_add_string "output" "$out"
json_dump
}
bridge_vlan_show() {
local out
out=$(bridge vlan show 2>&1)
json_init
json_add_string "output" "$out"
json_dump
}
iface_stats() {
local ifaces
_load ifaces ifaces
json_init
json_add_array "stats"
for i in $ifaces; do
[ -d "/sys/class/net/$i" ] || continue
local rx tx
rx=$(cat /sys/class/net/$i/statistics/rx_bytes 2>/dev/null)
tx=$(cat /sys/class/net/$i/statistics/tx_bytes 2>/dev/null)
json_add_object
json_add_string "iface" "$i"
json_add_int "rx" "${rx:-0}"
json_add_int "tx" "${tx:-0}"
json_close_object
done
json_close_array
json_dump
}
ping_test() {
local iface ip
_load iface iface ip ip
[ -z "$iface" ] && { json_init; json_add_string "error" "missing iface"; json_dump; return; }
[ -z "$ip" ] && { json_init; json_add_string "error" "missing ip"; json_dump; return; }
local out rc
out=$(ping -I "$iface" -c 3 -W 3 "$ip" 2>&1)
rc=$?
json_init
json_add_string "output" "$out"
json_add_int "exit_code" "$rc"
json_dump
}
call_method() {
local method="$1"
case "$method" in
detect) detect ;;
preview) preview ;;
apply) apply ;;
snapshot) snapshot ;;
list_snapshots) list_snapshots ;;
restore) restore ;;
confirm) confirm ;;
generate_cli) generate_cli ;;
save_template) save_template ;;
list_templates) list_templates ;;
load_template) load_template ;;
delete_template) delete_template ;;
save_generated) save_generated ;;
list_generated) list_generated ;;
delete_generated) delete_generated ;;
lldp_neighbors) lldp_neighbors ;;
snmp_query) snmp_query ;;
bridge_vlan_show) bridge_vlan_show ;;
kernel_ifaces) kernel_ifaces ;;
iface_stats) iface_stats ;;
ping_test) ping_test ;;
*) cat >/dev/null
json_init
json_add_string "error" "unknown_method"
json_dump ;;
esac
}
case "$1" in
list) list_methods ;;
call) call_method "$2" ;;
*) echo '{"error":"usage"}' ;;
esac
@@ -0,0 +1,10 @@
{
"admin/quickactions": {
"title": "Quick Actions",
"action": {
"type": "view",
"path": "system/quickactions"
},
"order": 25
}
}
@@ -0,0 +1,239 @@
{
"luci-app-quickactions": {
"description": "Grant access to Quick Actions",
"read": {
"ubus": {
"file": [
"exec",
"read"
],
"luci": [
"getInitList",
"getThemes"
],
"luci.quickactions-taskplan": [
"*"
],
"luci.quickactions-failover": [
"*"
],
"luci.quickactions-scripts": [
"*"
],
"luci.quickactions-vlan": [
"*"
]
},
"file": {
"/etc/crontabs/root": [
"read"
],
"/etc/config/hotplug": [
"read"
],
"/etc/quickactions-taskplan/taskplan.log": [
"read"
],
"/etc/quickactions-taskplan/taskplancustomscript": [
"read"
],
"/etc/quickactions-taskplan/taskplancustomscript2": [
"read"
],
"/etc/config/taskplan": [
"read"
],
"/etc/config/wizard": [
"read"
],
"/etc/config/netwizard": [
"read"
],
"/etc/config/network": [
"read"
],
"/etc/config/firewall": [
"read"
],
"/etc/config/dhcp": [
"read"
],
"/etc/config/wireless": [
"read"
],
"/etc/config/nginx": [
"read"
],
"/etc/config/uhttpd": [
"read"
],
"/etc/config/lighttpd": [
"read"
],
"/tmp/quickactions_wan_down.lock": [
"read"
],
"/tmp/quickactions_wan_fails.count": [
"read"
],
"/tmp/quickactions_wan_oks.count": [
"read"
],
"/etc/quickactions/scripts/*": [
"read"
],
"/etc/quickactions/vlan/*": [
"read"
],
"/etc/quickactions/vlan/templates/*": [
"read"
],
"/etc/quickactions/vlan/generated/*": [
"read"
]
},
"uci": [
"luci",
"quickactions",
"hotplug",
"guestwifi",
"network",
"wireless",
"dhcp",
"firewall",
"taskplan",
"wizard",
"netwizard",
"nginx"
],
"execute": {
"/usr/libexec/quickactions-netwizard/list-backups.sh": [
"exec"
]
}
},
"write": {
"ubus": {
"file": [
"exec",
"read",
"write"
],
"uci": [
"*"
],
"luci.quickactions-taskplan": [
"*"
],
"luci.quickactions-failover": [
"*"
],
"luci.quickactions-scripts": [
"*"
],
"luci.quickactions-vlan": [
"*"
]
},
"file": {
"/etc/crontabs/root": [
"write"
],
"/etc/config/hotplug": [
"write"
],
"/etc/quickactions-taskplan/taskplan.log": [
"write"
],
"/etc/quickactions-taskplan/taskplancustomscript": [
"write"
],
"/etc/quickactions-taskplan/taskplancustomscript2": [
"write"
],
"/etc/config/taskplan": [
"write"
],
"/etc/config/wizard": [
"write"
],
"/etc/config/netwizard": [
"write"
],
"/etc/config/network": [
"write"
],
"/etc/config/firewall": [
"write"
],
"/etc/config/dhcp": [
"write"
],
"/etc/config/wireless": [
"write"
],
"/etc/config/nginx": [
"write"
],
"/etc/config/uhttpd": [
"write"
],
"/etc/config/lighttpd": [
"write"
],
"/etc/quickactions/scripts/*": [
"write"
],
"/etc/quickactions/vlan/*": [
"write"
],
"/etc/quickactions/vlan/templates/*": [
"write"
],
"/etc/quickactions/vlan/generated/*": [
"write"
]
},
"uci": [
"luci",
"quickactions",
"hotplug",
"guestwifi",
"network",
"wireless",
"dhcp",
"firewall",
"taskplan",
"wizard",
"netwizard",
"nginx"
],
"execute": {
"/etc/init.d/quickactions-taskplan": [
"start",
"stop",
"restart",
"reload"
],
"/usr/libexec/quickactions-netwizard/apply-aggressive.sh": [
"exec"
],
"/usr/libexec/quickactions-netwizard/backup.sh": [
"exec"
],
"/usr/libexec/quickactions-netwizard/restore.sh": [
"exec"
],
"/usr/libexec/quickactions-netwizard/apply-shortcuts.sh": [
"exec"
],
"/usr/bin/quickactions-failover": [
"on",
"off",
"status",
"test"
]
}
}
}
}