🛸 Sync 2026-10-06 21:21:05

This commit is contained in:
github-actions[bot] committed 2026-10-06 21:21:05 +08:00
1 parent b239ebf896
commit 3c831d5757
56 files changed
+5677

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
# Editor backups
*.bak
*.swp
*.tmp
# Local test artifacts
/tmp/
/.config
# Python
__pycache__/
*.pyc
# OS files
.DS_Store
Thumbs.db
+124
View File
@@ -0,0 +1,124 @@
# EasyMesh 2.4.1-r1
LuCI interface for Batman-adv 802.11s mesh on OpenWrt / ImmortalWrt.
## Changes since upstream
- De-integration of DAWN for Usteer pre-installed devices (conflict-free install, better performance)
- Optional cron fallback for priority reapplication (UI checkbox, default off)
- Live latency monitoring for wireless peers (smoothed over 3 pings)
- Live RX/TX bandwidth rates for wireless and wired links
- Neighbor MAC + last-seen for wired backhaul (from batman originator table)
- Robust priority read (tries multiple batctl syntaxes)
- Hotplug integration for priority reapplication on interface up
- 90-second priority retry loop + second-pass catchup in init script
- Removed `+wpad-mesh` dependency (fixed kconfig recursive dependency)
- Meta packages for `wpad-openssl` / `wpad-wolfssl` / `wpad-mbedtls` variants
- WPS handler `/etc/rc.wps/easymesh-pair` (no conflict with wifi-scripts)
- বাংলা (Bengali) Language → now supports full Bengali UI
**Mesh features**
- Editable link priority weights in UI — wired, 6 GHz, 5 GHz, 2.4 GHz
- Priority Weight column for wireless peers (previously wired-only)
- Mesh VLANs — extend guest / IoT / hotspot networks over the mesh via 802.1Q
- Comma-separated VLAN IDs in UI (e.g. `10,20,30`)
- Auto-attach to `br-<vid>` / `br-guest<vid>` / `br-iot<vid>` bridges
- Safe re-apply (removes previous 802.1Q devices first)
**Documentation**
- English README with screenshots
- Bengali (বাংলা) README — `docs/bn`
- Bengali glossary — 80+ networking terms
- Language switcher on both READMEs
## Bundle
`easymesh-pkg-2.4.1-r1.zip` (1.6 MB): 6 IPK + 6 APK + full source + Bengali docs (README, glossary, 7 screenshots) + Bengali/Chinese `.po`.
## Notes
- GitHub replaces `~` with `.` in **asset names on this page** (`…37259.7cad107…`). Files inside the bundle keep the original `~`. Package metadata is unchanged — `opkg` / `apk` read the version from inside the package.
- Tag `2.4.1-r1` was force-moved. Re-download if you pulled an earlier build.
- SAE mesh encryption is driver-limited on ath10k / MT7915E — mesh runs unencrypted.
## Credits
- **Original authors:**
- dz <dingzhong110@gmail.com>
- torguardvpn — [torguardvpn/luci-app-easymesh](https://github.com/torguardvpn/luci-app-easymesh)
- **Upstream forks:**
- [ntlf9t/luci-app-easymesh](https://github.com/ntlf9t/luci-app-easymesh)
- [kenzok78/luci-app-easymesh](https://github.com/kenzok78/luci-app-easymesh)
- [torguardvpn/luci-app-easymesh](https://github.com/torguardvpn/luci-app-easymesh)
- **Bugfix branch:** [mobing8/luci-app-easymesh-dawn](https://github.com/mobing8/luci-app-easymesh-dawn)
- **Current maintainer:** Arafat Rahman Zami Mondol — [@arafatrahmanzami](https://github.com/arafatrahmanzami) · [open an issue](https://github.com/arafatrahmanzami/luci-app-easymesh/issues/new)
*Preview (screenshots)*
<img width="1484" height="828" alt="01-dashboard" src="https://github.com/user-attachments/assets/8206e3b0-6a32-417a-a03e-76472b07b636" />
<img width="1784" height="666" alt="07-priority-vlans" src="https://github.com/user-attachments/assets/c573dad7-2f46-422e-94b4-7021327ec061" />
<img width="1423" height="820" alt="02-quick-setup-1-9" src="https://github.com/user-attachments/assets/641072db-b72f-439d-90e4-3ba14a599877" />
<img width="1356" height="867" alt="03-quick-setup-10-19" src="https://github.com/user-attachments/assets/f665db1c-0c42-4367-b3eb-587de7a49b01" />
<img width="1430" height="874" alt="04-mesh-settings" src="https://github.com/user-attachments/assets/abfc5ee7-108e-4e9d-9625-5dcb1fc03321" />
<img width="489" height="1012" alt="05-client-wifi-ap-mode" src="https://github.com/user-attachments/assets/7dd6097f-b4f4-4021-9c56-41ed5c8628b9" />
<img width="1355" height="504" alt="06-wired-backhaul-safety" src="https://github.com/user-attachments/assets/84543c6b-f5a2-4aed-9d97-f35c3a8bc4f7" />
## Installation example
Pick the wpad meta package matching your TLS backend, then the app, then i18n.
**OpenWrt ≤ 24.10 (opkg, openssl)**
```sh
cd /tmp
opkg install luci-app-easymesh-wpad-openssl_2.4.1-r1_all.ipk
opkg install luci-app-easymesh_2.4.1-r1_all.ipk
# optional
opkg install luci-i18n-easymesh-bn_26.184.37259.7cad107_all.ipk
/etc/init.d/uhttpd restart
```
**OpenWrt ≥ 25.12 (apk,wolfssl)**
```sh
cd /tmp
apk --allow-untrusted add /tmp/luci-app-easymesh-wpad-wolfssl-2.4.1-r1.apk
apk --allow-untrusted add /tmp/luci-app-easymesh-2.4.1-r1.apk
# optional
apk --allow-untrusted add /tmp/luci-i18n-easymesh-bn-26.184.37259.7cad107.apk
/etc/init.d/uhttpd restart
```
> The meta depends on `wpad-mesh-*` (not `wpad-*`). `apk verify` reports `UNTRUSTED signature` — expected, harmless; that's why `--allow-untrusted` is required.
**Bengali UI:** System → System → Language and Style → Language → **বাংলা (Bengali)**
> All packages are `PKG_ARCH=all` — universal across every router CPU.
+49
View File
@@ -0,0 +1,49 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-easymesh
PKG_VERSION:=2.4.1
PKG_RELEASE:=3
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Arafat Rahman Zami Mondol <zamimondol@gmail.com>
PKG_URL:=https://github.com/arafatrahmanzami/luci-app-easymesh
LUCI_TITLE:=LuCI Management Interface for EasyMesh (Batman-adv mesh)
LUCI_DESCRIPTION:=LuCI app for configuring and monitoring a Batman-adv \
based 802.11s mesh network. Requires a wpad-mesh-* package \
(openssl, wolfssl, or mbedtls) to be installed separately.
# ---- Runtime dependencies --------------------------------------------
# Wireless stack
LUCI_DEPENDS:= \
+kmod-cfg80211 \
+kmod-batman-adv \
+luci-proto-batman-adv \
+batctl-default \
+luci-compat \
+luci-lua-runtime \
+libiwinfo-lua
# Client steering (works with or without usteer)
LUCI_DEPENDS += \
+usteer \
+luci-app-usteer
LUCI_PKGARCH:=all
define Package/luci-app-easymesh/conffiles
/etc/config/easymesh
endef
define Package/luci-app-easymesh/postinst
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] && exit 0
rm -f /tmp/luci-indexcache 2>/dev/null
/etc/init.d/rpcd restart 2>/dev/null
/etc/init.d/uhttpd restart 2>/dev/null
exit 0
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
File diff suppressed because it is too large. Load diff
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
set -e
SRC="/home/zamimondol/easymesh-build/src/luci-app-easymesh"
OUT="/home/zamimondol/easymesh-build/output"
BUILD="/tmp/easymesh-apk"
APK="/home/zamimondol/easymesh-build/sdk-25.12/staging_dir/host/bin/apk"
PO2LMO="/home/zamimondol/easymesh-build/sdk-24.10/staging_dir/hostpkg/bin/po2lmo"
VERSION="2.4.1-r1"
BASE_DEPS="libc luci-base luci-compat luci-lua-runtime libiwinfo-lua kmod-cfg80211 kmod-batman-adv luci-proto-batman-adv batctl-default usteer luci-app-usteer"
mkdir -p "$OUT"
rm -rf "$BUILD"
mkdir -p "$BUILD"
stage_main() {
local DEST="$1"
mkdir -p "$DEST/usr/lib/lua/luci"
cp -a "$SRC/luasrc/." "$DEST/usr/lib/lua/luci/"
[ -d "$SRC/htdocs" ] && { mkdir -p "$DEST/www"; cp -a "$SRC/htdocs/." "$DEST/www/"; }
[ -d "$SRC/root" ] && cp -a "$SRC/root/." "$DEST/"
find "$DEST" -name '*.luadoc' -delete
}
build_apk() {
local NAME="$1" DIR="$2" DEPS="$3" DESC="$4"
local OUTFILE="$OUT/${NAME}-${VERSION}.apk"
rm -f "$OUTFILE"
SOURCE_DATE_EPOCH=0 "$APK" mkpkg \
--info "name:$NAME" \
--info "version:$VERSION" \
--info "arch:noarch" \
--info "description:$DESC" \
--info "maintainer:Arafat Rahman Zami Mondol <arafatrahmanzami@users.noreply.github.com>" \
--info "depends:$DEPS" \
--info "origin:luci-app-easymesh" \
--files "$DIR" \
--output "$OUTFILE"
local MAGIC=$(head -c 4 "$OUTFILE" | od -An -tx1 | tr -d ' \n')
[ "$MAGIC" = "41444264" ] && echo " OK: $NAME" || echo " WARN: $NAME"
}
echo "==> Building APK variants"
# Main (no wpad)
MAIN="$BUILD/main"
mkdir -p "$MAIN"
stage_main "$MAIN"
build_apk "luci-app-easymesh" "$MAIN" "$BASE_DEPS" \
"LuCI Management Interface for EasyMesh (Batman-adv mesh)"
# Meta variants (empty package with just a wpad dep)
for v in openssl wolfssl mbedtls; do
M="$BUILD/meta-$v"
mkdir -p "$M"
build_apk "luci-app-easymesh-wpad-$v" "$M" \
"luci-app-easymesh wpad-mesh-$v" \
"EasyMesh meta-package — installs with wpad-mesh-$v"
done
# i18n
if [ -x "$PO2LMO" ] && [ -f "$SRC/po/zh_Hans/easymesh.po" ]; then
I18N="$BUILD/i18n"
mkdir -p "$I18N/usr/lib/lua/luci/i18n" "$I18N/etc/uci-defaults"
"$PO2LMO" "$SRC/po/zh_Hans/easymesh.po" \
"$I18N/usr/lib/lua/luci/i18n/easymesh.zh-cn.lmo"
echo "uci set luci.languages.zh_cn='简体中文'; uci commit luci" \
> "$I18N/etc/uci-defaults/luci-i18n-easymesh-zh-cn"
build_apk "luci-i18n-easymesh-zh-cn" "$I18N" "libc luci-app-easymesh" \
"Chinese Simplified translation for luci-app-easymesh"
fi
echo ""
echo "============================================================"
ls -lh "$OUT"/*.apk
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
set -e
SRC="/home/zamimondol/easymesh-build/src/luci-app-easymesh"
OUT="/home/zamimondol/easymesh-build/output"
BUILD="/tmp/easymesh-ipk-dpkg"
PO2LMO="/home/zamimondol/easymesh-build/sdk-24.10/staging_dir/hostpkg/bin/po2lmo"
V="2.4.1-r1"
PKG="luci-app-easymesh"
I18N="luci-i18n-easymesh-zh-cn"
BASE_DEPS="libc, luci-base, luci-compat, luci-lua-runtime, libiwinfo-lua, kmod-cfg80211, kmod-batman-adv, luci-proto-batman-adv, batctl-default, usteer, luci-app-usteer"
mkdir -p "$OUT"
rm -rf "$BUILD"
mkdir -p "$BUILD"
build_ipk() {
local NAME="$1" DATADIR="$2" DEPS="$3" DESC="$4"
local WORK="$BUILD/$NAME"
rm -rf "$WORK"
mkdir -p "$WORK/DEBIAN"
# Copy data files into deb root
if [ -d "$DATADIR" ]; then
cp -a "$DATADIR/." "$WORK/"
fi
rm -rf "$WORK/CONTROL" "$WORK/.git"
# DEBIAN/control
cat > "$WORK/DEBIAN/control" <<EOF
Package: $NAME
Version: $V
Depends: $DEPS
Architecture: all
Maintainer: Arafat Rahman Zami Mondol <arafatrahmanzami@users.noreply.github.com>
Section: luci
Priority: optional
Description: $DESC
EOF
if [ "$NAME" = "$PKG" ]; then
echo "/etc/config/easymesh" > "$WORK/DEBIAN/conffiles"
cat > "$WORK/DEBIAN/postinst" <<'POST'
#!/bin/sh
[ -n "${IPKG_INSTROOT}" ] && exit 0
rm -f /tmp/luci-indexcache 2>/dev/null
/etc/init.d/rpcd restart 2>/dev/null
/etc/init.d/uhttpd restart 2>/dev/null
exit 0
POST
chmod 0755 "$WORK/DEBIAN/postinst"
fi
# Build .deb (which IS an ar archive, same format as .ipk)
rm -f "$OUT/${NAME}_${V}_all.ipk"
dpkg-deb --build -Zgzip -z9 "$WORK" "$OUT/${NAME}_${V}_all.ipk" >/dev/null
# Verify
local MAGIC=$(head -c 8 "$OUT/${NAME}_${V}_all.ipk" | od -An -tx1 | tr -d ' \n')
if [ "$MAGIC" = "213c617263683e0a" ]; then
echo " OK: $NAME ($(stat -c%s "$OUT/${NAME}_${V}_all.ipk") bytes)"
else
echo " FAIL: $NAME — magic $MAGIC"
fi
}
# Main package
MAIN_DATA="$BUILD/main-data"
mkdir -p "$MAIN_DATA/usr/lib/lua/luci"
cp -a "$SRC/luasrc/." "$MAIN_DATA/usr/lib/lua/luci/"
[ -d "$SRC/root" ] && cp -a "$SRC/root/." "$MAIN_DATA/"
find "$MAIN_DATA" -name '*.luadoc' -delete
echo "==> Main"
build_ipk "$PKG" "$MAIN_DATA" "$BASE_DEPS" \
"LuCI Management Interface for EasyMesh (Batman-adv mesh)"
# Meta packages
for v in openssl wolfssl mbedtls; do
echo "==> Meta $v"
EMPTY="$BUILD/empty"
rm -rf "$EMPTY"; mkdir -p "$EMPTY"
build_ipk "luci-app-easymesh-wpad-$v" "$EMPTY" \
"libc, $PKG, wpad-mesh-$v" \
"EasyMesh meta — installs with wpad-mesh-$v"
done
# i18n
if [ -x "$PO2LMO" ] && [ -f "$SRC/po/zh_Hans/easymesh.po" ]; then
echo "==> i18n"
I18N_DATA="$BUILD/i18n-data"
rm -rf "$I18N_DATA"
mkdir -p "$I18N_DATA/usr/lib/lua/luci/i18n" "$I18N_DATA/etc/uci-defaults"
"$PO2LMO" "$SRC/po/zh_Hans/easymesh.po" \
"$I18N_DATA/usr/lib/lua/luci/i18n/easymesh.zh-cn.lmo"
echo "uci set luci.languages.zh_cn='简体中文'; uci commit luci" \
> "$I18N_DATA/etc/uci-defaults/luci-i18n-easymesh-zh-cn"
build_ipk "$I18N" "$I18N_DATA" "libc, $PKG" "Chinese Simplified"
fi
echo ""
echo "==> Output:"
ls -lh "$OUT"/*.ipk
+121
View File
@@ -0,0 +1,121 @@
#!/bin/bash
set -e
SRC="/home/zamimondol/easymesh-build/src/luci-app-easymesh"
OUT="/home/zamimondol/easymesh-build/output"
BUILD="/tmp/easymesh-tarball"
PO2LMO="/home/zamimondol/easymesh-build/sdk-24.10/staging_dir/hostpkg/bin/po2lmo"
VERSION="2.4.1-r1"
mkdir -p "$OUT"
rm -rf "$BUILD"
mkdir -p "$BUILD"
# =========================================================
# Main app tarball
# =========================================================
echo "==> Building main tarball..."
STAGE="$BUILD/stage-main"
mkdir -p "$STAGE"
# luasrc/ -> usr/lib/lua/luci/
mkdir -p "$STAGE/usr/lib/lua/luci"
cp -a "$SRC/luasrc/." "$STAGE/usr/lib/lua/luci/"
# root/ -> ./ (etc/init.d/easymesh, etc/config/easymesh, etc/uci-defaults/, usr/share/...)
if [ -d "$SRC/root" ]; then
cp -a "$SRC/root/." "$STAGE/"
fi
# htdocs/ (if present) -> www/
if [ -d "$SRC/htdocs" ]; then
mkdir -p "$STAGE/www"
cp -a "$SRC/htdocs/." "$STAGE/www/"
fi
# Remove stray files
find "$STAGE" -name '*.luadoc' -delete
find "$STAGE" -name '.git*' -delete 2>/dev/null || true
# Build the tarball with clean permissions
cd "$STAGE"
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chmod 755 etc/init.d/easymesh 2>/dev/null || true
tar czf "$OUT/luci-app-easymesh-${VERSION}-all.tar.gz" \
--numeric-owner --owner=0 --group=0 \
--sort=name \
--mtime='2026-09-11 00:00:00' \
-C "$STAGE" \
./etc ./usr 2>/dev/null || \
tar czf "$OUT/luci-app-easymesh-${VERSION}-all.tar.gz" \
--numeric-owner --owner=0 --group=0 \
-C "$STAGE" .
echo " Created: $OUT/luci-app-easymesh-${VERSION}-all.tar.gz"
ls -lh "$OUT/luci-app-easymesh-${VERSION}-all.tar.gz"
# Show what's inside
echo ""
echo " Contents:"
tar tzf "$OUT/luci-app-easymesh-${VERSION}-all.tar.gz" | head -25
# =========================================================
# i18n tarball
# =========================================================
if [ -x "$PO2LMO" ] && [ -f "$SRC/po/zh_Hans/easymesh.po" ]; then
echo ""
echo "==> Building i18n tarball..."
STAGE_I18N="$BUILD/stage-i18n"
mkdir -p "$STAGE_I18N/usr/lib/lua/luci/i18n"
mkdir -p "$STAGE_I18N/etc/uci-defaults"
"$PO2LMO" "$SRC/po/zh_Hans/easymesh.po" \
"$STAGE_I18N/usr/lib/lua/luci/i18n/easymesh.zh-cn.lmo"
echo "uci set luci.languages.zh_cn='简体中文 (Chinese Simplified)'; uci commit luci" \
> "$STAGE_I18N/etc/uci-defaults/luci-i18n-easymesh-zh-cn"
chmod -R 755 "$STAGE_I18N/etc"
chmod 644 "$STAGE_I18N/usr/lib/lua/luci/i18n/easymesh.zh-cn.lmo"
tar czf "$OUT/luci-i18n-easymesh-zh-cn-${VERSION}-all.tar.gz" \
--numeric-owner --owner=0 --group=0 \
--sort=name \
--mtime='2026-09-11 00:00:00' \
-C "$STAGE_I18N" .
echo " Created: $OUT/luci-i18n-easymesh-zh-cn-${VERSION}-all.tar.gz"
ls -lh "$OUT/luci-i18n-easymesh-zh-cn-${VERSION}-all.tar.gz"
else
echo "!! po2lmo not found, skipping i18n tarball"
fi
# =========================================================
# Combined "install everything" tarball
# =========================================================
echo ""
echo "==> Building combined tarball (main + i18n)..."
STAGE_ALL="$BUILD/stage-all"
mkdir -p "$STAGE_ALL"
cp -a "$BUILD/stage-main/." "$STAGE_ALL/"
[ -d "$BUILD/stage-i18n" ] && cp -a "$BUILD/stage-i18n/." "$STAGE_ALL/"
tar czf "$OUT/luci-app-easymesh-${VERSION}-full.tar.gz" \
--numeric-owner --owner=0 --group=0 \
--sort=name \
--mtime='2026-09-11 00:00:00' \
-C "$STAGE_ALL" .
echo " Created: $OUT/luci-app-easymesh-${VERSION}-full.tar.gz"
ls -lh "$OUT/luci-app-easymesh-${VERSION}-full.tar.gz"
echo ""
echo "============================================================"
echo "Tarballs in $OUT:"
ls -lh "$OUT"/*.tar.gz
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Create a Debian/OpenWrt .ipk (ar archive) from debian-binary + control.tar.gz + data.tar.gz."""
import sys
import os
import struct
def write_ar_header(f, name, size):
# ar member header is 60 bytes
# name must end with '/' for GNU/SysV compatibility
name_field = (name + '/')[:16].ljust(16)
header = "{}{:<12}{:<6}{:<6}{:<8}{:<10}`\n".format(
name_field, "0", "0", "0", "100644", size
)
f.write(header.encode('ascii'))
def add_file(ar, path, archive_name):
data = open(path, 'rb').read()
write_ar_header(ar, archive_name, len(data))
ar.write(data)
# Pad to even length
if len(data) % 2:
ar.write(b'\n')
def main():
if len(sys.argv) != 5:
print("Usage: make-ipk.py <out.ipk> <debian-binary> <control.tar.gz> <data.tar.gz>")
sys.exit(1)
out, debian_bin, control_tgz, data_tgz = sys.argv[1:]
with open(out, 'wb') as ar:
# Magic
ar.write(b'!<arch>\n')
add_file(ar, debian_bin, 'debian-binary')
add_file(ar, control_tgz, 'control.tar.gz')
add_file(ar, data_tgz, 'data.tar.gz')
if __name__ == '__main__':
main()
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# Create wpad meta-package APKs (OpenWrt 25.12+ apk format)
set -e
OUT="${1:-/home/zamimondol/easymesh-build/output}"
V="2.4.1-r1"
PKG="luci-app-easymesh"
MAINTAINER="Arafat Rahman Zami Mondol <arafatrahmanzami@users.noreply.github.com>"
APK="/home/zamimondol/easymesh-build/sdk-25.12-fresh/staging_dir/host/bin/apk"
if [ ! -x "$APK" ]; then
echo "ERROR: apk tool not found at $APK"
exit 1
fi
mkdir -p "$OUT"
create_meta_apk() {
local NAME="$1" DEPS="$2" DESC="$3"
local EMPTY="/tmp/meta-apk-empty"
rm -rf "$EMPTY"
mkdir -p "$EMPTY"
rm -f "$OUT/${NAME}-${V}.apk"
SOURCE_DATE_EPOCH=0 "$APK" mkpkg \
--info "name:$NAME" \
--info "version:$V" \
--info "arch:noarch" \
--info "description:$DESC" \
--info "maintainer:$MAINTAINER" \
--info "depends:libc $PKG $DEPS" \
--info "origin:luci-app-easymesh" \
--files "$EMPTY" \
--output "$OUT/${NAME}-${V}.apk" 2>&1 | tail -2
rm -rf "$EMPTY"
local SIZE=$(stat -c%s "$OUT/${NAME}-${V}.apk" 2>/dev/null || echo 0)
local MAGIC=$(head -c 4 "$OUT/${NAME}-${V}.apk" 2>/dev/null | od -An -tx1 | tr -d ' \n')
if [ "$MAGIC" = "41444264" ]; then
echo " OK: $NAME (${SIZE} bytes, ADBd magic)"
else
echo " FAIL: $NAME (magic=$MAGIC)"
fi
}
echo "==> Creating wpad MESH meta APKs"
create_meta_apk "luci-app-easymesh-wpad-openssl" \
"wpad-mesh-openssl" \
"EasyMesh meta - installs with wpad-mesh-openssl (default OpenWrt)"
create_meta_apk "luci-app-easymesh-wpad-wolfssl" \
"wpad-mesh-wolfssl" \
"EasyMesh meta - installs with wpad-mesh-wolfssl (ImmortalWrt default)"
create_meta_apk "luci-app-easymesh-wpad-mbedtls" \
"wpad-mesh-mbedtls" \
"EasyMesh meta - installs with wpad-mesh-mbedtls (lightweight)"
echo ""
ls -lh "$OUT"/luci-app-easymesh-wpad-*.apk
+78
View File
@@ -0,0 +1,78 @@
#!/bin/bash
set -e
OUT="/home/zamimondol/easymesh-build/output"
PKG="/home/zamimondol/easymesh-build/easymesh-pkg"
SRC="/home/zamimondol/easymesh-build/src/luci-app-easymesh"
META="/home/zamimondol/easymesh-build/meta-packages"
V="2.4.1-r1"
rm -rf "$PKG"
mkdir -p "$PKG"/{ipk,apk,tarballs,meta-packages/luci-app-easymesh,meta-packages/wpad-openssl,meta-packages/wpad-wolfssl,meta-packages/wpad-mbedtls}
# Source
cp -a "$SRC/." "$PKG/meta-packages/luci-app-easymesh/"
rm -rf "$PKG/meta-packages/luci-app-easymesh/.git"
cp "$META/openssl/Makefile" "$PKG/meta-packages/wpad-openssl/"
cp "$META/wolfssl/Makefile" "$PKG/meta-packages/wpad-wolfssl/"
cp "$META/mbedtls/Makefile" "$PKG/meta-packages/wpad-mbedtls/"
# IPKs
cp -v "$OUT"/luci-app-easymesh_${V}_all.ipk "$PKG/ipk/"
cp -v "$OUT"/luci-app-easymesh-wpad-openssl_${V}_all.ipk "$PKG/ipk/"
cp -v "$OUT"/luci-app-easymesh-wpad-wolfssl_${V}_all.ipk "$PKG/ipk/"
cp -v "$OUT"/luci-app-easymesh-wpad-mbedtls_${V}_all.ipk "$PKG/ipk/"
find "$OUT" -name "luci-i18n-easymesh-zh-cn_*_all.ipk" -exec cp {} "$PKG/ipk/" \;
# APKs
cp -v "$OUT"/luci-app-easymesh-${V}.apk "$PKG/apk/"
cp -v "$OUT"/luci-app-easymesh-wpad-openssl-${V}.apk "$PKG/apk/"
cp -v "$OUT"/luci-app-easymesh-wpad-wolfssl-${V}.apk "$PKG/apk/"
cp -v "$OUT"/luci-app-easymesh-wpad-mbedtls-${V}.apk "$PKG/apk/"
find "$OUT" -name "luci-i18n-easymesh-zh-cn-*_all.apk" -exec cp {} "$PKG/apk/" \;
# Tarballs
find "$OUT" -name "luci-app-easymesh-${V}-*.tar.gz" -exec cp {} "$PKG/tarballs/" \;
cat > "$PKG/MANIFEST.txt" <<EOF
EasyMesh $V — Package Bundle
Generated: $(date)
Pick ONE of the following for your router:
A) Fresh install, want auto wpad selection
OpenWrt 24.10 (opkg):
opkg install ipk/luci-app-easymesh_${V}_all.ipk
opkg install ipk/luci-app-easymesh-wpad-openssl_${V}_all.ipk
OpenWrt 25.12 (apk):
apk add --allow-untrusted apk/luci-app-easymesh-${V}.apk
apk add --allow-untrusted apk/luci-app-easymesh-wpad-openssl-${V}.apk
B) Already have wpad-mesh-wolfssl (ImmortalWrt default)
Use wpad-wolfssl variant instead
C) Already have wpad-mesh-mbedtls (lightweight builds)
Use wpad-mbedtls variant instead
D) Any OpenWrt, any wpad already installed
opkg install ipk/luci-app-easymesh_${V}_all.ipk
(no wpad meta needed)
E) Tarball install (any OpenWrt)
scp tarballs/luci-app-easymesh-${V}-full.tar.gz root@router:/tmp/
ssh root@router
cd /
tar xzf /tmp/luci-app-easymesh-${V}-full.tar.gz
chmod +x /etc/init.d/easymesh
rm -f /tmp/luci-indexcache
/etc/init.d/rpcd restart
/etc/init.d/uhttpd restart
(Installs the app. Install your wpad variant separately.)
Credits:
Original: torguardvpn/luci-app-easymesh
Fixes: mobing8/luci-app-easymesh-dawn
This build: Arafat Rahman Zami Mondol
EOF
echo "Bundle ready: $PKG"
du -sh "$PKG"/*
+214
View File
@@ -0,0 +1,214 @@
#!/bin/bash
# Verify all EasyMesh build artifacts before shipping
OUT="/home/zamimondol/easymesh-build/output"
PKG="/home/zamimondol/easymesh-build/easymesh-pkg"
BUNDLE="/home/zamimondol/easymesh-build/easymesh-pkg-2.4.1-r1.tar.gz"
SRC="/home/zamimondol/easymesh-build/src/luci-app-easymesh"
V="2.4.1-r1"
PASS=0
FAIL=0
check() {
local label="$1" result="$2"
if [ "$result" = "0" ]; then
echo " [PASS] $label"
PASS=$((PASS+1))
else
echo " [FAIL] $label"
FAIL=$((FAIL+1))
fi
}
section() { echo ""; echo "=== $1 ==="; }
TMP=$(mktemp -d)
trap "rm -rf $TMP" EXIT
# ============================================================
section "1. Source tree"
# ============================================================
[ -f "$SRC/Makefile" ]
check "Makefile exists" $?
[ -f "$SRC/luasrc/model/cbi/easymesh.lua" ]
check "CBI model exists" $?
[ -f "$SRC/luasrc/controller/easymesh.lua" ]
check "Controller exists" $?
[ -f "$SRC/root/etc/init.d/easymesh" ]
check "Init script exists" $?
[ -f "$SRC/root/etc/config/easymesh" ]
check "Config file exists" $?
[ -f "$SRC/root/etc/rc.wps/easymesh-pair" ]
check "WPS handler (rc.wps) exists" $?
[ ! -d "$SRC/root/etc/rc.button" ]
check "No rc.button dir (no conflict)" $?
[ -f "$SRC/root/etc/uci-defaults/luci-easymesh" ]
check "UCI-defaults exists" $?
grep -q "PKG_VERSION:=2.4.1" "$SRC/Makefile"
check "Version is 2.4.1" $?
grep -q "PKG_RELEASE:=1" "$SRC/Makefile"
check "Release is 1" $?
grep -q "LUCI_PKGARCH:=all" "$SRC/Makefile"
check "PKGARCH=all (universal)" $?
! grep -q "+wpad-mesh" "$SRC/Makefile"
check "No wpad dep in Makefile" $?
! grep -qE "uci set.*='.*'" "$SRC/root/etc/init.d/easymesh"
check "No escaped quotes in init script" $?
lua5.3 -e "assert(loadfile('$SRC/luasrc/model/cbi/easymesh.lua'))" 2>/dev/null
check "CBI Lua syntax valid" $?
sh -n "$SRC/root/etc/init.d/easymesh"
check "Init script syntax valid" $?
# ============================================================
section "2. IPK files"
# ============================================================
for p in luci-app-easymesh luci-app-easymesh-wpad-openssl \
luci-app-easymesh-wpad-wolfssl luci-app-easymesh-wpad-mbedtls \
luci-i18n-easymesh-zh-cn
do
F="$OUT/${p}_${V}_all.ipk"
[ -f "$F" ]
check "IPK present: $(basename $F)" $?
done
# Verify main IPK contents
D="$TMP/ipk"; mkdir -p "$D"; cd "$D"
if ar x "$OUT/luci-app-easymesh_${V}_all.ipk" 2>/dev/null; then
check "Main IPK is valid ar archive" 0
tar xzf control.tar.gz 2>/dev/null
grep -q "Package: luci-app-easymesh" control
check "Main IPK control: package name" $?
grep -q "Version: 2.4.1-r1" control
check "Main IPK control: version" $?
grep -q "Architecture: all" control
check "Main IPK control: arch=all" $?
grep -q "Depends:.*kmod-batman-adv" control
check "Main IPK control: has batman dep" $?
! grep -q "Depends:.*wpad-mesh" control
check "Main IPK control: no wpad dep" $?
tar tzf data.tar.gz | grep -q "etc/rc.wps/easymesh-pair"
check "Main IPK data: rc.wps handler present" $?
! tar tzf data.tar.gz | grep -q "etc/rc.button/wps"
check "Main IPK data: no rc.button conflict" $?
tar tzf data.tar.gz | grep -q "etc/init.d/easymesh"
check "Main IPK data: init.d present" $?
tar tzf data.tar.gz | grep -q "usr/lib/lua/luci/model/cbi/easymesh.lua"
check "Main IPK data: CBI present" $?
else
check "Main IPK is valid ar archive" 1
fi
cd "$TMP"
# Verify a meta IPK
D="$TMP/meta"; mkdir -p "$D"; cd "$D"
if ar x "$OUT/luci-app-easymesh-wpad-wolfssl_${V}_all.ipk" 2>/dev/null; then
tar xzf control.tar.gz 2>/dev/null
grep -q "Package: luci-app-easymesh-wpad-wolfssl" control
check "Wolfssl meta: correct name" $?
grep -q "Depends:.*wpad-mesh-wolfssl" control
check "Wolfssl meta: depends on wolfssl" $?
grep -q "Depends:.*luci-app-easymesh" control
check "Wolfssl meta: depends on main" $?
fi
cd "$TMP"
# ============================================================
section "3. APK files"
# ============================================================
for p in luci-app-easymesh luci-app-easymesh-wpad-openssl \
luci-app-easymesh-wpad-wolfssl luci-app-easymesh-wpad-mbedtls \
luci-i18n-easymesh-zh-cn
do
F="$OUT/${p}-${V}.apk"
[ -f "$F" ]
check "APK present: $(basename $F)" $?
if [ -f "$F" ]; then
MAGIC=$(head -c 4 "$F" 2>/dev/null | od -An -tx1 | tr -d ' \n')
[ "$MAGIC" = "41444264" ]
check " magic bytes ADBd: $(basename $F)" $?
fi
done
# ============================================================
section "4. Tarballs"
# ============================================================
for t in luci-app-easymesh-${V}-all.tar.gz \
luci-app-easymesh-${V}-full.tar.gz \
luci-i18n-easymesh-zh-cn-${V}-all.tar.gz
do
F="$OUT/$t"
[ -f "$F" ]
check "Tarball present: $t" $?
if [ -f "$F" ]; then
tar tzf "$F" >/dev/null 2>&1
check " tarball valid: $t" $?
fi
done
# Verify full tarball contains the right paths
tar tzf "$OUT/luci-app-easymesh-${V}-full.tar.gz" 2>/dev/null | grep -q "etc/rc.wps/easymesh-pair"
check "Full tarball: rc.wps present" $?
tar tzf "$OUT/luci-app-easymesh-${V}-full.tar.gz" 2>/dev/null | grep -q "etc/init.d/easymesh"
check "Full tarball: init.d present" $?
# ============================================================
section "5. Bundle folder"
# ============================================================
[ -d "$PKG" ]
check "Bundle folder exists" $?
[ -f "$PKG/MANIFEST.txt" ]
check "MANIFEST.txt present" $?
[ -f "$PKG/ipk/luci-app-easymesh_${V}_all.ipk" ]
check "Bundle has main IPK" $?
[ -f "$PKG/apk/luci-app-easymesh-${V}.apk" ]
check "Bundle has main APK" $?
[ -f "$PKG/meta-packages/luci-app-easymesh/Makefile" ]
check "Bundle has source" $?
[ -f "$PKG/meta-packages/wpad-openssl/Makefile" ]
check "Bundle has openssl meta Makefile" $?
[ -f "$PKG/meta-packages/wpad-wolfssl/Makefile" ]
check "Bundle has wolfssl meta Makefile" $?
[ -f "$PKG/meta-packages/wpad-mbedtls/Makefile" ]
check "Bundle has mbedtls meta Makefile" $?
[ -f "$BUNDLE" ]
check "Bundle tarball exists" $?
if [ -f "$BUNDLE" ]; then
tar tzf "$BUNDLE" >/dev/null 2>&1
check "Bundle tarball valid" $?
fi
# ============================================================
echo ""
echo "============================================================"
echo "RESULT: $PASS passed, $FAIL failed"
echo "============================================================"
[ $FAIL -eq 0 ] && exit 0 || exit 1
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
local nixio = require "nixio"
require "nixio.fs"
-- Copyright (C) 2021 dz <dingzhong110@gmail.com>
module("luci.controller.easymesh", package.seeall)
local fs = require "nixio.fs"
function index()
if not fs.access("/etc/config/easymesh") then
return
end
-- luci 23.05+ 已通过 menu.d JSON 注册菜单,无需重复
if fs.access("/usr/share/luci/menu.d/luci-app-easymesh.json") then
return
end
local page
page = entry({"admin", "network", "easymesh"}, cbi("easymesh"), _("EASY MESH"), 60)
page.dependent = true
page.acl_depends = { "luci-app-easymesh" }
end
+685
View File
@@ -0,0 +1,685 @@
-- Copyright (C) 2021 dz <dingzhong110@gmail.com>
-- Copyright (C) 2026 Arafat Rahman Zami Mondol <zamimondol@gmail.com>
-- v2.8 — wired+wireless priority display, editable priority weights, mesh VLANs
local m, s, o
local sys = require "luci.sys"
local uci = require "luci.model.uci".cursor()
m = Map("easymesh", translate("EasyMesh"),
translate("Configure a Batman-adv mesh network."))
function m.parse(self, ...)
Map.parse(self, ...)
local role = self:get("config", "role")
local ap_mode = self:get("config", "ap_mode")
if role == "server" and ap_mode == "1" then
self:set("config", "ap_mode", "0")
end
local wired_if = self:get("config", "wired_if")
local preferred = self:get("config", "preferred_wired_port")
if (not wired_if or wired_if == "") and preferred and preferred ~= "" then
self:set("config", "wired_if", preferred)
elseif wired_if and wired_if ~= "" and (not preferred or preferred == "") then
self:set("config", "preferred_wired_port", wired_if)
end
end
function m.on_after_commit(self)
luci.sys.call("/etc/init.d/easymesh restart >/tmp/easymesh-apply.log 2>&1 &")
end
-- =========================================================
-- Helpers
-- =========================================================
local function get_preferred_wired_port()
local wired_if = uci:get("easymesh", "config", "wired_if")
local preferred = uci:get("easymesh", "config", "preferred_wired_port")
local pick = ""
if wired_if and wired_if ~= "" then pick = wired_if
elseif preferred and preferred ~= "" then pick = preferred end
if pick ~= "" then
local f = io.open("/sys/class/net/" .. pick, "r")
if f then f:close(); return pick end
end
return ""
end
local function list_eth_ports()
local ports, seen = {}, {}
local fp = io.popen("ls /sys/class/net 2>/dev/null")
if fp then
for d in fp:lines() do
if (d:match("^eth%d+$") or d:match("^lan%d+$")) and not seen[d] then
seen[d] = true; table.insert(ports, d)
end
end
fp:close()
end
uci:foreach("network", "device", function(sect)
local n = sect['.name']
if n and (n:match("^eth%d+$") or n:match("^lan%d+$")) and not seen[n] then
seen[n] = true; table.insert(ports, n)
end
end)
table.sort(ports)
return ports
end
local function port_label(p)
local f = io.open("/sys/class/net/" .. p .. "/brport/bridge", "r")
if f then f:close(); return p .. " (in bridge — will be removed on apply)" end
return p .. " (free)"
end
local function read_bytes(iface)
local rx, tx = 0, 0
local f = io.open("/sys/class/net/" .. iface .. "/statistics/rx_bytes", "r")
if f then rx = tonumber(f:read("*l")) or 0; f:close() end
f = io.open("/sys/class/net/" .. iface .. "/statistics/tx_bytes", "r")
if f then tx = tonumber(f:read("*l")) or 0; f:close() end
return rx, tx
end
local function human_rate(bps)
if bps < 0 then bps = 0 end
if bps < 1024 then return string.format("%d B/s", bps)
elseif bps < 1048576 then return string.format("%.1f KB/s", bps / 1024)
elseif bps < 1073741824 then return string.format("%.1f MB/s", bps / 1048576)
else return string.format("%.2f GB/s", bps / 1073741824) end
end
local function read_override(iface)
-- Try multiple batctl syntaxes — first one that returns a digit wins
local cmds = {
"batctl hardif " .. iface .. " throughput_override",
"batctl hardif " .. iface .. " to",
"batctl meshif bat0 hardif " .. iface .. " throughput_override",
}
for _, cmd in ipairs(cmds) do
local fp = io.popen(cmd .. " 2>/dev/null")
if fp then
local val = fp:read("*l")
fp:close()
if val and val ~= "" and val:match("%d") then
return val
end
end
end
return "?"
end
local function batman_ping(mac)
if not mac or mac == "" or mac == "—" then return "—" end
-- 30-second cache — page reloads within that window are instant
local cachepath = "/tmp/easymesh-ping-" .. mac:gsub(":", "")
local f = io.open(cachepath, "r")
if f then
local ts = tonumber(f:read("*l") or "0")
local val = f:read("*l") or "—"
f:close()
if ts and (os.time() - ts) < 30 then
return val
end
end
local fp = io.popen("batctl ping -c 2 -i 0.2 -t 1 " .. mac .. " 2>/dev/null")
if not fp then return "—" end
local min_ms = nil
for line in fp:lines() do
local t = line:match("time=([%d%.]+)")
if t then
local ms = tonumber(t)
if ms and (not min_ms or ms < min_ms) then min_ms = ms end
end
end
fp:close()
local result = min_ms and string.format("%.1f ms", min_ms) or "—"
local w = io.open(cachepath, "w")
if w then w:write(os.time(), "\n", result, "\n"); w:close() end
return result
end
-- =========================================================
-- Active hardifs + live bandwidth sampling (1s window)
-- =========================================================
local active_ifaces = {}
local afl = io.popen("batctl if 2>/dev/null")
if afl then
for line in afl:lines() do
local iface = line:match("^([^:]+):%s*active")
if iface then table.insert(active_ifaces, iface) end
end
afl:close()
end
local s1, s2 = {}, {}
for _, iface in ipairs(active_ifaces) do
local rx, tx = read_bytes(iface); s1[iface] = { rx = rx, tx = tx }
end
if #active_ifaces > 0 then
os.execute("sleep 1")
for _, iface in ipairs(active_ifaces) do
local rx, tx = read_bytes(iface); s2[iface] = { rx = rx, tx = tx }
end
end
local rates = {}
for _, iface in ipairs(active_ifaces) do
rates[iface] = {
rx = human_rate(s2[iface].rx - s1[iface].rx),
tx = human_rate(s2[iface].tx - s1[iface].tx),
}
end
-- =========================================================
-- Originators (for wired neighbor / last-seen)
-- =========================================================
local function parse_originators()
local by_iface = {}
local fp = io.popen("batctl o 2>/dev/null | tail -n +2")
if not fp then return by_iface end
for line in fp:lines() do
local starred = line:match("^%s*%*") ~= nil
local mac, seen = line:match("([0-9a-fA-F:]+)%s+(%d+%.%d+s)")
local iface = line:match("%[%s*([%w%-_]+)%s*%]")
if mac and iface then
if not by_iface[iface] then by_iface[iface] = {} end
table.insert(by_iface[iface], { mac = mac, seen = seen, starred = starred })
end
end
fp:close()
return by_iface
end
local originators = parse_originators()
-- =========================================================
-- Wireless peers table (with Priority column)
-- =========================================================
local function detect_nodes()
local data = {}
local fp = io.popen("batctl n 2>/dev/null | tail -n +2")
if not fp then return data end
for line in fp:lines() do
local iface, neighbor, lastseen = line:match("^%s*(%S+)%s+(%S+)%s+(%S+)")
if iface and neighbor and neighbor:match("^[0-9a-fA-F:]+$") then
if iface:match("^phy") or iface:match("mesh") then
table.insert(data, { IF = iface, Neighbor = neighbor, lastseen = lastseen })
end
end
end
fp:close()
return data
end
local wireless_count = 0
local wired_count = 0
for _, iface in ipairs(active_ifaces) do
if iface:match("^phy") or iface:match("mesh") then
wireless_count = wireless_count + 1
elseif iface:match("^eth") or iface:match("^lan") then
wired_count = wired_count + 1
end
end
local total_links = wireless_count + wired_count
local status_line
if wired_count > 0 then
status_line = translate("Active mesh links") .. ": " .. total_links ..
" (" .. wireless_count .. " wireless + " .. wired_count .. " wired)"
else
status_line = translate("Active mesh links") .. ": " .. wireless_count .. " (all wireless)"
end
local peers = detect_nodes()
for _, p in ipairs(peers) do
-- Skip ping if lastseen > 30s → no timeout cost for stale entries
local ls = tonumber((p.lastseen or ""):match("([%d%.]+)") or "9999")
if ls and ls < 30 then
p.Latency = batman_ping(p.Neighbor)
else
p.Latency = translate("stale")
end
p.Priority = read_override(p.IF)
local r = rates[p.IF]
if r then p.BW_RX = r.rx; p.BW_TX = r.tx
else p.BW_RX = "—"; p.BW_TX = "—" end
end
local v = m:section(Table, peers, "", "<b>" .. status_line .. "</b>")
v:option(DummyValue, "IF", translate("Interface"))
v:option(DummyValue, "Neighbor", translate("Neighbor MAC"))
v:option(DummyValue, "lastseen", translate("Last seen"))
v:option(DummyValue, "Latency", translate("Latency"))
v:option(DummyValue, "BW_RX", translate("RX rate"))
v:option(DummyValue, "BW_TX", translate("TX rate"))
v:option(DummyValue, "Priority", translate("Priority weight"))
-- =========================================================
-- Wired backhaul details
-- =========================================================
local function detect_wired_details()
local rows = {}
local fp = io.popen("batctl if 2>/dev/null")
if not fp then return rows end
for line in fp:lines() do
local iface, state = line:match("^([^:]+):%s*(%S+)")
if iface and state == "active" and
(iface:match("^eth%d+$") or iface:match("^lan%d+$")) then
local carrier = "?"
local cf = io.open("/sys/class/net/" .. iface .. "/carrier", "r")
if cf then local val = cf:read("*l"); cf:close()
carrier = (val == "1") and "yes" or "no" end
local speed = "?"
local sf = io.open("/sys/class/net/" .. iface .. "/speed", "r")
if sf then local val = sf:read("*l"); sf:close()
if val and val ~= "" and val ~= "-1" then speed = val .. " Mbps" end end
local override = read_override(iface)
local bridged = "no"
local bf = io.open("/sys/class/net/" .. iface .. "/brport/bridge", "r")
if bf then bf:close(); bridged = "yes" end
local entries = originators[iface] or {}
local best = nil
for _, e in ipairs(entries) do
if e.starred then best = e; break end
end
best = best or entries[1]
local neighbor = best and best.mac or "—"
local lastseen = best and best.seen or "—"
local latency = batman_ping(neighbor)
local r = rates[iface]
table.insert(rows, {
IF = iface, State = state, Cable = carrier, Speed = speed,
Neighbor = neighbor, Lastseen = lastseen, Latency = latency,
BW_RX = r and r.rx or "—", BW_TX = r and r.tx or "—",
Override = override, Bridged = bridged,
})
end
end
fp:close()
return rows
end
local wired_rows = detect_wired_details()
if #wired_rows > 0 then
local wv = m:section(Table, wired_rows,
"<b>" .. translate("Wired backhaul details") .. "</b>")
wv:option(DummyValue, "IF", translate("Interface"))
wv:option(DummyValue, "State", translate("State"))
wv:option(DummyValue, "Cable", translate("Cable"))
wv:option(DummyValue, "Speed", translate("Link speed"))
wv:option(DummyValue, "Neighbor", translate("Neighbor MAC"))
wv:option(DummyValue, "Lastseen", translate("Last seen"))
wv:option(DummyValue, "Latency", translate("Latency"))
wv:option(DummyValue, "BW_RX", translate("RX rate"))
wv:option(DummyValue, "BW_TX", translate("TX rate"))
wv:option(DummyValue, "Override", translate("Priority weight"))
wv:option(DummyValue, "Bridged", translate("In LAN bridge"))
end
-- =========================================================
-- TOP: Preferred wired port
-- =========================================================
local wp = m:section(TypedSection, "easymesh", translate("Wired backhaul port"),
translate("Convenience selector. Mirrors 'Wired backhaul interface' below. " ..
"Presets that use wired backhaul will remove this port from the LAN bridge. " ..
"Pick a port NOT used by your uplink or modem. " ..
"Leave empty to disable wired backhaul entirely."))
wp.anonymous = true
o = wp:option(ListValue, "preferred_wired_port", translate("Preferred wired port"))
o:value("", translate("(none — wired presets will skip)"))
for _, p in ipairs(list_eth_ports()) do o:value(p, port_label(p)) end
o.default = ""
o.rmempty = false
-- =========================================================
-- Boot-time reliability (cron toggle)
-- =========================================================
local cf = m:section(TypedSection, "easymesh",
translate("Boot-time reliability"),
translate("The init script applies link priorities at boot and 90s later. " ..
"On some hardware, mesh interfaces appear even later, leaving " ..
"priority weight at 0. Enable cron fallback to reapply priority " ..
"every minute. Safe (idempotent) but runs in the background. " ..
"When disabled, the cron line is commented out in /etc/crontabs/root."))
cf.anonymous = true
o = cf:option(Flag, "cron_fallback", translate("Enable cron fallback"))
o.default = "0"
o.rmempty = false
-- =========================================================
-- Link priority weights (editable)
-- =========================================================
local lp = m:section(TypedSection, "easymesh",
translate("Link priority weights"),
translate("Batman-adv throughput_override values in Kbit/s. " ..
"Higher = preferred. Set to 0 to let batman auto-decide per link. " ..
"Defaults: wired=1000000, 6G=900000, 5G=600000, 2.4G=30000. " ..
"Changes apply on next Save & Apply (or 'Reapply EasyMesh Settings')."))
lp.anonymous = true
o = lp:option(Value, "priority_wired", translate("Wired (Kbit/s)"))
o.default = "1000000"
o.datatype = "uinteger"
o.rmempty = false
o = lp:option(Value, "priority_6g", translate("6 GHz wireless (Kbit/s)"))
o.default = "900000"
o.datatype = "uinteger"
o.rmempty = false
o = lp:option(Value, "priority_5g", translate("5 GHz wireless (Kbit/s)"))
o.default = "600000"
o.datatype = "uinteger"
o.rmempty = false
o = lp:option(Value, "priority_24g", translate("2.4 GHz wireless (Kbit/s)"))
o.default = "30000"
o.datatype = "uinteger"
o.rmempty = false
-- =========================================================
-- Mesh VLANs (extend guest/IoT/hotspot over mesh)
-- =========================================================
local mv = m:section(TypedSection, "easymesh",
translate("Mesh VLANs"),
translate("Extend guest / IoT / hotspot networks across the mesh. " ..
"Enter a comma-separated list of VLAN IDs (e.g. 10,20,30). " ..
"The init script creates bat0.<vid> as an 8021q device for each ID. " ..
"If a bridge named br-<vid>, br-lan<vid>, br-vlan<vid>, br-guest<vid>, " ..
"or br-iot<vid> already exists, it is auto-added to that bridge. " ..
"Otherwise, add the bat0.<vid> device manually in " ..
"Network → Interfaces → Devices. " ..
"Requires 802.1Q-capable switches on the physical wired backhaul."))
mv.anonymous = true
o = mv:option(Value, "mesh_vlans",
translate("VLAN IDs (comma-separated)"),
translate("Example: 10,20,30 — Leave empty to disable"))
o.default = ""
o.datatype = "maxlength(200)"
o.rmempty = false
-- =========================================================
-- QUICK SETUP — 19 presets
-- =========================================================
local q = m:section(TypedSection, "easymesh", translate("Quick Setup"),
translate("Click a preset. A confirmation dialog will appear. " ..
"Current config is backed up automatically before applying. " ..
"Grouped by role: SERVER (has internet) → CLIENT (uses mesh) → NODE (relay only)."))
q.anonymous = true
local presets = {
{ id = "srv_5g", values = { enabled="1", role="server", band_mode="single_5g", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="" }, label = translate("1. Server — 5 GHz wireless only"), desc = translate("[SERVER] Gateway. Extenders join via 5 GHz mesh only.") },
{ id = "srv_2g", values = { enabled="1", role="server", band_mode="single_2g", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="" }, label = translate("2. Server — 2.4 GHz wireless only"), desc = translate("[SERVER] Extenders join via 2.4 GHz (longer range).") },
{ id = "srv_dual", values = { enabled="1", role="server", band_mode="dual", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="" }, label = translate("3. Server — Dual-band wireless only"), desc = translate("[SERVER] Both radios carry backhaul.") },
{ id = "srv_wired", values = { enabled="1", role="server", band_mode="none", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="AUTO_DETECT" }, label = translate("4. Server — Wired backhaul only"), desc = translate("[SERVER] No wireless mesh — extenders connect by cable.") },
{ id = "srv_5g_wired", values = { enabled="1", role="server", band_mode="single_5g_wired", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="AUTO_DETECT" }, label = translate("5. Server — 5 GHz + Wired"), desc = translate("[SERVER] 5 GHz mesh primary, Ethernet parallel.") },
{ id = "srv_2g_wired", values = { enabled="1", role="server", band_mode="single_2g_wired", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="AUTO_DETECT" }, label = translate("6. Server — 2.4 GHz + Wired"), desc = translate("[SERVER] 2.4 GHz mesh plus Ethernet.") },
{ id = "srv_dual_wired", values = { enabled="1", role="server", band_mode="dual_wired", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="AUTO_DETECT" }, label = translate("7. Server — Dual-band + Wired"), desc = translate("[SERVER] Maximum redundancy. Both radios + Ethernet.") },
{ id = "cli_5g", values = { enabled="1", role="client", band_mode="single_5g", encryption="0", kvr="1", ap_mode="1", ip_mode="dhcp", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="" }, label = translate("8. Client — 5 GHz wireless only"), desc = translate("[CLIENT] Extender via 5 GHz. No cable.") },
{ id = "cli_2g", values = { enabled="1", role="client", band_mode="single_2g", encryption="0", kvr="1", ap_mode="1", ip_mode="dhcp", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="" }, label = translate("9. Client — 2.4 GHz wireless only"), desc = translate("[CLIENT] Extender via 2.4 GHz.") },
{ id = "cli_dual", values = { enabled="1", role="client", band_mode="dual", encryption="0", kvr="1", ap_mode="1", ip_mode="dhcp", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="" }, label = translate("10. Client — Dual-band wireless only"), desc = translate("[CLIENT] Extender via both radios.") },
{ id = "cli_wired", values = { enabled="1", role="client", band_mode="none", encryption="0", kvr="1", ap_mode="1", ip_mode="dhcp", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="AUTO_DETECT" }, label = translate("11. Client — Wired backhaul only"), desc = translate("[CLIENT] Cable only, no wireless mesh.") },
{ id = "cli_5g_wired", values = { enabled="1", role="client", band_mode="single_5g_wired", encryption="0", kvr="1", ap_mode="1", ip_mode="dhcp", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="AUTO_DETECT" }, label = translate("12. Client — 5 GHz + Wired fallback"), desc = translate("[CLIENT] 5 GHz primary, Ethernet fallback.") },
{ id = "cli_2g_wired", values = { enabled="1", role="client", band_mode="single_2g_wired", encryption="0", kvr="1", ap_mode="1", ip_mode="dhcp", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="AUTO_DETECT" }, label = translate("13. Client — 2.4 GHz + Wired fallback"), desc = translate("[CLIENT] 2.4 GHz primary, Ethernet fallback.") },
{ id = "cli_dual_wired", values = { enabled="1", role="client", band_mode="dual_wired", encryption="0", kvr="1", ap_mode="1", ip_mode="dhcp", ap_enabled_2g="1", ap_enabled_5g="1", ap_network="lan", wired_if="AUTO_DETECT" }, label = translate("14. Client — Dual-band + Wired fallback"), desc = translate("[CLIENT] Both radios + Ethernet. Maximum redundancy.") },
{ id = "node_5g", values = { enabled="1", role="node", band_mode="single_5g", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="0", ap_enabled_5g="0", wired_if="" }, label = translate("15. Node — 5 GHz relay"), desc = translate("[NODE] Relay only, 5 GHz. No client SSID.") },
{ id = "node_dual", values = { enabled="1", role="node", band_mode="dual", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="0", ap_enabled_5g="0", wired_if="" }, label = translate("16. Node — Dual-band relay"), desc = translate("[NODE] Relay only, both radios.") },
{ id = "node_wired", values = { enabled="1", role="node", band_mode="none", encryption="0", kvr="1", ap_mode="0", ap_enabled_2g="0", ap_enabled_5g="0", wired_if="AUTO_DETECT" }, label = translate("17. Node — Wired relay only"), desc = translate("[NODE] Relay via cable only. No wireless.") },
{ id = "revert_wired", values = {}, custom = "detach_wired", label = translate("18. Detach wired backhaul"), desc = translate("Removes Ethernet backhaul, restores port to bridge.") },
{ id = "disable", values = { enabled="0" }, label = translate("19. Disable mesh"), desc = translate("Turns off mesh. Radios stay enabled.") },
}
for _, p in ipairs(presets) do
local btn = q:option(Button, "_preset_" .. p.id, p.label)
btn.inputstyle = "apply"
btn.description = p.desc
function btn.write(self, section)
luci.sys.call("/etc/init.d/easymesh backup >/tmp/easymesh-preset.log 2>&1")
if p.custom == "detach_wired" then
luci.sys.call("/etc/init.d/easymesh detach_wired >>/tmp/easymesh-preset.log 2>&1")
else
local cur = luci.model.uci.cursor()
local needs_wired = false
for _, val in pairs(p.values) do
if val == "AUTO_DETECT" then needs_wired = true; break end
end
local wired_port = ""
if needs_wired then
wired_port = get_preferred_wired_port()
if wired_port == "" then
luci.sys.call("logger -t easymesh 'Preset needs wired but no port chosen — wired disabled'")
end
end
for k, val in pairs(p.values) do
if val ~= "AUTO_DETECT" then cur:set("easymesh", "config", k, val) end
end
cur:set("easymesh", "config", "wired_if", wired_port)
cur:set("easymesh", "config", "preferred_wired_port", wired_port)
if needs_wired and wired_port == "" then
local bm = p.values.band_mode or "single_5g"
bm = tostring(bm):gsub("_wired$", "")
cur:set("easymesh", "config", "band_mode", bm)
end
cur:commit("easymesh")
luci.sys.call("/etc/init.d/easymesh restart >>/tmp/easymesh-preset.log 2>&1")
end
luci.http.redirect(luci.dispatcher.build_url("admin", "network", "easymesh"))
end
end
-- =========================================================
-- MESH SETTINGS
-- =========================================================
s = m:section(TypedSection, "easymesh", translate("Mesh settings"))
s.anonymous = true
o = s:option(Flag, "enabled", translate("Enable EasyMesh"))
o.default = "0"; o.rmempty = false
o = s:option(ListValue, "role", translate("Mesh role"))
o:value("off", translate("Off"))
o:value("server", translate("Server (gateway - has internet)"))
o:value("client", translate("Client (uses mesh internet)"))
o:value("node", translate("Node (relay only)"))
o.default = "off"; o.rmempty = false
o = s:option(ListValue, "band_mode", translate("Mesh mode"),
translate("Combined wireless band + wired backhaul. Wired variants use the port selected above."))
o:value("single_5g", translate("5 GHz wireless only"))
o:value("single_2g", translate("2.4 GHz wireless only"))
o:value("dual", translate("Dual-band wireless only"))
o:value("single_5g_wired", translate("5 GHz wireless + Wired backhaul"))
o:value("single_2g_wired", translate("2.4 GHz wireless + Wired backhaul"))
o:value("dual_wired", translate("Dual-band wireless + Wired backhaul"))
o:value("none", translate("Wired only (no wireless mesh)"))
o:value("custom", translate("Custom radio"))
o.default = "single_5g"
o:depends("enabled", "1")
local apRadio = s:option(ListValue, "custom_radio", translate("Custom mesh radio"))
uci:foreach("wireless", "wifi-device", function(sect)
local name = sect['.name']
local band = sect.band or "?"
local label = name .. " ("
if band == "2g" then label = label .. "2.4 GHz"
elseif band == "5g" then label = label .. "5 GHz"
elseif band == "6g" then label = label .. "6 GHz"
else label = label .. band end
apRadio:value(name, label .. ")")
end)
apRadio:depends("band_mode", "custom")
o = s:option(Value, "mesh_id", translate("Mesh ID"),
translate("Must match on all nodes. Ignored in wired-only mode."))
o.default = "EasyMesh_Dualband"; o.datatype = "maxlength(32)"; o:depends("enabled", "1")
o = s:option(Flag, "encryption", translate("Require password (WPA3-SAE)"))
o.default = "0"; o.rmempty = false; o:depends("enabled", "1")
o = s:option(Value, "key", translate("Mesh password"),
translate("Must match on all nodes. Only used when 'Require password' is enabled."))
o.default = "changeme123"; o.password = true; o.rmempty = false; o:depends("encryption", "1")
o = s:option(Flag, "kvr", translate("Enable 802.11k/v/r fast roaming"))
o.default = "1"; o.rmempty = false; o:depends("enabled", "1")
o = s:option(Value, "mobility_domain", translate("Mobility domain"))
o.default = "4f57"; o.datatype = "and(hexstring,rangelength(4,4))"; o:depends("kvr", "1")
-- =========================================================
-- CLIENT WIFI — 2.4 GHz
-- =========================================================
local t24 = m:section(TypedSection, "easymesh", translate("Client WiFi — 2.4 GHz"),
translate("Longer range, lower speed. Use a different SSID from 5 GHz."))
t24.anonymous = true
o = t24:option(Flag, "ap_enabled_2g", translate("Broadcast 2.4 GHz"))
o.default = "1"; o.rmempty = false; o:depends("enabled", "1")
o = t24:option(Value, "ap_ssid_2g", translate("2.4 GHz SSID"))
o.default = "EasyMesh_WiFi"; o.datatype = "maxlength(32)"; o:depends("ap_enabled_2g", "1")
o = t24:option(ListValue, "ap_encryption_2g", translate("2.4 GHz security"))
o:value("none", translate("None (open)"))
o:value("psk2+ccmp", translate("WPA2-PSK"))
o:value("sae", translate("WPA3-SAE"))
o:value("sae-mixed", translate("WPA2/WPA3 transition"))
o.default = "sae-mixed"; o:depends("ap_enabled_2g", "1")
o = t24:option(Value, "ap_key_2g", translate("2.4 GHz password"))
o.default = "changeme123"; o.password = true
o:depends("ap_encryption_2g", "psk2+ccmp")
o:depends("ap_encryption_2g", "sae")
o:depends("ap_encryption_2g", "sae-mixed")
-- =========================================================
-- CLIENT WIFI — 5 GHz
-- =========================================================
local t5 = m:section(TypedSection, "easymesh", translate("Client WiFi — 5 GHz"),
translate("Shorter range, much faster. Use a different SSID from 2.4 GHz."))
t5.anonymous = true
o = t5:option(Flag, "ap_enabled_5g", translate("Broadcast 5 GHz"))
o.default = "1"; o.rmempty = false; o:depends("enabled", "1")
o = t5:option(Value, "ap_ssid_5g", translate("5 GHz SSID"))
o.default = "EasyMesh_WiFi_5G"; o.datatype = "maxlength(32)"; o:depends("ap_enabled_5g", "1")
o = t5:option(ListValue, "ap_encryption_5g", translate("5 GHz security"))
o:value("none", translate("None (open)"))
o:value("psk2+ccmp", translate("WPA2-PSK"))
o:value("sae", translate("WPA3-SAE"))
o:value("sae-mixed", translate("WPA2/WPA3 transition"))
o.default = "sae-mixed"; o:depends("ap_enabled_5g", "1")
o = t5:option(Value, "ap_key_5g", translate("5 GHz password"))
o.default = "changeme123"; o.password = true
o:depends("ap_encryption_5g", "psk2+ccmp")
o:depends("ap_encryption_5g", "sae")
o:depends("ap_encryption_5g", "sae-mixed")
local tn = m:section(TypedSection, "easymesh", translate("Client WiFi — shared"))
tn.anonymous = true
o = tn:option(ListValue, "ap_network", translate("Attach client WiFi to network"))
o.default = "lan"
uci:foreach("network", "interface", function(sect)
local name = sect['.name']
if name then o:value(name, name) end
end)
o:depends("enabled", "1")
-- =========================================================
-- AP MODE
-- =========================================================
local u = m:section(TypedSection, "easymesh", translate("AP Mode (Dumb AP)"))
u.anonymous = true
o = u:option(Flag, "ap_mode", translate("Enable AP Mode (Dumb AP)"))
o.default = "0"; o.rmempty = false
o:depends("role", "client"); o:depends("role", "node")
o = u:option(ListValue, "ip_mode", translate("IP mode"))
o:value("dhcp", translate("DHCP (from Server)"))
o:value("static", translate("Static IP"))
o.default = "static"; o:depends("ap_mode", "1")
o = u:option(Value, "ipaddr", translate("IP address"))
o.default = "192.168.10.10"; o.datatype = "ip4addr"; o:depends("ip_mode", "static")
o = u:option(Value, "netmask", translate("Netmask"))
o.default = "255.255.255.0"; o.datatype = "ip4addr"; o:depends("ip_mode", "static")
o = u:option(Value, "gateway", translate("Gateway"))
o.default = "192.168.10.1"; o.datatype = "ip4addr"; o:depends("ip_mode", "static")
o = u:option(Value, "dns", translate("DNS server"))
o.default = "192.168.10.1"; o.datatype = "ip4addr"; o:depends("ip_mode", "static")
-- =========================================================
-- BOTTOM: Wired backhaul (source of truth)
-- =========================================================
local w = m:section(TypedSection, "easymesh", translate("Wired backhaul"),
translate("Optional. Adds a physical Ethernet port as a batman hardif. " ..
"Required on BOTH sides for a wired link. " ..
"The port is auto-removed from the LAN bridge on apply."))
w.anonymous = true
o = w:option(ListValue, "wired_if", translate("Wired backhaul interface"))
o:value("", translate("Disabled"))
for _, p in ipairs(list_eth_ports()) do o:value(p, port_label(p)) end
-- =========================================================
-- Safety and actions
-- =========================================================
local a = m:section(TypedSection, "easymesh", translate("Safety and actions"))
a.anonymous = true
o = a:option(Flag, "safe_apply", translate("Safe apply (60-second rollback)"))
o.default = "0"; o.rmempty = false
local reapply = a:option(Button, "_reapply", translate("Reapply EasyMesh Settings"))
reapply.inputtitle = translate("Reapply EasyMesh Settings")
reapply.inputstyle = "apply"
function reapply.write(self, section)
luci.sys.call("/etc/init.d/easymesh restart >/tmp/easymesh-reapply.log 2>&1")
luci.http.redirect(luci.dispatcher.build_url("admin", "network", "easymesh"))
end
local restore = a:option(Button, "_restore", translate("Restore from last backup"))
restore.inputtitle = translate("Restore from last backup")
restore.inputstyle = "reset"
function restore.write(self, section)
luci.sys.call("/etc/init.d/easymesh restore >/tmp/easymesh-restore.log 2>&1")
luci.http.redirect(luci.dispatcher.build_url("admin", "network", "easymesh"))
end
local pairbtn = a:option(Button, "_pair", translate("Pair with another router"))
pairbtn.inputtitle = translate("Enter pairing mode (2 minutes)")
pairbtn.inputstyle = "apply"
pairbtn.description = translate("WPS-style pairing. Press the WPS button on both routers within 2 minutes. Auto-reverts on timeout.")
function pairbtn.write(self, section)
luci.sys.call("/etc/init.d/easymesh pair >/tmp/easymesh-pair.log 2>&1 &")
luci.http.redirect(luci.dispatcher.build_url("admin", "network", "easymesh"))
end
local confirm_section = m:section(SimpleSection)
confirm_section.template = "easymesh/preset_confirm"
return m
@@ -0,0 +1,28 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-easymesh-wpad-mbedtls
PKG_VERSION:=2.4.1
PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Arafat Rahman Zami Mondol <arafatrahmanzami@users.noreply.github.com>
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-easymesh-wpad-mbedtls
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=EasyMesh — install with wpad-mesh-mbedtls
DEPENDS:=+luci-app-easymesh +wpad-mesh-mbedtls
PKGARCH:=all
endef
define Build/Compile
endef
define Package/luci-app-easymesh-wpad-mbedtls/install
# Meta-package — no files.
endef
$(eval $(call BuildPackage,luci-app-easymesh-wpad-mbedtls))
@@ -0,0 +1,28 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-easymesh-wpad-openssl
PKG_VERSION:=2.4.1
PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Arafat Rahman Zami Mondol <arafatrahmanzami@users.noreply.github.com>
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-easymesh-wpad-openssl
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=EasyMesh — install with wpad-mesh-openssl
DEPENDS:=+luci-app-easymesh +wpad-mesh-openssl
PKGARCH:=all
endef
define Build/Compile
endef
define Package/luci-app-easymesh-wpad-openssl/install
# Meta-package — no files.
endef
$(eval $(call BuildPackage,luci-app-easymesh-wpad-openssl))
@@ -0,0 +1,28 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-easymesh-wpad-wolfssl
PKG_VERSION:=2.4.1
PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Arafat Rahman Zami Mondol <arafatrahmanzami@users.noreply.github.com>
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-easymesh-wpad-wolfssl
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=EasyMesh — install with wpad-mesh-wolfssl
DEPENDS:=+luci-app-easymesh +wpad-mesh-wolfssl
PKGARCH:=all
endef
define Build/Compile
endef
define Package/luci-app-easymesh-wpad-wolfssl/install
# Meta-package — no files. Pulls in luci-app-easymesh + wpad-mesh-wolfssl.
endef
$(eval $(call BuildPackage,luci-app-easymesh-wpad-wolfssl))
+467
View File
@@ -0,0 +1,467 @@
msgid ""
msgstr ""
"Project-Id-Version: luci-app-easymesh 2.4.1-r1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-19 12:00+0600\n"
"PO-Revision-Date: 2026-09-19 12:00+0600\n"
"Last-Translator: Arafat Rahman Zami Mondol <zamimondol@gmail.com>\n"
"Language-Team: Bengali <bn@li.org>\n"
"Language: bn\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
# =========================================================
# Main app title & description
# =========================================================
msgid "EasyMesh"
msgstr "EasyMesh"
msgid "Configure a Batman-adv mesh network."
msgstr "একটি Batman-adv মেশ নেটওয়ার্ক কনফিগার করুন।"
# =========================================================
# Dashboard status
# =========================================================
msgid "Active mesh links"
msgstr "সক্রিয় মেশ সংযোগ"
msgid "Interface"
msgstr "ইন্টারফেস"
msgid "Neighbor MAC"
msgstr "প্রতিবেশী MAC"
msgid "Last seen"
msgstr "সর্বশেষ দেখা"
msgid "Latency"
msgstr "লেটেন্সি"
msgid "RX rate"
msgstr "RX হার"
msgid "TX rate"
msgstr "TX হার"
# =========================================================
# Wired backhaul details
# =========================================================
msgid "Wired backhaul details"
msgstr "তারযুক্ত ব্যাকহল বিস্তারিত"
msgid "State"
msgstr "অবস্থা"
msgid "Cable"
msgstr "কেবল"
msgid "Link speed"
msgstr "সংযোগ গতি"
msgid "Priority weight"
msgstr "অগ্রাধিকার ওজন"
msgid "In LAN bridge"
msgstr "LAN ব্রিজে"
# =========================================================
# Wired port picker
# =========================================================
msgid "Wired backhaul port"
msgstr "তারযুক্ত ব্যাকহল পোর্ট"
msgid "Preferred wired port"
msgstr "পছন্দের তারযুক্ত পোর্ট"
msgid "(none — wired presets will skip)"
msgstr "(কোনোটি নয় — তারযুক্ত প্রিসেট এড়িয়ে যাবে)"
# =========================================================
# Boot-time reliability / Cron
# =========================================================
msgid "Boot-time reliability"
msgstr "বুট-সময় নির্ভরযোগ্যতা"
msgid "Enable cron fallback"
msgstr "cron ফলব্যাক সক্রিয় করুন"
# =========================================================
# Link priority weights (new)
# =========================================================
msgid "Link priority weights"
msgstr "সংযোগ অগ্রাধিকার ওজন"
msgid "Wired (Kbit/s)"
msgstr "তারযুক্ত (Kbit/s)"
msgid "6 GHz wireless (Kbit/s)"
msgstr "6 GHz বেতার (Kbit/s)"
msgid "5 GHz wireless (Kbit/s)"
msgstr "5 GHz বেতার (Kbit/s)"
msgid "2.4 GHz wireless (Kbit/s)"
msgstr "2.4 GHz বেতার (Kbit/s)"
# =========================================================
# Mesh VLANs (new)
# =========================================================
msgid "Mesh VLANs"
msgstr "মেশ VLAN"
msgid "VLAN IDs (comma-separated)"
msgstr "VLAN ID (কমা-বিভক্ত)"
# =========================================================
# Quick Setup
# =========================================================
msgid "Quick Setup"
msgstr "দ্রুত সেটআপ"
msgid "1. Server — 5 GHz wireless only"
msgstr "১. সার্ভার — শুধু 5 GHz বেতার"
msgid "2. Server — 2.4 GHz wireless only"
msgstr "২. সার্ভার — শুধু 2.4 GHz বেতার"
msgid "3. Server — Dual-band wireless only"
msgstr "৩. সার্ভার — শুধু দ্বি-ব্যান্ড বেতার"
msgid "4. Server — Wired backhaul only"
msgstr "৪. সার্ভার — শুধু তারযুক্ত ব্যাকহল"
msgid "5. Server — 5 GHz + Wired"
msgstr "৫. সার্ভার — 5 GHz + তারযুক্ত"
msgid "6. Server — 2.4 GHz + Wired"
msgstr "৬. সার্ভার — 2.4 GHz + তারযুক্ত"
msgid "7. Server — Dual-band + Wired"
msgstr "৭. সার্ভার — দ্বি-ব্যান্ড + তারযুক্ত"
msgid "8. Client — 5 GHz wireless only"
msgstr "৮. ক্লায়েন্ট — শুধু 5 GHz বেতার"
msgid "9. Client — 2.4 GHz wireless only"
msgstr "৯. ক্লায়েন্ট — শুধু 2.4 GHz বেতার"
msgid "10. Client — Dual-band wireless only"
msgstr "১০. ক্লায়েন্ট — শুধু দ্বি-ব্যান্ড বেতার"
msgid "11. Client — Wired backhaul only"
msgstr "১১. ক্লায়েন্ট — শুধু তারযুক্ত ব্যাকহল"
msgid "12. Client — 5 GHz + Wired fallback"
msgstr "১২. ক্লায়েন্ট — 5 GHz + তারযুক্ত ফলব্যাক"
msgid "13. Client — 2.4 GHz + Wired fallback"
msgstr "১৩. ক্লায়েন্ট — 2.4 GHz + তারযুক্ত ফলব্যাক"
msgid "14. Client — Dual-band + Wired fallback"
msgstr "১৪. ক্লায়েন্ট — দ্বি-ব্যান্ড + তারযুক্ত ফলব্যাক"
msgid "15. Node — 5 GHz relay"
msgstr "১৫. নোড — 5 GHz রিলে"
msgid "16. Node — Dual-band relay"
msgstr "১৬. নোড — দ্বি-ব্যান্ড রিলে"
msgid "17. Node — Wired relay only"
msgstr "১৭. নোড — শুধু তারযুক্ত রিলে"
msgid "18. Detach wired backhaul"
msgstr "১৮. তারযুক্ত ব্যাকহল বিচ্ছিন্ন করুন"
msgid "19. Disable mesh"
msgstr "১৯. মেশ বন্ধ করুন"
# Preset descriptions
msgid "[SERVER] Gateway. Extenders join via 5 GHz mesh only."
msgstr "[সার্ভার] গেটওয়ে। এক্সটেন্ডারগুলি শুধু 5 GHz মেশ দিয়ে যুক্ত হবে।"
msgid "[SERVER] Extenders join via 2.4 GHz (longer range)."
msgstr "[সার্ভার] এক্সটেন্ডারগুলি 2.4 GHz দিয়ে যুক্ত হবে (দীর্ঘ পরিসর)।"
msgid "[SERVER] Both radios carry backhaul."
msgstr "[সার্ভার] উভয় রেডিও ব্যাকহল বহন করবে।"
msgid "[SERVER] No wireless mesh — extenders connect by cable."
msgstr "[সার্ভার] কোনো বেতার মেশ নেই — এক্সটেন্ডারগুলি কেবল দিয়ে যুক্ত হবে।"
msgid "[SERVER] 5 GHz mesh primary, Ethernet parallel."
msgstr "[সার্ভার] 5 GHz মেশ প্রাথমিক, ইথারনেট সমান্তরাল।"
msgid "[SERVER] 2.4 GHz mesh plus Ethernet."
msgstr "[সার্ভার] 2.4 GHz মেশ এবং ইথারনেট।"
msgid "[SERVER] Maximum redundancy. Both radios + Ethernet."
msgstr "[সার্ভার] সর্বোচ্চ রিডান্ডেন্সি। উভয় রেডিও + ইথারনেট।"
msgid "[CLIENT] Extender via 5 GHz. No cable."
msgstr "[ক্লায়েন্ট] 5 GHz দিয়ে এক্সটেন্ডার। কেবল নেই।"
msgid "[CLIENT] Extender via 2.4 GHz."
msgstr "[ক্লায়েন্ট] 2.4 GHz দিয়ে এক্সটেন্ডার।"
msgid "[CLIENT] Extender via both radios."
msgstr "[ক্লায়েন্ট] উভয় রেডিও দিয়ে এক্সটেন্ডার।"
msgid "[CLIENT] Cable only, no wireless mesh."
msgstr "[ক্লায়েন্ট] শুধু কেবল, কোনো বেতার মেশ নেই।"
msgid "[CLIENT] 5 GHz primary, Ethernet fallback."
msgstr "[ক্লায়েন্ট] 5 GHz প্রাথমিক, ইথারনেট ফলব্যাক।"
msgid "[CLIENT] 2.4 GHz primary, Ethernet fallback."
msgstr "[ক্লায়েন্ট] 2.4 GHz প্রাথমিক, ইথারনেট ফলব্যাক।"
msgid "[CLIENT] Both radios + Ethernet. Maximum redundancy."
msgstr "[ক্লায়েন্ট] উভয় রেডিও + ইথারনেট। সর্বোচ্চ রিডান্ডেন্সি।"
msgid "[NODE] Relay only, 5 GHz. No client SSID."
msgstr "[নোড] শুধু রিলে, 5 GHz। কোনো ক্লায়েন্ট SSID নেই।"
msgid "[NODE] Relay only, both radios."
msgstr "[নোড] শুধু রিলে, উভয় রেডিও।"
msgid "[NODE] Relay via cable only. No wireless."
msgstr "[নোড] শুধু কেবল দিয়ে রিলে। বেতার নেই।"
msgid "Removes Ethernet backhaul, restores port to bridge."
msgstr "ইথারনেট ব্যাকহল সরায়, পোর্ট ব্রিজে ফিরিয়ে দেয়।"
msgid "Turns off mesh. Radios stay enabled."
msgstr "মেশ বন্ধ করে। রেডিও চালু থাকে।"
# =========================================================
# Mesh settings
# =========================================================
msgid "Mesh settings"
msgstr "মেশ সেটিংস"
msgid "Enable EasyMesh"
msgstr "EasyMesh সক্রিয় করুন"
msgid "Mesh role"
msgstr "মেশ ভূমিকা"
msgid "Off"
msgstr "বন্ধ"
msgid "Server (gateway - has internet)"
msgstr "সার্ভার (গেটওয়ে - ইন্টারনেট আছে)"
msgid "Client (uses mesh internet)"
msgstr "ক্লায়েন্ট (মেশ ইন্টারনেট ব্যবহার করে)"
msgid "Node (relay only)"
msgstr "নোড (শুধু রিলে)"
msgid "Mesh mode"
msgstr "মেশ মোড"
msgid "5 GHz wireless only"
msgstr "শুধু 5 GHz বেতার"
msgid "2.4 GHz wireless only"
msgstr "শুধু 2.4 GHz বেতার"
msgid "Dual-band wireless only"
msgstr "শুধু দ্বি-ব্যান্ড বেতার"
msgid "5 GHz wireless + Wired backhaul"
msgstr "5 GHz বেতার + তারযুক্ত ব্যাকহল"
msgid "2.4 GHz wireless + Wired backhaul"
msgstr "2.4 GHz বেতার + তারযুক্ত ব্যাকহল"
msgid "Dual-band wireless + Wired backhaul"
msgstr "দ্বি-ব্যান্ড বেতার + তারযুক্ত ব্যাকহল"
msgid "Wired only (no wireless mesh)"
msgstr "শুধু তারযুক্ত (কোনো বেতার মেশ নেই)"
msgid "Custom radio"
msgstr "কাস্টম রেডিও"
msgid "Custom mesh radio"
msgstr "কাস্টম মেশ রেডিও"
msgid "Mesh ID"
msgstr "মেশ ID"
msgid "Require password (WPA3-SAE)"
msgstr "পাসওয়ার্ড আবশ্যক (WPA3-SAE)"
msgid "Mesh password"
msgstr "মেশ পাসওয়ার্ড"
msgid "Enable 802.11k/v/r fast roaming"
msgstr "802.11k/v/r দ্রুত রোমিং সক্রিয় করুন"
msgid "Mobility domain"
msgstr "মোবিলিটি ডোমেইন"
# =========================================================
# Client WiFi
# =========================================================
msgid "Client WiFi — 2.4 GHz"
msgstr "ক্লায়েন্ট WiFi — 2.4 GHz"
msgid "Broadcast 2.4 GHz"
msgstr "2.4 GHz সম্প্রচার করুন"
msgid "2.4 GHz SSID"
msgstr "2.4 GHz SSID"
msgid "2.4 GHz security"
msgstr "2.4 GHz নিরাপত্তা"
msgid "None (open)"
msgstr "কোনোটি নয় (খোলা)"
msgid "WPA2-PSK"
msgstr "WPA2-PSK"
msgid "WPA3-SAE"
msgstr "WPA3-SAE"
msgid "WPA2/WPA3 transition"
msgstr "WPA2/WPA3 ট্রানজিশন"
msgid "2.4 GHz password"
msgstr "2.4 GHz পাসওয়ার্ড"
msgid "Client WiFi — 5 GHz"
msgstr "ক্লায়েন্ট WiFi — 5 GHz"
msgid "Broadcast 5 GHz"
msgstr "5 GHz সম্প্রচার করুন"
msgid "5 GHz SSID"
msgstr "5 GHz SSID"
msgid "5 GHz security"
msgstr "5 GHz নিরাপত্তা"
msgid "5 GHz password"
msgstr "5 GHz পাসওয়ার্ড"
msgid "Client WiFi — shared"
msgstr "ক্লায়েন্ট WiFi — শেয়ার্ড"
msgid "Attach client WiFi to network"
msgstr "ক্লায়েন্ট WiFi নেটওয়ার্কে সংযুক্ত করুন"
# =========================================================
# AP Mode
# =========================================================
msgid "AP Mode (Dumb AP)"
msgstr "AP মোড (Dumb AP)"
msgid "Enable AP Mode (Dumb AP)"
msgstr "AP মোড সক্রিয় করুন (Dumb AP)"
msgid "IP mode"
msgstr "IP মোড"
msgid "DHCP (from Server)"
msgstr "DHCP (সার্ভার থেকে)"
msgid "Static IP"
msgstr "স্থির IP"
msgid "IP address"
msgstr "IP ঠিকানা"
msgid "Netmask"
msgstr "নেটমাস্ক"
msgid "Gateway"
msgstr "গেটওয়ে"
msgid "DNS server"
msgstr "DNS সার্ভার"
# =========================================================
# Wired backhaul
# =========================================================
msgid "Wired backhaul"
msgstr "তারযুক্ত ব্যাকহল"
msgid "Wired backhaul interface"
msgstr "তারযুক্ত ব্যাকহল ইন্টারফেস"
msgid "Disabled"
msgstr "নিষ্ক্রিয়"
# =========================================================
# Safety and actions
# =========================================================
msgid "Safety and actions"
msgstr "নিরাপত্তা ও ক্রিয়া"
msgid "Safe apply (60-second rollback)"
msgstr "নিরাপদ প্রয়োগ (৬০-সেকেন্ড রোলব্যাক)"
msgid "Reapply EasyMesh Settings"
msgstr "EasyMesh সেটিংস পুনঃপ্রয়োগ করুন"
msgid "Restore from last backup"
msgstr "সর্বশেষ ব্যাকআপ থেকে পুনরুদ্ধার করুন"
msgid "Pair with another router"
msgstr "অন্য রাউটারের সাথে পেয়ার করুন"
msgid "Enter pairing mode (2 minutes)"
msgstr "পেয়ারিং মোডে প্রবেশ করুন (২ মিনিট)"
# =========================================================
# Long descriptions
# =========================================================
msgid "Optional. Adds a physical Ethernet port as a batman hardif. Required on BOTH sides for a wired link. The port is auto-removed from the LAN bridge on apply."
msgstr "ঐচ্ছিক। একটি ফিজিক্যাল ইথারনেট পোর্ট batman hardif হিসেবে যোগ করে। তারযুক্ত সংযোগের জন্য উভয় পাশে প্রয়োজন। প্রয়োগের সময় পোর্ট স্বয়ংক্রিয়ভাবে LAN ব্রিজ থেকে সরানো হয়।"
msgid "WPS-style pairing. Press the WPS button on both routers within 2 minutes. Auto-reverts on timeout."
msgstr "WPS-স্টাইল পেয়ারিং। ২ মিনিটের মধ্যে উভয় রাউটারে WPS বোতাম চাপুন। সময় শেষ হলে স্বয়ংক্রিয়ভাবে ফিরে যায়।"
msgid "Batman-adv throughput_override values in Kbit/s. Higher = preferred. Set to 0 to let batman auto-decide per link. Defaults: wired=1000000, 6G=900000, 5G=600000, 2.4G=30000. Changes apply on next Save & Apply (or 'Reapply EasyMesh Settings')."
msgstr "Batman-adv throughput_override মান Kbit/s-এ। উচ্চতর = পছন্দের। প্রতিটি লিংকে batman-কে স্বয়ংক্রিয় সিদ্ধান্ত নিতে দিতে 0 সেট করুন। ডিফল্ট: তারযুক্ত=1000000, 6G=900000, 5G=600000, 2.4G=30000। পরিবর্তনগুলি পরবর্তী Save & Apply-এ (বা 'EasyMesh সেটিংস পুনঃপ্রয়োগ করুন') কার্যকর হয়।"
msgid "Extend guest / IoT / hotspot networks across the mesh. Enter a comma-separated list of VLAN IDs (e.g. 10,20,30). The init script creates bat0.<vid> as an 8021q device for each ID. If a bridge named br-<vid>, br-lan<vid>, br-vlan<vid>, br-guest<vid>, or br-iot<vid> already exists, it is auto-added to that bridge. Otherwise, add the bat0.<vid> device manually in Network → Interfaces → Devices. Requires 802.1Q-capable switches on the physical wired backhaul."
msgstr "মেশ জুড়ে অতিথি / IoT / হটস্পট নেটওয়ার্ক প্রসারিত করুন। কমা-বিভক্ত VLAN ID তালিকা লিখুন (যেমন 10,20,30)। init লিপি প্রতিটি ID-এর জন্য bat0.<vid> কে একটি 8021q ডিভাইস হিসেবে তৈরি করে। যদি br-<vid>, br-lan<vid>, br-vlan<vid>, br-guest<vid>, বা br-iot<vid> নামের একটি ব্রিজ ইতিমধ্যে বিদ্যমান থাকে, তবে সেটি স্বয়ংক্রিয়ভাবে সেই ব্রিজে যোগ করা হয়। অন্যথায়, Network → Interfaces → Devices-এ ম্যানুয়ালি bat0.<vid> ডিভাইস যোগ করুন। ফিজিক্যাল তারযুক্ত ব্যাকহলে 802.1Q-সক্ষম সুইচ প্রয়োজন।"
msgid "The init script applies link priorities at boot and 90s later. On some hardware, mesh interfaces appear even later, leaving priority weight at 0. Enable cron fallback to reapply priority every minute. Safe (idempotent) but runs in the background. When disabled, the cron line is commented out in /etc/crontabs/root."
msgstr "init লিপি বুটে এবং 90 সেকেন্ড পরে সংযোগ অগ্রাধিকার প্রয়োগ করে। কিছু হার্ডওয়্যারে, মেশ ইন্টারফেস আরও পরে দেখা দেয়, ফলে অগ্রাধিকার ওজন 0-এ থাকে। প্রতি মিনিটে অগ্রাধিকার পুনঃপ্রয়োগ করতে cron ফলব্যাক সক্রিয় করুন। নিরাপদ (idempotent) কিন্তু ব্যাকগ্রাউন্ডে চলে। নিষ্ক্রিয় থাকলে, /etc/crontabs/root-এ cron লাইনটি মন্তব্য করা থাকে।"
msgid "Convenience selector. Mirrors 'Wired backhaul interface' below. Presets that use wired backhaul will remove this port from the LAN bridge. Pick a port NOT used by your uplink or modem. Leave empty to disable wired backhaul entirely."
msgstr "সুবিধাজনক নির্বাচক। নিচের 'তারযুক্ত ব্যাকহল ইন্টারফেস' এর সাথে মিলে যায়। তারযুক্ত ব্যাকহল ব্যবহার করে এমন প্রিসেট এই পোর্টটি LAN ব্রিজ থেকে সরিয়ে দেবে। আপনার আপলিংক বা মডেম দ্বারা ব্যবহৃত নয় এমন একটি পোর্ট বাছুন। তারযুক্ত ব্যাকহল সম্পূর্ণভাবে নিষ্ক্রিয় করতে খালি রাখুন।"
msgid "Must match on all nodes. Ignored in wired-only mode."
msgstr "সমস্ত নোডে অবশ্যই মিলতে হবে। শুধু তারযুক্ত মোডে উপেক্ষা করা হয়।"
msgid "Must match on all nodes. Only used when 'Require password' is enabled."
msgstr "সমস্ত নোডে অবশ্যই মিলতে হবে। শুধু 'পাসওয়ার্ড আবশ্যক' সক্রিয় থাকলে ব্যবহৃত হয়।"
msgid "Click a preset. A confirmation dialog will appear. Current config is backed up automatically before applying. Grouped by role: SERVER (has internet) → CLIENT (uses mesh) → NODE (relay only)."
msgstr "একটি প্রিসেট ক্লিক করুন। একটি নিশ্চিতকরণ ডায়ালগ আসবে। প্রয়োগের আগে বর্তমান কনফিগার স্বয়ংক্রিয়ভাবে ব্যাকআপ করা হয়। ভূমিকা অনুযায়ী গোষ্ঠীভুক্ত: সার্ভার (ইন্টারনেট আছে) → ক্লায়েন্ট (মেশ ব্যবহার করে) → নোড (শুধু রিলে)।"
msgid "Longer range, lower speed. Use a different SSID from 5 GHz."
msgstr "দীর্ঘ পরিসর, কম গতি। 5 GHz থেকে একটি ভিন্ন SSID ব্যবহার করুন।"
msgid "Shorter range, much faster. Use a different SSID from 2.4 GHz."
msgstr "ছোট পরিসর, অনেক দ্রুত। 2.4 GHz থেকে একটি ভিন্ন SSID ব্যবহার করুন।"
# =========================================================
# Additional descriptions
# =========================================================
msgid "Combined wireless band + wired backhaul. Wired variants use the port selected above."
msgstr "সম্মিলিত বেতার ব্যান্ড + তারযুক্ত ব্যাকহল। তারযুক্ত ভেরিয়েন্ট উপরে নির্বাচিত পোর্ট ব্যবহার করে।"
msgid "Example: 10,20,30 — Leave empty to disable"
msgstr "উদাহরণ: 10,20,30 — নিষ্ক্রিয় করতে খালি রাখুন"
+1
View File
@@ -0,0 +1 @@
zh_Hans
+95
View File
@@ -0,0 +1,95 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "easymesh"
msgstr "easymesh"
msgid "Active node"
msgstr "活动节点"
msgid "IF"
msgstr "IF"
msgid "Neighbor"
msgstr "节点邻居设备"
msgid "lastseen"
msgstr "上次连接延时"
msgid "EASY MESH"
msgstr "简单MESH"
msgid "Settings"
msgstr "设置"
msgid "General Settings"
msgstr "基本设置"
msgid "Enable"
msgstr "启用"
msgid "role"
msgstr "角色"
msgid "off"
msgstr "关闭"
msgid "host MESH"
msgstr "主MESH"
msgid "son MESH"
msgstr "子MESH"
msgid "MESH Radio device"
msgstr "MESH无线设备"
msgid "The radio device which MESH use"
msgstr "使用MESH组网的无线设备"
msgid "AUTO"
msgstr "自动"
msgid "Enable or disable EASY MESH"
msgstr "启用或禁用简单MESH"
msgid "Encryption"
msgstr "加密"
msgid "Key"
msgstr "密码"
msgid "inter-access point protocol"
msgstr "接入点内部协议"
msgid "Wireless Access Points (APs) running on different vendors can communicate with each other"
msgstr "运行在不同供应商的无线访问点(AP)可以相互交流"
msgid "Threshold for an good RSSI"
msgstr "快速漫游接入值"
msgid "Threshold for an bad RSSI"
msgstr "快速漫游踢出值"
msgid "Threshold for a good RSSI"
msgstr "优良RSSI信号阈值"
msgid "Threshold for a bad RSSI"
msgstr "不良RSSI信号阈值"
msgid "AP MODE Enable"
msgstr "启用AP模式"
msgid "Enable or disable AP MODE"
msgstr "启用或禁用AP模式"
msgid "IPv4-Address"
msgstr "IPv4 地址"
msgid "IPv4 netmask"
msgstr "IPv4 子网掩码"
msgid "IPv4 gateway"
msgstr "IPv4 网关"
msgid "Use custom DNS servers"
msgstr "使用自定义的 DNS 服务器"
@@ -0,0 +1,7 @@
ADBdåV{PTU?°ÄÚê2f–:0_%{ÁTŒ¤$aJÜvqÊ ñ²{aWwïÞî½[Xb«© ‰�i˜’Šƒµ’,å£6’f©e–f¥Ó‹©½w—²Éj2j*ûÎÞ#2 ã¿Ítf¾=ûûÞßw^73ë>£`]\²R�"CУÎaN�Õ‘ìH�Ê's¬´ÄÅIöä'íÉV~ÈÄ)ÆÔ©“Œw¦Mœ|ײ4+kKMIËÎY^r²²ÃÍãb·ˆ#Ö¬ ôãdò{ƒíÏMê åxœÅᜎbgÃ3ìž“¸ñ±¼›­výLÓÉ�)“»ÛÅ1O².‡ËÍÛÜN¦'“"�Ãic$ÑÊô ”�)²Å¬ŒÍ¬ÝÅòø°Æ³#æx‰ q;Ý#q¢däÝ"'8—K²ÝSd´º]÷L¶Ë² ¥3ÌeÓ׺짣ȊP4ô0#
y÷SÐd€LsVê\4âj½Nfù%H³‘Á†Ì@ˆ[àø'D9]⬤ãÄžê5€^ŒèvËð?èÇ(x‡iL2Žƒ'[ Ÿ`|H<Z›>üÈØcM±úwJ
Žu˜šõý‰ï¾8Xq[µuâÑóÔ_C/±€ ä�:ÍÅŒï~<øfHI6®˜õ8e‰æ6ôˆ�¬h-æÃ€uÐ2’‡@yzÀ +,&</å�¼ZŒN‡$ãí£>µ7ÌÏý}GYiÑÉ´¾hW]ýkÜ �B;žÈã<‘<7DkyÎ¥yîOc1k]Ì–p—ò<Mó<Gã#äé‘D’Ó0Í0CsGy)6KøÓ(?¾Ÿ¡kEäùTžp©*#­ÊåÆ_[ü›¿:VËŸÿäÏöwÍúúŸõ²óÄÈÆdîÎ YHM²N«©S§ÕTøÆ¾±Ò7­.ŸN«ëm÷ à!� vOñ/Jœ~6%îó ÝÇ?ع(t†ÒµM:úS`^
ô г@�ô°ÖtK¡ò˜H‘aßu¤o}FðÁü%ÌçÈÞ‡Üe 
 �Ë  Y@v]¤®Î £™"ÏHvÃ|œtÛS9¦ûgæÍ)´Ì0ç˜òË’pNJMÂxÌÌ•:dœzɦš9y–|óœ9ùe‘ž{øÈ�‘Œp!à¥K/™¯­oàJ·(cr¶2’úª'] ‹KxÖÅe$]mÏ&X›­°Dt{„B–·’+È@J¡à–d/Ɇ@ï²ÿÛåôä.r¢Ë00êræÔñ<×4Óœ™•�‘ú¿ZEí õŒšä-¿îò™™KX gȤ×ÎLˆPLÜ{·ÌÈo|ôÂõ¥®Û/ nû!%;:ýyôÇ_>ü9õ÷æ´÷¬ßßn¬¬ðæŽþåâ¼óßéºÆÔ²h|¯`zgâ”ÆgÿmˆÖ3»RjÓ&I —UÖtªïÞq,ü/@Ú[õa‰“#¶ÑÉò%rÂEThå3Æ^óÉ{7&àyt94'P4‹´·�øgú¾ÌÕZj`®xu1ý/Gíß9¸—Ò8ê¾V¥Ò®\Zñ–öÛµ¢Uy}UØ@i(G(¨Twׇ;¶ªëv*­o¨µo Õ)Ugg[fÁ;è?j÷#¤lkQëvkH©imð�ì•õá5{@¶joWmñÕ1Ršj"3ùQk·‡Z:´hÁö°#ÜPëÊPûs¡Í+{‹Q¸¼rÿjÅ ¡——ßÞé ¥r»Ò¼ææÕ¡íË¡>H{ÕN(KY½ªëŸRUöûÉûÝQ®_«Ôù‚�*¥fmðÀ¥eƒ²qu¯˜]µ~0—i¯T7[w†¶‚-ëƒ-␍êþåJÇ«]ÞmjÛ«j  ÅP¼m$~/~8°C)oÑøÁ¶Má5Íf‹%'ØQ¯T¿Ùµ©""€ðWäk5fšT_½ÒVݯæAŽéñIX©{MyÉKÿ7Õ@ëÔu»"åk<`À ­¿¤‹þÍÁ·Ö@©8+ςպ*¥²^yÑ]{ô9m[DÙÉSf¶l/Ý&d º¡JÜBå°
è¦Ñé1Ë©œ´~ÐcÜ*'çq`^çl‘âs0ã‹«S)>ó`óÅÌ‘ÔÞ ¤_päé/)&ö7.<^6Œâé@±6¼ë0ÅlÏÿc4Å“€ ¥‡âçSœOäëâbŠm¿”kø‰bÂOØ*FýEm‚9nÛ´—½T¾”|l;ûÍXŠÉg¡ÞwúÔ~Š%þ|¡¹ïºùµáò)ê�øºÿ¡¹f*ï&5\Í™Tn"õ,¹¡‘ÊÉóžð¾anÅk�n=-Í£õDM%ú
>_DågH|uazÅ» ¾x×_'©þÛ¤ÿðõBü¢
+3
View File
@@ -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
+994
View File
@@ -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
View File
@@ -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
View File
@@ -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" ]
}
}
}