mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-06 20:14:10 +08:00
🛸 Sync 2026-10-06 21:21:05
This commit is contained in:
1 parent
b239ebf896
commit
3c831d5757
56 files changed
+5677
No files matched your search
Executable
+3
@@ -0,0 +1,3 @@
|
||||
|
||||
config easymesh 'config'
|
||||
option enabled '0'
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
# Reapply link priorities when a mesh or wired-backhaul interface comes up.
|
||||
# Runs once per interface event. Idempotent — safe to call repeatedly.
|
||||
|
||||
[ "$ACTION" = "ifup" ] || exit 0
|
||||
|
||||
case "$INTERFACE" in
|
||||
nwi_mesh_*|nwi_mesh_wired)
|
||||
LOCK="/tmp/easymesh-priority.lock"
|
||||
|
||||
# Stale lock cleanup — if the lock is older than 5 minutes, remove it
|
||||
if [ -d "$LOCK" ]; then
|
||||
lock_age=$(($(date +%s) - $(date -r "$LOCK" +%s 2>/dev/null || echo 0)))
|
||||
[ "$lock_age" -gt 300 ] && rmdir "$LOCK" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Atomic lock acquisition — only one priority run at a time
|
||||
if mkdir "$LOCK" 2>/dev/null; then
|
||||
(
|
||||
sleep 5
|
||||
[ -x /etc/init.d/easymesh ] && \
|
||||
/etc/init.d/easymesh priority >/dev/null 2>&1
|
||||
rmdir "$LOCK" 2>/dev/null
|
||||
) </dev/null >/dev/null 2>&1 &
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Executable
+994
@@ -0,0 +1,994 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
#
|
||||
# EasyMesh init script
|
||||
# Copyright (C) 2021 dz <dingzhong110@gmail.com>
|
||||
# Copyright (C) 2026 Arafat Rahman Zami Mondol <zamimondol@gmail.com>
|
||||
#
|
||||
# Modified from mobing8/luci-app-easymesh-dawn.
|
||||
# Additions: 90-second priority retry loop, second-pass catchup,
|
||||
# optional cron fallback, hotplug integration,
|
||||
# robust UCI handling (no escaped quotes), safe role sanitation.
|
||||
|
||||
START=99
|
||||
STOP=70
|
||||
USE_PROCD=0
|
||||
|
||||
# --- Custom subcommands -------------------------------------------------
|
||||
EXTRA_COMMANDS="backup restore detach_wired pair rollback_start rollback_confirm rollback_cancel priority"
|
||||
EXTRA_HELP=" backup Save current config
|
||||
restore Restore from last backup
|
||||
detach_wired Remove wired backhaul
|
||||
pair Enter WPS pairing mode
|
||||
rollback_start Start rollback watchdog
|
||||
rollback_confirm Confirm pending changes
|
||||
rollback_cancel Revert immediately"
|
||||
|
||||
# --- Constants ----------------------------------------------------------
|
||||
LOG_TAG="easymesh"
|
||||
BACKUP_DIR="/etc/easymesh-backups"
|
||||
MAX_BACKUPS=10
|
||||
PAIR_TIMEOUT=120
|
||||
PAIRING_MESH_ID="EasyMesh_PAIRING"
|
||||
RB_DIR="/tmp/easymesh-rollback"
|
||||
SAVED_PORTS_FILE="$BACKUP_DIR/original-ports.txt"
|
||||
|
||||
log() { logger -t "$LOG_TAG" "$@"; echo "[$LOG_TAG] $*" >&2; }
|
||||
|
||||
# =========================================================
|
||||
# Helpers
|
||||
# =========================================================
|
||||
list_radios() {
|
||||
uci show wireless 2>/dev/null | \
|
||||
sed -n 's/^wireless\.\(radio[0-9]*\)=wifi-device$/\1/p'
|
||||
}
|
||||
|
||||
radio_band() { uci -q get "wireless.$1.band"; }
|
||||
|
||||
find_radio_by_band() {
|
||||
local r
|
||||
for r in $(list_radios); do
|
||||
[ "$(radio_band "$r")" = "$1" ] && { echo "$r"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
check_prereqs() {
|
||||
local ok=1
|
||||
if ! lsmod 2>/dev/null | grep -q '^batman_adv'; then
|
||||
log "ERROR: batman_adv module not loaded"
|
||||
ok=0
|
||||
fi
|
||||
command -v batctl >/dev/null 2>&1 || { log "ERROR: batctl missing"; ok=0; }
|
||||
[ "$ok" = "1" ]
|
||||
}
|
||||
|
||||
find_lan_bridge() {
|
||||
local dev
|
||||
dev=$(uci -q get network.@device[0].name)
|
||||
[ -n "$dev" ] && { echo "$dev"; return; }
|
||||
echo "br-lan"
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Wait for mesh VIFs and attach to bat0
|
||||
# =========================================================
|
||||
wait_and_attach_mesh() {
|
||||
# Wired-only: no VIFs to wait for
|
||||
if [ "$BAND_MODE" = "none" ]; then
|
||||
log "Wired-only mode — skipping mesh VIF wait"
|
||||
ifup bat0
|
||||
sleep 3
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Determine expected VIF count from the base band (strip _wired)
|
||||
local base=$(echo "$BAND_MODE" | sed 's/_wired$//')
|
||||
local expected
|
||||
case "$base" in
|
||||
dual) expected=2 ;;
|
||||
custom) expected=1 ;;
|
||||
*) expected=1 ;;
|
||||
esac
|
||||
|
||||
log "Waiting for mesh VIFs (expect $expected)..."
|
||||
local tries=0
|
||||
|
||||
while [ $tries -lt 30 ]; do
|
||||
local mesh_count=$(iw dev 2>/dev/null | grep -c "type mesh point")
|
||||
[ "$mesh_count" -ge "$expected" ] && break
|
||||
sleep 2
|
||||
tries=$((tries + 1))
|
||||
done
|
||||
log "Mesh VIFs present: $(iw dev 2>/dev/null | grep -c 'type mesh point')/$expected"
|
||||
|
||||
ifup bat0
|
||||
sleep 3
|
||||
|
||||
# Attach any mesh VIFs netifd missed
|
||||
local iface
|
||||
for iface in $(iw dev 2>/dev/null | awk '/Interface/{print $2}' | grep mesh); do
|
||||
if ! batctl if 2>/dev/null | grep -q "^${iface}:"; then
|
||||
log "Manually attaching $iface to bat0"
|
||||
batctl if add "$iface" 2>/dev/null
|
||||
fi
|
||||
done
|
||||
sleep 2
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Backup / restore
|
||||
# =========================================================
|
||||
backup_config() {
|
||||
local ts=$(date +%Y%m%d-%H%M%S)
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
for cfg in network wireless easymesh dhcp; do
|
||||
[ -f "/etc/config/$cfg" ] && cp "/etc/config/$cfg" "$BACKUP_DIR/$cfg.$ts"
|
||||
done
|
||||
echo "$ts" > "$BACKUP_DIR/LAST"
|
||||
|
||||
ls -1t "$BACKUP_DIR"/network.* 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)) | xargs -r rm -f
|
||||
ls -1t "$BACKUP_DIR"/wireless.* 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)) | xargs -r rm -f
|
||||
ls -1t "$BACKUP_DIR"/easymesh.* 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)) | xargs -r rm -f
|
||||
ls -1t "$BACKUP_DIR"/dhcp.* 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)) | xargs -r rm -f
|
||||
|
||||
log "Backup saved: ts=$ts"
|
||||
}
|
||||
|
||||
restore_config() {
|
||||
[ -f "$BACKUP_DIR/LAST" ] || { log "No backup found"; return 1; }
|
||||
local ts=$(cat "$BACKUP_DIR/LAST")
|
||||
for cfg in network wireless easymesh dhcp; do
|
||||
[ -f "$BACKUP_DIR/$cfg.$ts" ] && cp "$BACKUP_DIR/$cfg.$ts" "/etc/config/$cfg"
|
||||
done
|
||||
uci commit
|
||||
log "Restored from backup ts=$ts"
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Original bridge ports save / restore
|
||||
# =========================================================
|
||||
save_original_ports() {
|
||||
[ -f "$SAVED_PORTS_FILE" ] && return 0
|
||||
mkdir -p "$(dirname "$SAVED_PORTS_FILE")"
|
||||
|
||||
local idx=0 name ports
|
||||
while name=$(uci -q get "network.@device[$idx].name"); do
|
||||
ports=$(uci -q get "network.@device[$idx].ports")
|
||||
[ -n "$ports" ] && echo "$name|$ports" >> "$SAVED_PORTS_FILE"
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
log "Saved original bridge ports"
|
||||
}
|
||||
|
||||
restore_wired_port() {
|
||||
uci -q delete network.nwi_mesh_wired 2>/dev/null
|
||||
uci commit network
|
||||
|
||||
[ -f "$SAVED_PORTS_FILE" ] || {
|
||||
log "No saved ports file — nothing to restore"
|
||||
return 0
|
||||
}
|
||||
|
||||
while IFS='|' read -r bridge ports; do
|
||||
[ -z "$bridge" ] && continue
|
||||
local idx=0 name
|
||||
while name=$(uci -q get "network.@device[$idx].name"); do
|
||||
if [ "$name" = "$bridge" ]; then
|
||||
for p in $ports; do
|
||||
uci -q add_list "network.@device[$idx].ports=$p"
|
||||
done
|
||||
uci commit network
|
||||
log "Restored $ports to bridge $bridge"
|
||||
break
|
||||
fi
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
done < "$SAVED_PORTS_FILE"
|
||||
|
||||
/etc/init.d/network reload
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Loop prevention
|
||||
# =========================================================
|
||||
prevent_wired_loop() {
|
||||
local iface="$1"
|
||||
[ -z "$iface" ] && return 0
|
||||
|
||||
local idx=0 name ports
|
||||
while name=$(uci -q get "network.@device[$idx].name"); do
|
||||
ports=$(uci -q get "network.@device[$idx].ports")
|
||||
case " $ports " in
|
||||
*" $iface "*)
|
||||
log "PREVENT: $iface in bridge $name — removing to avoid loop"
|
||||
uci -q del_list "network.@device[$idx].ports=$iface"
|
||||
uci commit network
|
||||
;;
|
||||
esac
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Role sanitation — server keeps wired_if (needed for wired backhaul)
|
||||
# =========================================================
|
||||
sanitize_for_role() {
|
||||
case "$ROLE" in
|
||||
server)
|
||||
uci -q delete easymesh.config.ap_mode 2>/dev/null
|
||||
uci -q delete easymesh.config.ip_mode 2>/dev/null
|
||||
uci -q delete easymesh.config.ipaddr 2>/dev/null
|
||||
uci -q delete easymesh.config.netmask 2>/dev/null
|
||||
uci -q delete easymesh.config.gateway 2>/dev/null
|
||||
uci -q delete easymesh.config.dns 2>/dev/null
|
||||
uci commit easymesh
|
||||
log "Sanitized: Server — removed AP mode (wired backhaul allowed)"
|
||||
;;
|
||||
off)
|
||||
uci -q delete easymesh.config.ap_mode 2>/dev/null
|
||||
uci -q delete easymesh.config.wired_if 2>/dev/null
|
||||
uci commit easymesh
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Config loader
|
||||
# =========================================================
|
||||
load_config() {
|
||||
ENABLED=$(uci -q get easymesh.config.enabled)
|
||||
ROLE=$(uci -q get easymesh.config.role)
|
||||
BAND_MODE=$(uci -q get easymesh.config.band_mode)
|
||||
CUSTOM_RADIO=$(uci -q get easymesh.config.custom_radio)
|
||||
MESH_ID=$(uci -q get easymesh.config.mesh_id)
|
||||
ENCRYPTION=$(uci -q get easymesh.config.encryption)
|
||||
KEY=$(uci -q get easymesh.config.key)
|
||||
KVR=$(uci -q get easymesh.config.kvr)
|
||||
MOBILITY_DOMAIN=$(uci -q get easymesh.config.mobility_domain)
|
||||
WIRED_IF=$(uci -q get easymesh.config.wired_if)
|
||||
|
||||
AP_ENABLED_2G=$(uci -q get easymesh.config.ap_enabled_2g)
|
||||
AP_SSID_2G=$(uci -q get easymesh.config.ap_ssid_2g)
|
||||
AP_ENCRYPTION_2G=$(uci -q get easymesh.config.ap_encryption_2g)
|
||||
AP_KEY_2G=$(uci -q get easymesh.config.ap_key_2g)
|
||||
|
||||
AP_ENABLED_5G=$(uci -q get easymesh.config.ap_enabled_5g)
|
||||
AP_SSID_5G=$(uci -q get easymesh.config.ap_ssid_5g)
|
||||
AP_ENCRYPTION_5G=$(uci -q get easymesh.config.ap_encryption_5g)
|
||||
AP_KEY_5G=$(uci -q get easymesh.config.ap_key_5g)
|
||||
|
||||
AP_NETWORK=$(uci -q get easymesh.config.ap_network)
|
||||
SAFE_APPLY=$(uci -q get easymesh.config.safe_apply)
|
||||
|
||||
AP_MODE=$(uci -q get easymesh.config.ap_mode)
|
||||
IP_MODE=$(uci -q get easymesh.config.ip_mode)
|
||||
IPADDR=$(uci -q get easymesh.config.ipaddr)
|
||||
NETMASK=$(uci -q get easymesh.config.netmask)
|
||||
GATEWAY=$(uci -q get easymesh.config.gateway)
|
||||
DNS=$(uci -q get easymesh.config.dns)
|
||||
|
||||
# SAFETY: server never has AP mode
|
||||
if [ "$ROLE" = "server" ] && [ "$AP_MODE" = "1" ]; then
|
||||
log "SAFETY: role=server has ap_mode=1 — forcing off"
|
||||
AP_MODE="0"
|
||||
uci set easymesh.config.ap_mode=0
|
||||
uci commit easymesh
|
||||
fi
|
||||
|
||||
: "${MESH_ID:=EasyMesh_Dualband}"
|
||||
: "${BAND_MODE:=single_5g}"
|
||||
: "${MOBILITY_DOMAIN:=4f57}"
|
||||
: "${AP_ENABLED_2G:=1}"
|
||||
: "${AP_ENABLED_5G:=1}"
|
||||
: "${AP_SSID_2G:=EasyMesh_WiFi}"
|
||||
: "${AP_SSID_5G:=EasyMesh_WiFi_5G}"
|
||||
: "${AP_ENCRYPTION_2G:=sae-mixed}"
|
||||
: "${AP_ENCRYPTION_5G:=sae-mixed}"
|
||||
: "${AP_NETWORK:=lan}"
|
||||
|
||||
# Priority weights (Kbit/s) + mesh VLANs
|
||||
PRIORITY_WIRED=$(uci -q get easymesh.config.priority_wired)
|
||||
PRIORITY_6G=$(uci -q get easymesh.config.priority_6g)
|
||||
PRIORITY_5G=$(uci -q get easymesh.config.priority_5g)
|
||||
PRIORITY_24G=$(uci -q get easymesh.config.priority_24g)
|
||||
MESH_VLANS=$(uci -q get easymesh.config.mesh_vlans)
|
||||
|
||||
: "${PRIORITY_WIRED:=1000000}"
|
||||
: "${PRIORITY_6G:=900000}"
|
||||
: "${PRIORITY_5G:=600000}"
|
||||
: "${PRIORITY_24G:=30000}"
|
||||
: "${MESH_VLANS:=}"
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# bat0
|
||||
# =========================================================
|
||||
setup_bat0() {
|
||||
local gw_mode=off
|
||||
case "$ROLE" in
|
||||
server) gw_mode=server ;;
|
||||
client) gw_mode=client ;;
|
||||
node) gw_mode=off ;;
|
||||
esac
|
||||
|
||||
uci set network.bat0=interface
|
||||
uci set network.bat0.proto=batadv
|
||||
uci set network.bat0.routing_algo=BATMAN_IV
|
||||
uci set network.bat0.gw_mode=$gw_mode
|
||||
uci set network.bat0.aggregated_ogms=1
|
||||
uci set network.bat0.bridge_loop_avoidance=1
|
||||
uci set network.bat0.distributed_arp_table=1
|
||||
uci set network.bat0.fragmentation=1
|
||||
uci set network.bat0.hop_penalty=30
|
||||
uci set network.bat0.orig_interval=500
|
||||
uci set network.bat0.bonding=1
|
||||
uci commit network
|
||||
log "bat0 configured (gw_mode=$gw_mode, bonding=1)"
|
||||
}
|
||||
|
||||
attach_bat0_to_lan() {
|
||||
local brlan=$(find_lan_bridge)
|
||||
if [ "$brlan" = "br-lan" ]; then
|
||||
uci -q get network.@device[0].ports | grep -q bat0 || {
|
||||
uci add_list network.@device[0].ports=bat0
|
||||
uci commit network
|
||||
log "Added bat0 to br-lan"
|
||||
}
|
||||
|
||||
# Reduce bridge MAC aging for faster failover
|
||||
local cur_ageing=$(uci -q get network.@device[0].ageing_time)
|
||||
if [ "$cur_ageing" != "30" ]; then
|
||||
uci set network.@device[0].ageing_time=30
|
||||
uci commit network
|
||||
log "Bridge ageing_time set to 30s (was ${cur_ageing:-300})"
|
||||
fi
|
||||
else
|
||||
local lan=$(uci -q get network.lan.device)
|
||||
case " $lan " in
|
||||
*" bat0 "*) : ;;
|
||||
*) uci set network.lan.device="$lan bat0"; uci commit network ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
detach_bat0_from_lan() {
|
||||
local brlan=$(find_lan_bridge)
|
||||
if [ "$brlan" = "br-lan" ]; then
|
||||
uci -q del_list network.@device[0].ports=bat0 2>/dev/null
|
||||
uci commit network
|
||||
else
|
||||
local lan=$(uci -q get network.lan.device)
|
||||
lan=$(echo "$lan" | sed 's/ bat0//; s/bat0 //; s/^bat0$//')
|
||||
uci set network.lan.device="$lan"
|
||||
uci commit network
|
||||
fi
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Mesh iface (no quoted values — no escaped-quote bug)
|
||||
# =========================================================
|
||||
setup_mesh_iface() {
|
||||
local radio="$1"
|
||||
local meshname="mesh_${radio}"
|
||||
local netname="nwi_mesh_${radio}"
|
||||
|
||||
uci -q delete "wireless.$meshname" 2>/dev/null
|
||||
uci -q delete "network.$netname" 2>/dev/null
|
||||
|
||||
uci set "network.$netname=interface"
|
||||
uci set "network.$netname.proto=batadv_hardif"
|
||||
uci set "network.$netname.master=bat0"
|
||||
uci set "network.$netname.mtu=1500"
|
||||
uci commit network
|
||||
|
||||
uci set "wireless.$meshname=wifi-iface"
|
||||
uci set "wireless.$meshname.device=$radio"
|
||||
uci set "wireless.$meshname.network=$netname"
|
||||
uci set "wireless.$meshname.mode=mesh"
|
||||
uci set "wireless.$meshname.mesh_id=$MESH_ID"
|
||||
uci set "wireless.$meshname.mesh_fwding=1"
|
||||
uci set "wireless.$meshname.mesh_ttl=2"
|
||||
uci set "wireless.$meshname.mcast_rate=24000"
|
||||
uci set "wireless.$meshname.disabled=0"
|
||||
|
||||
if [ "$ENCRYPTION" = "1" ] && [ -n "$KEY" ]; then
|
||||
uci set "wireless.$meshname.encryption=sae"
|
||||
uci set "wireless.$meshname.key=$KEY"
|
||||
uci set "wireless.$meshname.ieee80211w=2"
|
||||
uci set "wireless.$meshname.sae_pwe=1"
|
||||
log "Mesh iface $radio: WPA3-SAE"
|
||||
else
|
||||
uci set "wireless.$meshname.encryption=none"
|
||||
uci -q delete "wireless.$meshname.key" 2>/dev/null
|
||||
uci -q delete "wireless.$meshname.ieee80211w" 2>/dev/null
|
||||
uci -q delete "wireless.$meshname.sae_pwe" 2>/dev/null
|
||||
uci -q delete "wireless.$meshname.sae_anti_clogging_threshold" 2>/dev/null
|
||||
log "Mesh iface $radio: OPEN"
|
||||
fi
|
||||
|
||||
uci commit wireless
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# K/V/R
|
||||
# =========================================================
|
||||
apply_kvr() {
|
||||
[ "$KVR" != "1" ] && return 0
|
||||
local r changed=0
|
||||
|
||||
for r in $(list_radios); do
|
||||
uci -q get "wireless.default_$r" >/dev/null || continue
|
||||
uci set "wireless.default_$r.ieee80211k=1"
|
||||
uci set "wireless.default_$r.rrm_neighbor_report=1"
|
||||
uci set "wireless.default_$r.rrm_beacon_report=1"
|
||||
uci set "wireless.default_$r.ieee80211v=1"
|
||||
uci set "wireless.default_$r.bss_transition=1"
|
||||
uci set "wireless.default_$r.ieee80211r=1"
|
||||
uci set "wireless.default_$r.mobility_domain=$MOBILITY_DOMAIN"
|
||||
uci set "wireless.default_$r.ft_over_ds=1"
|
||||
uci set "wireless.default_$r.ft_psk_generate_local=1"
|
||||
changed=1
|
||||
done
|
||||
|
||||
[ "$changed" = "1" ] && uci commit wireless && log "K/V/R applied"
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Client AP
|
||||
# =========================================================
|
||||
apply_client_ap() {
|
||||
local r band iface enabled ssid enc key
|
||||
|
||||
for r in $(list_radios); do
|
||||
iface="default_$r"
|
||||
uci -q get "wireless.$iface" >/dev/null || continue
|
||||
|
||||
band=$(radio_band "$r")
|
||||
|
||||
case "$band" in
|
||||
2g)
|
||||
enabled="$AP_ENABLED_2G"
|
||||
ssid="$AP_SSID_2G"
|
||||
enc="$AP_ENCRYPTION_2G"
|
||||
key="$AP_KEY_2G"
|
||||
;;
|
||||
5g)
|
||||
enabled="$AP_ENABLED_5G"
|
||||
ssid="$AP_SSID_5G"
|
||||
enc="$AP_ENCRYPTION_5G"
|
||||
key="$AP_KEY_5G"
|
||||
;;
|
||||
*)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
# Only disable if EXPLICITLY set to 0. Empty = keep enabled.
|
||||
if [ "$enabled" = "0" ]; then
|
||||
uci set "wireless.$iface.disabled=1"
|
||||
log "Client AP on $band ($r): disabled"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -z "$ssid" ]; then
|
||||
ssid=$(uci -q get "wireless.$iface.ssid")
|
||||
log "Using existing SSID for $band: $ssid"
|
||||
fi
|
||||
[ -n "$ssid" ] && uci set "wireless.$iface.ssid=$ssid"
|
||||
|
||||
uci set "wireless.$iface.mode=ap"
|
||||
uci set "wireless.$iface.network=$AP_NETWORK"
|
||||
uci set "wireless.$iface.disabled=0"
|
||||
|
||||
case "$enc" in
|
||||
none)
|
||||
uci set "wireless.$iface.encryption=none"
|
||||
uci -q delete "wireless.$iface.key" 2>/dev/null
|
||||
uci -q delete "wireless.$iface.ieee80211w" 2>/dev/null
|
||||
;;
|
||||
psk2+ccmp)
|
||||
uci set "wireless.$iface.encryption=psk2+ccmp"
|
||||
uci set "wireless.$iface.key=$key"
|
||||
uci -q delete "wireless.$iface.ieee80211w" 2>/dev/null
|
||||
;;
|
||||
sae)
|
||||
uci set "wireless.$iface.encryption=sae"
|
||||
uci set "wireless.$iface.key=$key"
|
||||
uci set "wireless.$iface.ieee80211w=2"
|
||||
;;
|
||||
sae-mixed|*)
|
||||
uci set "wireless.$iface.encryption=sae-mixed"
|
||||
uci set "wireless.$iface.key=$key"
|
||||
uci set "wireless.$iface.ieee80211w=1"
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Client AP on $band ($r): ssid=$ssid enc=$enc"
|
||||
done
|
||||
|
||||
uci commit wireless
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# AP mode (with hard role guard)
|
||||
# =========================================================
|
||||
apply_ap_mode() {
|
||||
if [ "$ROLE" = "server" ]; then
|
||||
log "apply_ap_mode: skipped (role=server)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
[ "$AP_MODE" = "1" ] || return 0
|
||||
log "AP mode: ip_mode=$IP_MODE"
|
||||
|
||||
if [ "$IP_MODE" = "static" ] && [ -n "$IPADDR" ]; then
|
||||
uci set network.lan.proto=static
|
||||
uci set network.lan.ipaddr=$IPADDR
|
||||
uci set network.lan.netmask=${NETMASK:-255.255.255.0}
|
||||
[ -n "$GATEWAY" ] && uci set network.lan.gateway=$GATEWAY
|
||||
uci -q delete network.lan.dns 2>/dev/null
|
||||
[ -n "$DNS" ] && uci add_list network.lan.dns=$DNS
|
||||
elif [ "$IP_MODE" = "dhcp" ]; then
|
||||
uci set network.lan.proto=dhcp
|
||||
uci -q delete network.lan.ipaddr 2>/dev/null
|
||||
uci -q delete network.lan.netmask 2>/dev/null
|
||||
uci -q delete network.lan.gateway 2>/dev/null
|
||||
uci -q delete network.lan.dns 2>/dev/null
|
||||
fi
|
||||
|
||||
uci set dhcp.lan.ignore=1
|
||||
uci -q delete dhcp.lan.dhcpv6 2>/dev/null
|
||||
uci -q delete dhcp.lan.ra 2>/dev/null
|
||||
uci -q delete dhcp.lan.ra_management 2>/dev/null
|
||||
uci commit network
|
||||
uci commit dhcp
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Wired backhaul
|
||||
# =========================================================
|
||||
setup_wired() {
|
||||
if [ -z "$WIRED_IF" ]; then
|
||||
# Clean up any previously-configured wired hardif
|
||||
if uci -q get network.nwi_mesh_wired >/dev/null 2>&1; then
|
||||
log "Wired disabled — removing nwi_mesh_wired config"
|
||||
uci -q delete network.nwi_mesh_wired
|
||||
uci commit network
|
||||
# Restore the port to its original bridge so LAN works again
|
||||
restore_wired_port
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
prevent_wired_loop "$WIRED_IF"
|
||||
save_original_ports
|
||||
|
||||
uci -q delete network.nwi_mesh_wired 2>/dev/null
|
||||
uci set network.nwi_mesh_wired=interface
|
||||
uci set network.nwi_mesh_wired.proto=batadv_hardif
|
||||
uci set network.nwi_mesh_wired.master=bat0
|
||||
uci set network.nwi_mesh_wired.mtu=1500
|
||||
uci set network.nwi_mesh_wired.device=$WIRED_IF
|
||||
uci commit network
|
||||
log "Wired backhaul: $WIRED_IF"
|
||||
}
|
||||
|
||||
|
||||
# =========================================================
|
||||
# Mesh VLANs — extend guest/IoT over mesh via 802.1Q
|
||||
# =========================================================
|
||||
setup_mesh_vlans() {
|
||||
# Remove any previous 8021q devices on bat0
|
||||
local idx=0 name typ ifn
|
||||
while name=$(uci -q get "network.@device[$idx].name"); do
|
||||
typ=$(uci -q get "network.@device[$idx].type")
|
||||
ifn=$(uci -q get "network.@device[$idx].device")
|
||||
if [ "$typ" = "8021q" ] && [ "$ifn" = "bat0" ]; then
|
||||
uci -q delete "network.@device[$idx]"
|
||||
continue
|
||||
fi
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
|
||||
if [ -z "$MESH_VLANS" ]; then
|
||||
uci commit network
|
||||
return 0
|
||||
fi
|
||||
|
||||
local vlan br_name
|
||||
for vlan in $(echo "$MESH_VLANS" | tr ',' ' '); do
|
||||
vlan=$(echo "$vlan" | tr -d ' ')
|
||||
case "$vlan" in
|
||||
''|*[!0-9]*) log "Skipping invalid VLAN: $vlan"; continue ;;
|
||||
esac
|
||||
|
||||
# Create bat0.<vid> as 802.1q device
|
||||
uci add network device >/dev/null
|
||||
uci set "network.@device[-1].name=bat0.$vlan"
|
||||
uci set "network.@device[-1].type=8021q"
|
||||
uci set "network.@device[-1].device=bat0"
|
||||
uci set "network.@device[-1].vid=$vlan"
|
||||
log "Mesh VLAN: bat0.$vlan created"
|
||||
|
||||
# Auto-add to matching bridge if it exists
|
||||
for br_name in "br-$vlan" "br-lan$vlan" "br-vlan$vlan" "br-guest$vlan" "br-iot$vlan"; do
|
||||
local bi=0 bn
|
||||
while bn=$(uci -q get "network.@device[$bi].name"); do
|
||||
if [ "$bn" = "$br_name" ]; then
|
||||
uci -q get "network.@device[$bi].ports" | grep -q "bat0.$vlan" || \
|
||||
uci add_list "network.@device[$bi].ports=bat0.$vlan"
|
||||
log "Mesh VLAN $vlan: attached to $br_name"
|
||||
bi=-1
|
||||
break
|
||||
fi
|
||||
bi=$((bi + 1))
|
||||
done
|
||||
[ "$bi" = "-1" ] && break
|
||||
done
|
||||
done
|
||||
|
||||
uci commit network
|
||||
}
|
||||
|
||||
|
||||
# =========================================================
|
||||
# Link priority (throughput_override in Kbit/s)
|
||||
# =========================================================
|
||||
apply_link_priority() {
|
||||
local tries=0
|
||||
local active=0
|
||||
while [ $tries -lt 45 ]; do
|
||||
active=$(batctl if 2>/dev/null | grep -c "active")
|
||||
[ "$active" -gt 0 ] && break
|
||||
sleep 2
|
||||
tries=$((tries + 1))
|
||||
done
|
||||
log "apply_link_priority: found $active active hardif(s) after $((tries*2))s"
|
||||
|
||||
if [ "$active" = "0" ]; then
|
||||
log "apply_link_priority: no hardifs — aborting"
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
|
||||
# Wired hardifs
|
||||
local iface
|
||||
for iface in $(batctl if 2>/dev/null | awk -F: '/active/ {print $1}'); do
|
||||
case "$iface" in
|
||||
eth*|lan*)
|
||||
batctl hardif "$iface" throughput_override "$PRIORITY_WIRED" 2>/dev/null
|
||||
log "Link priority: $iface = $PRIORITY_WIRED Kbit (wired)"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Wireless mesh — by frequency
|
||||
for iface in $(batctl if 2>/dev/null | awk -F: '/active/ {print $1}'); do
|
||||
case "$iface" in
|
||||
phy*-mesh*)
|
||||
local freq=$(iw dev "$iface" info 2>/dev/null | \
|
||||
sed -n 's/.*(\([0-9]*\) MHz).*/\1/p')
|
||||
[ -z "$freq" ] && continue
|
||||
|
||||
if [ "$freq" -lt 2500 ]; then
|
||||
batctl hardif "$iface" throughput_override "$PRIORITY_24G" 2>/dev/null
|
||||
log "Link priority: $iface = $PRIORITY_24G Kbit (2.4 GHz, $freq MHz)"
|
||||
elif [ "$freq" -lt 6000 ]; then
|
||||
batctl hardif "$iface" throughput_override "$PRIORITY_5G" 2>/dev/null
|
||||
log "Link priority: $iface = $PRIORITY_5G Kbit (5 GHz, $freq MHz)"
|
||||
else
|
||||
batctl hardif "$iface" throughput_override "$PRIORITY_6G" 2>/dev/null
|
||||
log "Link priority: $iface = $PRIORITY_6G Kbit (6 GHz, $freq MHz)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
# =========================================================
|
||||
# Cron fallback toggle
|
||||
# =========================================================
|
||||
apply_cron_setting() {
|
||||
local cron_file="/etc/crontabs/root"
|
||||
local enabled=$(uci -q get easymesh.config.cron_fallback)
|
||||
|
||||
[ -f "$cron_file" ] || {
|
||||
mkdir -p /etc/crontabs
|
||||
touch "$cron_file"
|
||||
}
|
||||
|
||||
# Remove any existing easymesh priority lines (active or commented)
|
||||
sed -i '\|easymesh priority|d' "$cron_file"
|
||||
|
||||
if [ "$enabled" = "1" ]; then
|
||||
echo "* * * * * /etc/init.d/easymesh priority >/dev/null 2>&1" >> "$cron_file"
|
||||
log "Cron fallback: ENABLED (every minute)"
|
||||
else
|
||||
echo "#* * * * * /etc/init.d/easymesh priority >/dev/null 2>&1" >> "$cron_file"
|
||||
log "Cron fallback: DISABLED (commented out)"
|
||||
fi
|
||||
|
||||
/etc/init.d/cron restart 2>/dev/null || true
|
||||
}
|
||||
|
||||
|
||||
# =========================================================
|
||||
# Enable / disable
|
||||
# =========================================================
|
||||
enable_mesh() {
|
||||
check_prereqs || { log "Prereq failed"; return 1; }
|
||||
|
||||
sanitize_for_role
|
||||
load_config
|
||||
|
||||
log "Enabling mesh (role=$ROLE, band=$BAND_MODE)"
|
||||
|
||||
local r
|
||||
for r in $(list_radios); do
|
||||
uci set "wireless.$r.disabled=0"
|
||||
done
|
||||
uci commit wireless
|
||||
|
||||
setup_bat0
|
||||
attach_bat0_to_lan
|
||||
|
||||
# Normalize band mode: strip the _wired suffix to select radio logic
|
||||
local BASE_BAND=$(echo "$BAND_MODE" | sed 's/_wired$//')
|
||||
|
||||
case "$BASE_BAND" in
|
||||
none)
|
||||
log "Wired-only mode — skipping wireless mesh setup"
|
||||
;;
|
||||
dual)
|
||||
local r5 r2
|
||||
r5=$(find_radio_by_band 5g)
|
||||
r2=$(find_radio_by_band 2g)
|
||||
[ -n "$r5" ] && setup_mesh_iface "$r5"
|
||||
[ -n "$r2" ] && setup_mesh_iface "$r2"
|
||||
;;
|
||||
single_2g)
|
||||
local r2b; r2b=$(find_radio_by_band 2g)
|
||||
[ -n "$r2b" ] && setup_mesh_iface "$r2b"
|
||||
;;
|
||||
custom)
|
||||
[ -n "$CUSTOM_RADIO" ] && setup_mesh_iface "$CUSTOM_RADIO"
|
||||
;;
|
||||
single_5g|*)
|
||||
local r5b; r5b=$(find_radio_by_band 5g)
|
||||
[ -n "$r5b" ] && setup_mesh_iface "$r5b"
|
||||
;;
|
||||
esac
|
||||
|
||||
setup_wired
|
||||
setup_mesh_vlans
|
||||
apply_kvr
|
||||
apply_client_ap
|
||||
apply_ap_mode
|
||||
|
||||
apply_cron_setting
|
||||
log "UCI config complete"
|
||||
}
|
||||
|
||||
disable_mesh() {
|
||||
log "Disabling mesh"
|
||||
|
||||
local r
|
||||
for r in $(list_radios); do
|
||||
uci -q delete "wireless.mesh_$r" 2>/dev/null
|
||||
uci -q delete "network.nwi_mesh_$r" 2>/dev/null
|
||||
done
|
||||
uci -q delete network.nwi_mesh_wired 2>/dev/null
|
||||
uci -q delete network.bat0 2>/dev/null
|
||||
uci commit wireless
|
||||
uci commit network
|
||||
|
||||
detach_bat0_from_lan
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# WPS pairing
|
||||
# =========================================================
|
||||
pair() {
|
||||
log "Pairing mode ($PAIR_TIMEOUT sec)"
|
||||
|
||||
backup_config
|
||||
|
||||
local orig_id=$(uci -q get easymesh.config.mesh_id)
|
||||
local orig_enc=$(uci -q get easymesh.config.encryption)
|
||||
local orig_key=$(uci -q get easymesh.config.key)
|
||||
echo "$orig_id|$orig_enc|$orig_key" > /tmp/easymesh-pair-orig
|
||||
|
||||
uci set easymesh.config.mesh_id=$PAIRING_MESH_ID
|
||||
uci set easymesh.config.encryption=0
|
||||
uci -q delete easymesh.config.key 2>/dev/null
|
||||
uci commit easymesh
|
||||
|
||||
load_config
|
||||
enable_mesh
|
||||
wifi reload
|
||||
|
||||
wait_and_attach_mesh
|
||||
|
||||
local led=$(ls /sys/class/leds/ 2>/dev/null | grep -iE 'wlan|wifi|status' | head -1)
|
||||
local start=$(date +%s)
|
||||
local peer_found=0
|
||||
|
||||
while [ $(( $(date +%s) - start )) -lt $PAIR_TIMEOUT ]; do
|
||||
if [ -d /sys/class/net/bat0 ]; then
|
||||
local n=$(batctl n 2>/dev/null | tail -n +2 | \
|
||||
grep -cE '([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}')
|
||||
if [ "$n" -gt 0 ]; then
|
||||
peer_found=1
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$led" ]; then
|
||||
echo 1 > /sys/class/leds/$led/brightness 2>/dev/null
|
||||
sleep 1
|
||||
echo 0 > /sys/class/leds/$led/brightness 2>/dev/null
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ "$peer_found" = "1" ]; then
|
||||
log "Peer joined — pairing successful"
|
||||
[ -n "$led" ] && echo 1 > /sys/class/leds/$led/brightness 2>/dev/null
|
||||
else
|
||||
log "Timed out — restoring"
|
||||
IFS='|' read -r orig_id orig_enc orig_key < /tmp/easymesh-pair-orig
|
||||
uci set easymesh.config.mesh_id=$orig_id
|
||||
uci set easymesh.config.encryption=$orig_enc
|
||||
[ -n "$orig_key" ] && uci set easymesh.config.key=$orig_key
|
||||
uci commit easymesh
|
||||
|
||||
load_config
|
||||
enable_mesh
|
||||
wifi reload
|
||||
wait_and_attach_mesh
|
||||
log "Restored"
|
||||
fi
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Rollback watchdog
|
||||
# =========================================================
|
||||
rollback_start() {
|
||||
local delay="${1:-60}"
|
||||
mkdir -p "$RB_DIR"
|
||||
rm -f "$RB_DIR/confirmed" "$RB_DIR/running"
|
||||
touch "$RB_DIR/running"
|
||||
|
||||
cp /etc/config/network "$RB_DIR/network.before"
|
||||
cp /etc/config/wireless "$RB_DIR/wireless.before"
|
||||
|
||||
log "Rollback watchdog: ${delay}s to confirm"
|
||||
|
||||
(
|
||||
sleep "$delay"
|
||||
[ -f "$RB_DIR/confirmed" ] && {
|
||||
log "Rollback: confirmed"
|
||||
rm -f "$RB_DIR/running"
|
||||
exit 0
|
||||
}
|
||||
log "Rollback: no confirmation — restoring"
|
||||
[ -f "$RB_DIR/network.before" ] && cp "$RB_DIR/network.before" /etc/config/network
|
||||
[ -f "$RB_DIR/wireless.before" ] && cp "$RB_DIR/wireless.before" /etc/config/wireless
|
||||
uci commit network
|
||||
uci commit wireless
|
||||
wifi reload
|
||||
/etc/init.d/network reload
|
||||
rm -f "$RB_DIR/running"
|
||||
) &
|
||||
}
|
||||
|
||||
rollback_confirm() {
|
||||
mkdir -p "$RB_DIR"
|
||||
touch "$RB_DIR/confirmed"
|
||||
rm -f "$RB_DIR/running"
|
||||
log "Rollback: user confirmed"
|
||||
}
|
||||
|
||||
rollback_cancel() {
|
||||
mkdir -p "$RB_DIR"
|
||||
log "Rollback: user cancelled"
|
||||
if [ -f "$RB_DIR/network.before" ]; then
|
||||
cp "$RB_DIR/network.before" /etc/config/network
|
||||
cp "$RB_DIR/wireless.before" /etc/config/wireless
|
||||
uci commit network
|
||||
uci commit wireless
|
||||
wifi reload
|
||||
/etc/init.d/network reload
|
||||
fi
|
||||
rm -f "$RB_DIR/running" "$RB_DIR/confirmed"
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
# Service entry points
|
||||
# =========================================================
|
||||
start() {
|
||||
load_config
|
||||
[ "$ENABLED" = "1" ] || { log "easymesh disabled"; return 0; }
|
||||
enable_mesh
|
||||
log "Reloading wireless..."
|
||||
wifi reload
|
||||
|
||||
wait_and_attach_mesh
|
||||
|
||||
[ -d /sys/class/net/bat0 ] && log "bat0 UP" || log "bat0 missing"
|
||||
apply_link_priority
|
||||
apply_cron_setting
|
||||
|
||||
# Second pass 90s after boot to catch late-arriving hardifs
|
||||
(
|
||||
sleep 90
|
||||
log "Priority: second pass (post-boot)"
|
||||
apply_link_priority
|
||||
) </dev/null >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
stop() {
|
||||
log "Stopping mesh (config preserved)"
|
||||
local r
|
||||
for r in $(list_radios); do
|
||||
ifdown "nwi_mesh_$r" 2>/dev/null
|
||||
done
|
||||
ifdown bat0 2>/dev/null
|
||||
}
|
||||
|
||||
restart() {
|
||||
load_config
|
||||
[ "$ENABLED" = "1" ] || { log "easymesh disabled"; return 0; }
|
||||
enable_mesh
|
||||
wifi reload
|
||||
|
||||
wait_and_attach_mesh
|
||||
|
||||
[ -d /sys/class/net/bat0 ] && log "bat0 UP" || log "bat0 missing"
|
||||
apply_link_priority
|
||||
apply_cron_setting
|
||||
}
|
||||
|
||||
reload() { restart; }
|
||||
|
||||
backup() { backup_config; }
|
||||
|
||||
restore() {
|
||||
restore_config
|
||||
wifi reload
|
||||
/etc/init.d/network reload
|
||||
}
|
||||
|
||||
detach_wired() {
|
||||
log "Detaching wired backhaul"
|
||||
restore_wired_port
|
||||
uci -q delete easymesh.config.wired_if 2>/dev/null
|
||||
uci -q delete easymesh.config.preferred_wired_port 2>/dev/null
|
||||
uci commit easymesh
|
||||
log "Wired backhaul removed"
|
||||
}
|
||||
|
||||
status() {
|
||||
if [ -d /sys/class/net/bat0 ]; then
|
||||
local n=$(batctl n 2>/dev/null | tail -n +2 | wc -l)
|
||||
local w=$(batctl if 2>/dev/null | grep -cE '^(eth|lan)')
|
||||
echo "easymesh: running - bat0 up, $n wireless + $w wired link(s)"
|
||||
return 0
|
||||
fi
|
||||
echo "easymesh: not running"
|
||||
return 1
|
||||
}
|
||||
|
||||
priority() {
|
||||
load_config
|
||||
apply_link_priority
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "easymesh"
|
||||
}
|
||||
|
||||
# --- File ends here. ---
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# EasyMesh pairing handler
|
||||
# Invoked by /etc/rc.button/wps → /etc/rc.wps/* dispatcher
|
||||
# On WPS press: switch to pairing mode for 2 minutes.
|
||||
|
||||
[ "$(uci -q get easymesh.config.enabled)" = "1" ] || exit 1
|
||||
|
||||
logger -t easymesh-button "WPS pressed → pairing mode"
|
||||
|
||||
/etc/init.d/easymesh pair >/tmp/easymesh-pair.log 2>&1 &
|
||||
|
||||
# Return success so the dispatcher stops scanning further hooks
|
||||
exit 0
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
[ -n "${IPKG_INSTROOT}" ] && exit 0
|
||||
|
||||
# Add easymesh backup dir to sysupgrade backups
|
||||
uci -q add_list system.@system[0].backup_path='/etc/easymesh-backups'
|
||||
uci commit system
|
||||
|
||||
# Ensure the backup dir exists
|
||||
mkdir -p /etc/easymesh-backups
|
||||
|
||||
# Enable the mesh service
|
||||
/etc/init.d/easymesh enable 2>/dev/null
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"admin/network/easymesh": {
|
||||
"title": "EASY MESH",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "cbi",
|
||||
"path": "easymesh"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-easymesh" ],
|
||||
"uci": { "easymesh": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"luci-app-easymesh": {
|
||||
"description": "Grant UCI access for luci-app-easymesh",
|
||||
"read": {
|
||||
"uci": [ "easymesh", "wireless", "network" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "easymesh" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user