2026-07-13 08:48:53 +05:30
|
|
|
#!/bin/sh
|
|
|
|
|
# shellcheck shell=sh
|
|
|
|
|
#
|
|
|
|
|
# zt-gateway-setup — configure this OpenWrt router as a ZeroTier exit
|
|
|
|
|
# gateway for the WIBLAN subnet (10.11.13.0/24).
|
|
|
|
|
#
|
|
|
|
|
# Subcommands:
|
|
|
|
|
# status Show current setup state
|
|
|
|
|
# setup-bridge Create the br-zt bridge interface
|
|
|
|
|
# setup-routing Configure policy routing (tables 100/101, ip rules)
|
|
|
|
|
# setup-dhcp Configure DHCP for WIBLAN on br-zt
|
|
|
|
|
# setup-hotplug Create hotplug script to re-apply routes on ifup
|
|
|
|
|
# setup-persistence Write rc.local + UCI network routes
|
|
|
|
|
# setup-all Run all setup-* commands in order
|
|
|
|
|
#
|
|
|
|
|
# Exit codes:
|
|
|
|
|
# 0 success
|
|
|
|
|
# 1 usage / argument error
|
|
|
|
|
# 2 precondition failed
|
|
|
|
|
# 3 runtime failure
|
|
|
|
|
#
|
|
|
|
|
# Environment overrides (for testing and non-default configs):
|
|
|
|
|
# ZTG_BRIDGE bridge device (default: br-zt)
|
2026-07-13 11:10:57 +05:30
|
|
|
# ZTG_BRIDGE_PORTS space-separated ports (default: auto-detect zt* interface)
|
2026-07-13 08:48:53 +05:30
|
|
|
# ZTG_WIBLAN_CIDR WIBLAN subnet (default: 10.11.13.0/24)
|
|
|
|
|
# ZTG_WIBLAN_GW WIBLAN gateway IP (default: 10.11.13.1)
|
2026-07-13 11:10:57 +05:30
|
|
|
# ZTG_BRIDGE_NETMASK bridge subnet mask (default: 255.255.254.0 /23)
|
2026-07-13 08:48:53 +05:30
|
|
|
# ZTG_WIBLAN_LEASE_FIRST first DHCP IP (default: 10.11.13.100)
|
|
|
|
|
# ZTG_WIBLAN_LEASE_LAST last DHCP IP (default: 10.11.13.200)
|
|
|
|
|
# ZTG_TABLE_MAIN main policy table (default: 100)
|
|
|
|
|
# ZTG_TABLE_DRAIN drain policy table (default: 101)
|
|
|
|
|
# ZTG_TABLE_MWAN mwan3 return table (default: 1)
|
|
|
|
|
# ZTG_FWMARK drain fwmark (default: 0x100)
|
|
|
|
|
# ZTG_DRAIN_PRIORITY drain rule priority (default: 99)
|
|
|
|
|
# ZTG_HOTPLUG hotplug script path
|
|
|
|
|
# ZTG_RCLOCAL rc.local path
|
|
|
|
|
# ZTG_DHCPCONF DHCP UCI config file (default: /etc/config/dhcp)
|
|
|
|
|
# ZTG_NETWORKCONF network UCI config file (default: /etc/config/network)
|
2026-07-13 11:10:57 +05:30
|
|
|
# ZTG_WIFI_SSID WiFi AP SSID (default: WIBLAN)
|
|
|
|
|
# ZTG_WIFI_KEY WiFi AP WPA2 key (default: zt-r0ute-2026)
|
|
|
|
|
# ZTG_WIFI_RADIO WiFi radio device (default: auto-detect first radio)
|
|
|
|
|
# ZTG_WIFI_ENCRYPTION WiFi encryption (default: psk2)
|
2026-07-13 08:48:53 +05:30
|
|
|
# ZTG_SKIP_PERSIST skip UCI persistence (testing)
|
|
|
|
|
|
|
|
|
|
set -eu
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# Config
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
BRIDGE="${ZTG_BRIDGE:-br-zt}"
|
2026-07-13 11:10:57 +05:30
|
|
|
# Auto-detect ZeroTier interface if not specified (zt + random suffix, not br-zt)
|
|
|
|
|
if [ -n "${ZTG_BRIDGE_PORTS:-}" ]; then
|
|
|
|
|
BRIDGE_PORTS="$ZTG_BRIDGE_PORTS"
|
|
|
|
|
else
|
|
|
|
|
BRIDGE_PORTS=$(ip -o link show 2>/dev/null \
|
|
|
|
|
| awk -F': ' '/^[0-9]+:/{gsub(/@.*/, "", $2); if ($2 ~ /^zt/ && $2 != "br-zt") print $2; exit}')
|
|
|
|
|
if [ -z "$BRIDGE_PORTS" ]; then
|
|
|
|
|
BRIDGE_PORTS="ztabc0"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
2026-07-13 08:48:53 +05:30
|
|
|
WIBLAN_CIDR="${ZTG_WIBLAN_CIDR:-10.11.13.0/24}"
|
|
|
|
|
WIBLAN_GW="${ZTG_WIBLAN_GW:-10.11.13.1}"
|
2026-07-13 11:10:57 +05:30
|
|
|
# Bridge netmask must be /23 to cover both ZeroTier (10.11.12.x) and WIBLAN (10.11.13.x)
|
|
|
|
|
BRIDGE_NETMASK="${ZTG_BRIDGE_NETMASK:-255.255.254.0}"
|
2026-07-13 08:48:53 +05:30
|
|
|
WIBLAN_LEASE_FIRST="${ZTG_WIBLAN_LEASE_FIRST:-10.11.13.100}"
|
|
|
|
|
WIBLAN_LEASE_LAST="${ZTG_WIBLAN_LEASE_LAST:-10.11.13.200}"
|
|
|
|
|
TABLE_MAIN="${ZTG_TABLE_MAIN:-100}"
|
|
|
|
|
TABLE_DRAIN="${ZTG_TABLE_DRAIN:-101}"
|
|
|
|
|
TABLE_MWAN="${ZTG_TABLE_MWAN:-1}"
|
|
|
|
|
FWMARK="${ZTG_FWMARK:-0x100}"
|
|
|
|
|
DRAIN_PRIORITY="${ZTG_DRAIN_PRIORITY:-99}"
|
|
|
|
|
HOTPLUG="${ZTG_HOTPLUG:-/etc/hotplug.d/net/99-zerotier-bridge}"
|
|
|
|
|
RCLOCAL="${ZTG_RCLOCAL:-/etc/rc.local}"
|
|
|
|
|
DHCPCONF="${ZTG_DHCPCONF:-/etc/config/dhcp}"
|
|
|
|
|
SKIP_PERSIST="${ZTG_SKIP_PERSIST:-0}"
|
2026-07-13 11:10:57 +05:30
|
|
|
WiFi_SSID="${ZTG_WIFI_SSID:-WIBLAN}"
|
|
|
|
|
WiFi_KEY="${ZTG_WIFI_KEY:-zt-r0ute-2026}"
|
|
|
|
|
WiFi_ENCRYPTION="${ZTG_WIFI_ENCRYPTION:-psk2}"
|
|
|
|
|
# Auto-detect first WiFi radio if not specified
|
|
|
|
|
WiFi_RADIO="${ZTG_WIFI_RADIO:-}"
|
|
|
|
|
if [ -z "$WiFi_RADIO" ]; then
|
|
|
|
|
WiFi_RADIO=$(uci -q get wireless.@wifi-device[0].name 2>/dev/null || echo "radio0")
|
|
|
|
|
fi
|
2026-07-13 08:48:53 +05:30
|
|
|
|
|
|
|
|
# Derived: extract prefix bits from CIDR
|
|
|
|
|
WIBLAN_BITS="${WIBLAN_CIDR##*/}"
|
|
|
|
|
# UCI-safe section name (replace hyphens with underscores)
|
|
|
|
|
BRIDGE_UCI=$(printf '%s' "$BRIDGE" | tr '-' '_')
|
2026-07-13 11:10:57 +05:30
|
|
|
# Interface name (used by DHCP and WiFi AP, must reference interface not device)
|
|
|
|
|
WIBLAN_IFACE="zt_wiblan"
|
2026-07-13 08:48:53 +05:30
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# Logging
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
log() { printf '[zt-gateway-setup] %s\n' "$*" >&2; }
|
|
|
|
|
die() { rc=$1; shift; log "ERROR: $*"; exit "$rc"; }
|
|
|
|
|
# Ensure a UCI config file exists (touch it if missing)
|
|
|
|
|
ensure_uci_config() {
|
|
|
|
|
_conf=/etc/config/"$1"
|
|
|
|
|
if [ ! -f "$_conf" ]; then
|
|
|
|
|
touch "$_conf"
|
|
|
|
|
log "created empty UCI config: ${_conf}"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-07-13 11:10:57 +05:30
|
|
|
# CIDR to dotted mask (e.g. 24 -> 255.255.255.0)
|
2026-07-13 08:48:53 +05:30
|
|
|
# CIDR to dotted mask (e.g. 24 -> 255.255.255.0)
|
|
|
|
|
_cidr_to_mask() {
|
|
|
|
|
bits=$1
|
2026-07-13 11:10:57 +05:30
|
|
|
# Build octets from the CIDR prefix
|
|
|
|
|
octets=""
|
2026-07-13 08:48:53 +05:30
|
|
|
while [ "$bits" -gt 0 ]; do
|
|
|
|
|
if [ "$bits" -ge 8 ]; then
|
|
|
|
|
oct=255
|
|
|
|
|
bits=$((bits - 8))
|
|
|
|
|
else
|
|
|
|
|
oct=0
|
|
|
|
|
j=0
|
|
|
|
|
while [ $j -lt "$bits" ]; do
|
|
|
|
|
oct=$(( oct | (1 << (7 - j)) ))
|
|
|
|
|
j=$((j + 1))
|
|
|
|
|
done
|
|
|
|
|
bits=0
|
|
|
|
|
fi
|
2026-07-13 11:10:57 +05:30
|
|
|
if [ -n "$octets" ]; then
|
|
|
|
|
octets="${octets}.${oct}"
|
2026-07-13 08:48:53 +05:30
|
|
|
else
|
2026-07-13 11:10:57 +05:30
|
|
|
octets="${oct}"
|
2026-07-13 08:48:53 +05:30
|
|
|
fi
|
|
|
|
|
done
|
2026-07-13 11:10:57 +05:30
|
|
|
# Pad remaining octets with 0
|
|
|
|
|
while [ "$(printf '%s' "$octets" | tr -cd '.' | wc -c)" -lt 3 ]; do
|
|
|
|
|
octets="${octets}.0"
|
|
|
|
|
done
|
|
|
|
|
printf '%s' "$octets"
|
2026-07-13 08:48:53 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# Status
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
cmd_status() {
|
|
|
|
|
log "checking setup status..."
|
|
|
|
|
|
|
|
|
|
# Bridge
|
|
|
|
|
if ip link show "$BRIDGE" >/dev/null 2>&1; then
|
|
|
|
|
printf 'bridge: %s (up)\n' "$BRIDGE"
|
|
|
|
|
# List ports
|
|
|
|
|
ports=$(ip link show master "$BRIDGE" 2>/dev/null \
|
|
|
|
|
| awk -F': ' '/^[0-9]+:/{gsub(/@.*/, "", $2); print $2}' \
|
|
|
|
|
| tr '\n' ' ')
|
|
|
|
|
if [ -n "$ports" ]; then
|
|
|
|
|
printf ' ports: %s\n' "$ports"
|
|
|
|
|
fi
|
|
|
|
|
# IP on bridge
|
|
|
|
|
br_addr=$(ip -4 addr show dev "$BRIDGE" 2>/dev/null \
|
|
|
|
|
| awk '/inet /{gsub(/\/.*/, "", $2); print $2; exit}')
|
|
|
|
|
if [ -n "$br_addr" ]; then
|
|
|
|
|
printf ' addr: %s\n' "$br_addr"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
printf 'bridge: %s (missing)\n' "$BRIDGE"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Routing tables
|
|
|
|
|
table_main_gw=$(ip route show table "$TABLE_MAIN" 2>/dev/null \
|
|
|
|
|
| awk '/^[[:space:]]*default/{
|
|
|
|
|
for (i=1; i<=NF; i++) if ($i=="via") { print $(i+1); exit }
|
|
|
|
|
}')
|
|
|
|
|
if [ -n "$table_main_gw" ]; then
|
|
|
|
|
printf 'table %s: default via %s (configured)\n' "$TABLE_MAIN" "$table_main_gw"
|
|
|
|
|
else
|
|
|
|
|
printf 'table %s: (empty)\n' "$TABLE_MAIN"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
table_drain_gw=$(ip route show table "$TABLE_DRAIN" 2>/dev/null \
|
|
|
|
|
| awk '/^[[:space:]]*default/{
|
|
|
|
|
for (i=1; i<=NF; i++) if ($i=="via") { print $(i+1); exit }
|
|
|
|
|
}')
|
|
|
|
|
if [ -n "$table_drain_gw" ]; then
|
|
|
|
|
printf 'table %s: default via %s (configured)\n' "$TABLE_DRAIN" "$table_drain_gw"
|
|
|
|
|
else
|
|
|
|
|
printf 'table %s: (empty)\n' "$TABLE_DRAIN"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ip rule for fwmark
|
|
|
|
|
if ip rule show 2>/dev/null | grep -q "fwmark ${FWMARK}"; then
|
|
|
|
|
printf 'ip rule: fwmark %s -> table %s (configured)\n' "$FWMARK" "$TABLE_DRAIN"
|
|
|
|
|
else
|
|
|
|
|
printf 'ip rule: fwmark %s (missing)\n' "$FWMARK"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# DHCP
|
|
|
|
|
if grep -q "interface '${BRIDGE}'" "$DHCPCONF" 2>/dev/null; then
|
|
|
|
|
printf 'dhcp: %s configured in %s\n' "$BRIDGE" "$DHCPCONF"
|
|
|
|
|
else
|
|
|
|
|
printf 'dhcp: %s (not configured)\n' "$BRIDGE"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Hotplug
|
|
|
|
|
if [ -x "$HOTPLUG" ] || [ -f "$HOTPLUG" ]; then
|
|
|
|
|
printf 'hotplug: %s (present)\n' "$HOTPLUG"
|
|
|
|
|
else
|
|
|
|
|
printf 'hotplug: %s (missing)\n' "$HOTPLUG"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Persistence in rc.local
|
|
|
|
|
if grep -q "zt-gateway" "$RCLOCAL" 2>/dev/null; then
|
|
|
|
|
printf 'rc.local: entries present\n'
|
|
|
|
|
else
|
|
|
|
|
printf 'rc.local: no zt-gateway entries\n'
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# setup-bridge
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
cmd_setup_bridge() {
|
|
|
|
|
log "setting up bridge ${BRIDGE}..."
|
|
|
|
|
|
|
|
|
|
if ! command -v uci >/dev/null 2>&1; then
|
|
|
|
|
die 3 "uci not found; cannot configure bridge"
|
|
|
|
|
fi
|
|
|
|
|
ensure_uci_config network
|
|
|
|
|
|
|
|
|
|
# Create or update bridge device in network.uci
|
|
|
|
|
# Note: UCI section names cannot contain hyphens, so we use BRIDGE_UCI
|
|
|
|
|
if uci -q get "network.${BRIDGE_UCI}" >/dev/null 2>&1; then
|
|
|
|
|
log "bridge device ${BRIDGE} already exists in UCI; updating"
|
|
|
|
|
else
|
|
|
|
|
uci -q set "network.${BRIDGE_UCI}=device"
|
|
|
|
|
uci -q set "network.${BRIDGE_UCI}.type=bridge"
|
|
|
|
|
uci -q set "network.${BRIDGE_UCI}.name=${BRIDGE}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Set bridge ports (space-separated in UCI list)
|
|
|
|
|
uci -q delete "network.${BRIDGE_UCI}.ports" 2>/dev/null || true
|
|
|
|
|
for port in $BRIDGE_PORTS; do
|
|
|
|
|
uci -q add_list "network.${BRIDGE_UCI}.ports=${port}"
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# Create interface section bridging to br-zt for WIBLAN
|
|
|
|
|
if ! uci -q get "network.zt_wiblan" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "network.zt_wiblan=interface"
|
2026-07-13 11:10:57 +05:30
|
|
|
uci -q set "network.zt_wiblan.proto=static"
|
|
|
|
|
uci -q set "network.zt_wiblan.device=${BRIDGE}"
|
|
|
|
|
uci -q set "network.zt_wiblan.ipaddr=${WIBLAN_GW}"
|
|
|
|
|
uci -q set "network.zt_wiblan.netmask=${BRIDGE_NETMASK}"
|
2026-07-13 08:48:53 +05:30
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ "$SKIP_PERSIST" != "1" ]; then
|
|
|
|
|
uci commit network
|
|
|
|
|
log "bridge UCI config committed"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Bring up the bridge (best-effort; may need netifd restart)
|
|
|
|
|
if command -v ifup >/dev/null 2>&1; then
|
|
|
|
|
ifup "zt_wiblan" 2>/dev/null || \
|
|
|
|
|
log "warning: ifup zt_wiblan failed; may need 'service network restart'"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-13 11:10:57 +05:30
|
|
|
# Ensure ZeroTier-assigned IP stays on the ZT interface (needed for ARP)
|
|
|
|
|
# When ZT interface is a bridge port, the assigned IP can be lost
|
|
|
|
|
zt_ip=$(zerotier-cli listnetworks 2>/dev/null \
|
|
|
|
|
| awk '/OK/{for(i=6;i<=NF;i++) if($i ~ /\//) {split($i,a,"/"); print a[1]; exit}}')
|
|
|
|
|
zt_bits=$(zerotier-cli listnetworks 2>/dev/null \
|
|
|
|
|
| awk '/OK/{for(i=6;i<=NF;i++) if($i ~ /\//) {split($i,a,"/"); print a[2]; exit}}')
|
|
|
|
|
if [ -n "$zt_ip" ] && [ -n "$zt_bits" ]; then
|
|
|
|
|
if ! ip -4 addr show dev "$BRIDGE_PORTS" 2>/dev/null | grep -q "$zt_ip"; then
|
|
|
|
|
ip addr add "${zt_ip}/${zt_bits}" dev "$BRIDGE_PORTS" 2>/dev/null || \
|
|
|
|
|
log "warning: could not add ZT IP ${zt_ip}/${zt_bits} to ${BRIDGE_PORTS}"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
# Add bridge interface to firewall LAN zone (needed for nftables fw4)
|
|
|
|
|
if command -v uci >/dev/null 2>&1 && [ "$SKIP_PERSIST" != "1" ]; then
|
|
|
|
|
lan_zone=$(uci -q get firewall.@zone[0].name 2>/dev/null)
|
|
|
|
|
if [ "$lan_zone" = "lan" ]; then
|
|
|
|
|
if ! uci -q get firewall.@zone[0].network 2>/dev/null | grep -q "zt_wiblan"; then
|
|
|
|
|
uci -q add_list "firewall.@zone[0].network=zt_wiblan"
|
|
|
|
|
uci -q commit firewall
|
|
|
|
|
log "added zt_wiblan to firewall LAN zone"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-13 08:48:53 +05:30
|
|
|
log "bridge ${BRIDGE} setup complete"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# setup-routing
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
cmd_setup_routing() {
|
|
|
|
|
log "setting up routing (tables ${TABLE_MAIN}/${TABLE_DRAIN})..."
|
|
|
|
|
|
|
|
|
|
# Host route to WIBLAN gateway via bridge
|
|
|
|
|
ip route replace "$WIBLAN_GW" dev "$BRIDGE" 2>/dev/null || \
|
|
|
|
|
log "warning: host route to ${WIBLAN_GW} failed"
|
|
|
|
|
|
2026-07-13 11:10:57 +05:30
|
|
|
# Table 100 (main policy): default via exit gateway + direct WIBLAN subnet route
|
|
|
|
|
# The exit gateway is the ZeroTier peer that has internet access (not WIBLAN_GW which is local)
|
|
|
|
|
EXIT_GW=$(uci -q get zt-gateway.global.active_ip 2>/dev/null || \
|
|
|
|
|
uci -q get zt-gateway.@gateway[0].ip 2>/dev/null || \
|
|
|
|
|
ip route show table "$TABLE_MWAN" 2>/dev/null \
|
|
|
|
|
| awk '/default/{for(i=1;i<=NF;i++) if($i=="via") {print $(i+1); exit}}')
|
|
|
|
|
if [ -z "$EXIT_GW" ]; then
|
|
|
|
|
# Fallback: find ZeroTier peer IP on the same /23
|
|
|
|
|
EXIT_GW=$(ip route show table "$TABLE_MWAN" 2>/dev/null \
|
|
|
|
|
| awk '/via.*dev/{for(i=1;i<=NF;i++) if($i=="via") {print $(i+1); exit}}')
|
|
|
|
|
fi
|
|
|
|
|
if [ -n "$EXIT_GW" ]; then
|
|
|
|
|
log "exit gateway: ${EXIT_GW}"
|
|
|
|
|
ip route replace default via "$EXIT_GW" dev "$BRIDGE_PORTS" table "$TABLE_MAIN"
|
|
|
|
|
ip route replace "$WIBLAN_CIDR" dev "$BRIDGE" table "$TABLE_MAIN"
|
2026-07-13 08:48:53 +05:30
|
|
|
|
2026-07-13 11:10:57 +05:30
|
|
|
# Table 101 (drain): default via exit gateway
|
|
|
|
|
ip route replace default via "$EXIT_GW" dev "$BRIDGE_PORTS" table "$TABLE_DRAIN"
|
|
|
|
|
else
|
|
|
|
|
log "warning: could not determine exit gateway; using WIBLAN_GW"
|
|
|
|
|
ip route replace default via "$WIBLAN_GW" dev "$BRIDGE" table "$TABLE_MAIN"
|
|
|
|
|
ip route replace "$WIBLAN_CIDR" dev "$BRIDGE" table "$TABLE_MAIN"
|
|
|
|
|
ip route replace default via "$WIBLAN_GW" dev "$BRIDGE" table "$TABLE_DRAIN"
|
|
|
|
|
fi
|
2026-07-13 08:48:53 +05:30
|
|
|
|
|
|
|
|
# mwan3 return-traffic table: route WIBLAN back through bridge
|
|
|
|
|
ip route replace "$WIBLAN_CIDR" dev "$BRIDGE" table "$TABLE_MWAN"
|
|
|
|
|
|
2026-07-13 11:10:57 +05:30
|
|
|
# ip rule: WIBLAN subnet -> main policy table
|
|
|
|
|
ip rule add from "$WIBLAN_CIDR" table "$TABLE_MAIN" priority 100 2>/dev/null || \
|
|
|
|
|
ip rule replace from "$WIBLAN_CIDR" table "$TABLE_MAIN" priority 100
|
|
|
|
|
|
2026-07-13 08:48:53 +05:30
|
|
|
# ip rule: fwmark 0x100 -> drain table
|
|
|
|
|
ip rule add fwmark "$FWMARK" table "$TABLE_DRAIN" priority "$DRAIN_PRIORITY" 2>/dev/null || \
|
|
|
|
|
ip rule replace fwmark "$FWMARK" table "$TABLE_DRAIN" priority "$DRAIN_PRIORITY"
|
|
|
|
|
|
|
|
|
|
log "routing setup complete"
|
|
|
|
|
|
|
|
|
|
# Persist to UCI
|
|
|
|
|
if [ "$SKIP_PERSIST" != "1" ] && command -v uci >/dev/null 2>&1; then
|
|
|
|
|
ensure_uci_config network
|
|
|
|
|
# Host route
|
|
|
|
|
if ! uci -q get "network.zt_wiblan_host" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "network.zt_wiblan_host=route"
|
2026-07-13 11:10:57 +05:30
|
|
|
uci -q set "network.zt_wiblan_host.target=${WIBLAN_GW}"
|
|
|
|
|
uci -q set "network.zt_wiblan_host.interface=${BRIDGE}"
|
2026-07-13 08:48:53 +05:30
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Main policy table default route
|
|
|
|
|
if ! uci -q get "network.zt_wiblan_default" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "network.zt_wiblan_default=route"
|
2026-07-13 11:10:57 +05:30
|
|
|
uci -q set "network.zt_wiblan_default.target=0.0.0.0"
|
|
|
|
|
uci -q set "network.zt_wiblan_default.netmask=0.0.0.0"
|
|
|
|
|
uci -q set "network.zt_wiblan_default.gateway=${WIBLAN_GW}"
|
|
|
|
|
uci -q set "network.zt_wiblan_default.interface=${BRIDGE}"
|
|
|
|
|
uci -q set "network.zt_wiblan_default.table=${TABLE_MAIN}"
|
2026-07-13 08:48:53 +05:30
|
|
|
fi
|
|
|
|
|
|
2026-07-13 11:10:57 +05:30
|
|
|
# WIBLAN subnet route in main policy table
|
|
|
|
|
if ! uci -q get "network.zt_wiblan_subnet" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "network.zt_wiblan_subnet=route"
|
|
|
|
|
uci -q set "network.zt_wiblan_subnet.target=${WIBLAN_CIDR}"
|
|
|
|
|
uci -q set "network.zt_wiblan_subnet.netmask=$(_cidr_to_mask "$WIBLAN_BITS")"
|
|
|
|
|
uci -q set "network.zt_wiblan_subnet.interface=${BRIDGE}"
|
|
|
|
|
uci -q set "network.zt_wiblan_subnet.table=${TABLE_MAIN}"
|
|
|
|
|
fi
|
2026-07-13 08:48:53 +05:30
|
|
|
uci commit network
|
|
|
|
|
log "routing UCI config committed"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# setup-dhcp
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
cmd_setup_dhcp() {
|
|
|
|
|
log "setting up DHCP for ${BRIDGE} (${WIBLAN_CIDR})..."
|
|
|
|
|
|
|
|
|
|
if ! command -v uci >/dev/null 2>&1; then
|
|
|
|
|
die 3 "uci not found; cannot configure DHCP"
|
|
|
|
|
fi
|
|
|
|
|
ensure_uci_config dhcp
|
|
|
|
|
|
|
|
|
|
# Create DHCP subnet entry for br-zt
|
|
|
|
|
# Note: UCI section names cannot contain hyphens, so we use BRIDGE_UCI
|
|
|
|
|
if uci -q get "dhcp.${BRIDGE_UCI}" >/dev/null 2>&1; then
|
|
|
|
|
log "DHCP entry for ${BRIDGE} already exists; updating"
|
|
|
|
|
else
|
|
|
|
|
uci -q set "dhcp.${BRIDGE_UCI}=dhcp"
|
|
|
|
|
fi
|
2026-07-13 11:10:57 +05:30
|
|
|
uci -q set "dhcp.${BRIDGE_UCI}.interface=${WIBLAN_IFACE}"
|
2026-07-13 08:48:53 +05:30
|
|
|
uci -q set "dhcp.${BRIDGE_UCI}.start=${WIBLAN_LEASE_FIRST##*.}"
|
|
|
|
|
uci -q set "dhcp.${BRIDGE_UCI}.limit=$(( ${WIBLAN_LEASE_LAST##*.} - ${WIBLAN_LEASE_FIRST##*.} + 1 ))"
|
|
|
|
|
uci -q set "dhcp.${BRIDGE_UCI}.leasetime=12h"
|
2026-07-13 11:10:57 +05:30
|
|
|
# Provide gateway and DNS to DHCP clients
|
|
|
|
|
uci -q set "dhcp.${BRIDGE_UCI}.dhcp_option=3,${WIBLAN_GW}"
|
|
|
|
|
uci -q add_list "dhcp.${BRIDGE_UCI}.dhcp_option=6,8.8.8.8,1.1.1.1"
|
2026-07-13 08:48:53 +05:30
|
|
|
|
2026-07-13 11:10:57 +05:30
|
|
|
# Disable DHCP on LAN to prevent conflicting leases
|
|
|
|
|
if ! uci -q test dhcp.lan.ignore >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "dhcp.lan.ignore=1"
|
2026-07-13 08:48:53 +05:30
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ "$SKIP_PERSIST" != "1" ]; then
|
|
|
|
|
uci commit dhcp
|
|
|
|
|
log "DHCP UCI config committed"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Restart dnsmasq to pick up changes
|
|
|
|
|
if command -v service >/dev/null 2>&1; then
|
|
|
|
|
service dnsmasq restart 2>/dev/null || \
|
|
|
|
|
log "warning: dnsmasq restart failed; do it manually"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log "DHCP setup complete"
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 11:10:57 +05:30
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# setup-wifi-ap
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
cmd_setup_wifi_ap() {
|
|
|
|
|
log "setting up WiFi AP (${WiFi_SSID}) on ${WiFi_RADIO}..."
|
|
|
|
|
|
|
|
|
|
if ! command -v uci >/dev/null 2>&1; then
|
|
|
|
|
die 3 "uci not found; cannot configure WiFi"
|
|
|
|
|
fi
|
|
|
|
|
ensure_uci_config wireless
|
|
|
|
|
|
|
|
|
|
# Find existing wifinet section for our SSID, or create new one
|
|
|
|
|
existing=""
|
|
|
|
|
for idx in 0 1 2 3 4 5 6 7 8 9; do
|
|
|
|
|
if uci -q get "wireless.wifinet${idx}.ssid" 2>/dev/null | grep -q "^${WiFi_SSID}$"; then
|
|
|
|
|
existing="wifinet${idx}"
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [ -n "$existing" ]; then
|
|
|
|
|
log "WiFi AP '${WiFi_SSID}' already exists (${existing}); updating"
|
|
|
|
|
section="$existing"
|
|
|
|
|
else
|
|
|
|
|
section=$(uci -q add wireless wifi-iface)
|
|
|
|
|
log "created new wireless section: ${section}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
uci -q set "wireless.${section}.device=${WiFi_RADIO}"
|
|
|
|
|
uci -q set "wireless.${section}.mode=ap"
|
|
|
|
|
uci -q set "wireless.${section}.ssid=${WiFi_SSID}"
|
|
|
|
|
uci -q set "wireless.${section}.encryption=${WiFi_ENCRYPTION}"
|
|
|
|
|
uci -q set "wireless.${section}.key=${WiFi_KEY}"
|
|
|
|
|
uci -q set "wireless.${section}.network=${WIBLAN_IFACE}"
|
|
|
|
|
uci -q set "wireless.${section}.wpa_disable_eapol_key_retries=1"
|
|
|
|
|
|
|
|
|
|
if [ "$SKIP_PERSIST" != "1" ]; then
|
|
|
|
|
uci commit wireless
|
|
|
|
|
log "wireless UCI config committed"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log "WiFi AP setup complete: ssid=${WiFi_SSID} radio=${WiFi_RADIO} bridge=${BRIDGE}"
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 08:48:53 +05:30
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# setup-hotplug
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
cmd_setup_hotplug() {
|
|
|
|
|
log "creating hotplug script ${HOTPLUG}..."
|
|
|
|
|
|
|
|
|
|
mkdir -p "$(dirname "$HOTPLUG")"
|
|
|
|
|
|
|
|
|
|
cat >"$HOTPLUG" <<'HOTPLUG_SCRIPT'
|
|
|
|
|
#!/bin/sh
|
|
|
|
|
# shellcheck shell=sh
|
|
|
|
|
# ZeroTier gateway hotplug — re-apply routing when the bridge interface
|
|
|
|
|
# comes up (e.g. after boot, after ZeroTier restart).
|
|
|
|
|
#
|
|
|
|
|
# Environment: INTERFACE, ACTION (set by netifd hotplug)
|
|
|
|
|
# Config: /etc/config/zt-gateway (read at runtime for active gateway IP)
|
|
|
|
|
|
|
|
|
|
ZTG_BRIDGE="${ZTG_BRIDGE:-br-zt}"
|
|
|
|
|
ZTG_TABLE_MAIN="${ZTG_TABLE_MAIN:-100}"
|
|
|
|
|
ZTG_TABLE_DRAIN="${ZTG_TABLE_DRAIN:-101}"
|
|
|
|
|
ZTG_TABLE_MWAN="${ZTG_TABLE_MWAN:-1}"
|
|
|
|
|
ZTG_FWMARK="${ZTG_FWMARK:-0x100}"
|
|
|
|
|
ZTG_DRAIN_PRIORITY="${ZTG_DRAIN_PRIORITY:-99}"
|
|
|
|
|
ZTG_WIBLAN_CIDR="${ZTG_WIBLAN_CIDR:-10.11.13.0/24}"
|
|
|
|
|
|
|
|
|
|
# Only act on our bridge interface
|
|
|
|
|
[ "$INTERFACE" = "$ZTG_BRIDGE" ] || exit 0
|
|
|
|
|
|
|
|
|
|
case "$ACTION" in
|
|
|
|
|
ifup)
|
2026-07-13 11:10:57 +05:30
|
|
|
# Ensure ZeroTier-assigned IP stays on the ZT interface (needed for ARP)
|
|
|
|
|
zt_if=$(ip -o link show 2>/dev/null | awk -F': ' '/^[0-9]+:/{gsub(/@.*/, "", $2); if ($2 ~ /^zt/ && $2 != "'"$ZTG_BRIDGE"'") print $2; exit}')
|
|
|
|
|
if [ -n "$zt_if" ]; then
|
|
|
|
|
zt_ip=$(zerotier-cli listnetworks 2>/dev/null \
|
|
|
|
|
| awk '/OK/{for(i=6;i<=NF;i++) if($i ~ /\//) {split($i,a,"/"); print a[1]; exit}}')
|
|
|
|
|
zt_bits=$(zerotier-cli listnetworks 2>/dev/null \
|
|
|
|
|
| awk '/OK/{for(i=6;i<=NF;i++) if($i ~ /\//) {split($i,a,"/"); print a[2]; exit}}')
|
|
|
|
|
if [ -n "$zt_ip" ] && [ -n "$zt_bits" ] && \
|
|
|
|
|
! ip -4 addr show dev "$zt_if" 2>/dev/null | grep -q "$zt_ip"; then
|
|
|
|
|
ip addr add "${zt_ip}/${zt_bits}" dev "$zt_if" 2>/dev/null
|
|
|
|
|
logger -t zt-gw-hotplug "added ZT IP ${zt_ip}/${zt_bits} to ${zt_if}"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-13 08:48:53 +05:30
|
|
|
# Read active gateway from UCI
|
|
|
|
|
active_ip=$(uci -q get zt-gateway.global.active_ip 2>/dev/null || \
|
|
|
|
|
uci -q get zt-gateway.global.active_gateway 2>/dev/null)
|
|
|
|
|
|
|
|
|
|
# If active_ip is a region name, resolve to IP via gateway section
|
|
|
|
|
if [ -n "$active_ip" ] && ! echo "$active_ip" | grep -q '^[0-9]'; then
|
|
|
|
|
active_ip=$(uci -q get "zt-gateway.@gateway[0].ip" 2>/dev/null || \
|
|
|
|
|
for sec in $(uci -q show zt-gateway 2>/dev/null | \
|
|
|
|
|
awk -F'=' '/\.region=/{print $1}' | \
|
|
|
|
|
sed 's/\.region//'); do
|
|
|
|
|
r=$(uci -q get "${sec}.region" 2>/dev/null)
|
|
|
|
|
if [ "$r" = "$active_ip" ]; then
|
|
|
|
|
uci -q get "${sec}.ip" 2>/dev/null
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done)
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ -z "$active_ip" ]; then
|
|
|
|
|
# No active gateway configured; try reading from table
|
|
|
|
|
active_ip=$(ip route show table "$ZTG_TABLE_MAIN" 2>/dev/null \
|
|
|
|
|
| awk '/^[[:space:]]*default/{
|
|
|
|
|
for (i=1; i<=NF; i++) if ($i=="via") { print $(i+1); exit }
|
|
|
|
|
}')
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ -z "$active_ip" ]; then
|
|
|
|
|
logger -t zt-gw-hotplug "No active gateway IP; skipping route setup"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
logger -t zt-gw-hotplug "ifup ${ZTG_BRIDGE}: applying routes via ${active_ip}"
|
|
|
|
|
|
|
|
|
|
# Host route
|
|
|
|
|
ip route replace "$active_ip" dev "$ZTG_BRIDGE"
|
|
|
|
|
|
|
|
|
|
# Policy routes
|
|
|
|
|
ip route replace default via "$active_ip" dev "$ZTG_BRIDGE" table "$ZTG_TABLE_MAIN"
|
|
|
|
|
ip route replace default via "$active_ip" dev "$ZTG_BRIDGE" table "$ZTG_TABLE_DRAIN"
|
|
|
|
|
|
|
|
|
|
# mwan3 return
|
|
|
|
|
ip route replace "$ZTG_WIBLAN_CIDR" dev "$ZTG_BRIDGE" table "$ZTG_TABLE_MWAN"
|
|
|
|
|
|
|
|
|
|
# ip rule for drain fwmark
|
|
|
|
|
ip rule add fwmark "$ZTG_FWMARK" table "$ZTG_TABLE_DRAIN" \
|
|
|
|
|
priority "$ZTG_DRAIN_PRIORITY" 2>/dev/null || \
|
|
|
|
|
ip rule replace fwmark "$ZTG_FWMARK" table "$ZTG_TABLE_DRAIN" \
|
|
|
|
|
priority "$ZTG_DRAIN_PRIORITY"
|
|
|
|
|
;;
|
|
|
|
|
ifdown)
|
|
|
|
|
logger -t zt-gw-hotplug "ifdown ${ZTG_BRIDGE}: cleaning up"
|
|
|
|
|
ip rule del fwmark "$ZTG_FWMARK" table "$ZTG_TABLE_DRAIN" \
|
|
|
|
|
priority "$ZTG_DRAIN_PRIORITY" 2>/dev/null || true
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
HOTPLUG_SCRIPT
|
|
|
|
|
|
|
|
|
|
chmod +x "$HOTPLUG"
|
|
|
|
|
log "hotplug script created at ${HOTPLUG}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# setup-persistence
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
cmd_setup_persistence() {
|
|
|
|
|
log "setting up persistence..."
|
|
|
|
|
|
|
|
|
|
# Ensure rc.local has the gateway restoration logic
|
|
|
|
|
if ! grep -q "zt-gateway" "$RCLOCAL" 2>/dev/null; then
|
|
|
|
|
log "adding zt-gateway entry to ${RCLOCAL}"
|
|
|
|
|
|
|
|
|
|
# Read current rc.local content
|
|
|
|
|
rc_content=""
|
|
|
|
|
if [ -f "$RCLOCAL" ]; then
|
|
|
|
|
rc_content=$(cat "$RCLOCAL")
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Remove trailing 'exit 0' if present, add our block, re-add exit 0
|
|
|
|
|
rc_stripped=$(printf '%s\n' "$rc_content" | sed '/^exit 0$/d')
|
|
|
|
|
|
|
|
|
|
cat >"$RCLOCAL" <<RCEOF
|
|
|
|
|
${rc_stripped}
|
|
|
|
|
# --- zt-gateway: restore routes on boot ---
|
|
|
|
|
# Applied via /etc/hotplug.d/net/99-zerotier-bridge on ifup
|
|
|
|
|
# This block ensures the bridge comes up at boot
|
|
|
|
|
[ -x /etc/init.d/network ] && /etc/init.d/network reload
|
|
|
|
|
|
|
|
|
|
exit 0
|
|
|
|
|
RCEOF
|
|
|
|
|
log "rc.local updated"
|
|
|
|
|
else
|
|
|
|
|
log "rc.local already contains zt-gateway entries; skipping"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Write active gateway IP to UCI for the hotplug script
|
|
|
|
|
if [ "$SKIP_PERSIST" != "1" ] && command -v uci >/dev/null 2>&1; then
|
|
|
|
|
ensure_uci_config zt-gateway
|
|
|
|
|
# Store the WIBLAN config for the hotplug to read
|
|
|
|
|
if ! uci -q get "zt-gateway.global.wiblan_subnet" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "zt-gateway.global.wiblan_subnet='${WIBLAN_CIDR}'"
|
|
|
|
|
fi
|
|
|
|
|
if ! uci -q get "zt-gateway.global.bridge_device" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "zt-gateway.global.bridge_device='${BRIDGE}'"
|
|
|
|
|
fi
|
|
|
|
|
if ! uci -q get "zt-gateway.global.bridge_ports" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "zt-gateway.global.bridge_ports='${BRIDGE_PORTS}'"
|
|
|
|
|
fi
|
|
|
|
|
if ! uci -q get "zt-gateway.global.wiblan_gw" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "zt-gateway.global.wiblan_gw='${WIBLAN_GW}'"
|
|
|
|
|
fi
|
|
|
|
|
if ! uci -q get "zt-gateway.global.table_main" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "zt-gateway.global.table_main='${TABLE_MAIN}'"
|
|
|
|
|
fi
|
|
|
|
|
if ! uci -q get "zt-gateway.global.table_drain" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "zt-gateway.global.table_drain='${TABLE_DRAIN}'"
|
|
|
|
|
fi
|
|
|
|
|
if ! uci -q get "zt-gateway.global.dhcp_lease_first" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "zt-gateway.global.dhcp_lease_first='${WIBLAN_LEASE_FIRST}'"
|
|
|
|
|
fi
|
|
|
|
|
if ! uci -q get "zt-gateway.global.dhcp_lease_last" >/dev/null 2>&1; then
|
|
|
|
|
uci -q set "zt-gateway.global.dhcp_lease_last='${WIBLAN_LEASE_LAST}'"
|
|
|
|
|
fi
|
|
|
|
|
uci commit zt-gateway
|
|
|
|
|
log "UCI global config updated with setup parameters"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log "persistence setup complete"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# setup-all
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
cmd_setup_all() {
|
|
|
|
|
log "running full gateway setup..."
|
|
|
|
|
|
|
|
|
|
cmd_setup_bridge
|
|
|
|
|
cmd_setup_routing
|
|
|
|
|
cmd_setup_dhcp
|
2026-07-13 11:10:57 +05:30
|
|
|
cmd_setup_wifi_ap
|
2026-07-13 08:48:53 +05:30
|
|
|
cmd_setup_hotplug
|
|
|
|
|
cmd_setup_persistence
|
|
|
|
|
|
|
|
|
|
log "========================================="
|
|
|
|
|
log "Full gateway setup complete!"
|
|
|
|
|
log " Bridge: ${BRIDGE}"
|
|
|
|
|
log " Ports: ${BRIDGE_PORTS}"
|
|
|
|
|
log " Subnet: ${WIBLAN_CIDR}"
|
|
|
|
|
log " Gateway IP: ${WIBLAN_GW}"
|
|
|
|
|
log " Tables: ${TABLE_MAIN} (main), ${TABLE_DRAIN} (drain)"
|
|
|
|
|
log " DHCP: ${WIBLAN_LEASE_FIRST} - ${WIBLAN_LEASE_LAST}"
|
2026-07-13 11:10:57 +05:30
|
|
|
log " WiFi AP: ${WiFi_SSID} on ${WiFi_RADIO}"
|
2026-07-13 08:48:53 +05:30
|
|
|
log "========================================="
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# Usage
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
usage() {
|
|
|
|
|
cat >&2 <<'USAGE'
|
|
|
|
|
Usage: zt-gateway-setup <command>
|
|
|
|
|
|
|
|
|
|
Commands:
|
|
|
|
|
status Show current setup state
|
|
|
|
|
setup-bridge Create the br-zt bridge interface
|
|
|
|
|
setup-routing Configure policy routing (tables 100/101, ip rules)
|
|
|
|
|
setup-dhcp Configure DHCP for WIBLAN on br-zt
|
2026-07-13 11:10:57 +05:30
|
|
|
setup-wifi-ap Create WIBLAN WiFi AP bridged to br-zt
|
2026-07-13 08:48:53 +05:30
|
|
|
setup-hotplug Create hotplug script to re-apply routes on ifup
|
|
|
|
|
setup-persistence Write rc.local + UCI network routes
|
|
|
|
|
setup-all Run all setup-* commands in order
|
|
|
|
|
USAGE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
# Entry point
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
main() {
|
|
|
|
|
if [ $# -lt 1 ]; then
|
|
|
|
|
usage
|
|
|
|
|
die 1 "missing command"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
cmd=$1
|
|
|
|
|
shift
|
|
|
|
|
|
|
|
|
|
case "$cmd" in
|
|
|
|
|
status) cmd_status "$@" ;;
|
|
|
|
|
setup-bridge) cmd_setup_bridge "$@" ;;
|
|
|
|
|
setup-routing) cmd_setup_routing "$@" ;;
|
|
|
|
|
setup-dhcp) cmd_setup_dhcp "$@" ;;
|
2026-07-13 11:10:57 +05:30
|
|
|
setup-wifi-ap) cmd_setup_wifi_ap "$@" ;;
|
2026-07-13 08:48:53 +05:30
|
|
|
setup-hotplug) cmd_setup_hotplug "$@" ;;
|
|
|
|
|
setup-persistence) cmd_setup_persistence "$@" ;;
|
|
|
|
|
setup-all) cmd_setup_all "$@" ;;
|
|
|
|
|
-h|--help) usage; exit 0 ;;
|
|
|
|
|
*) usage; die 1 "unknown command: $cmd" ;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main "$@"
|