🛸 Sync 2026-10-06 21:21:05
No files matched your search
@@ -0,0 +1,16 @@
|
||||
# Editor backups
|
||||
*.bak
|
||||
*.swp
|
||||
*.tmp
|
||||
|
||||
# Local test artifacts
|
||||
/tmp/
|
||||
/.config
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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"/*
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 106 KiB |
@@ -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
|
||||
@@ -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))
|
||||
@@ -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 — নিষ্ক্রিয় করতে খালি রাখুন"
|
||||
@@ -0,0 +1 @@
|
||||
zh_Hans
|
||||
@@ -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ü¢
|
||||
@@ -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
|
||||
@@ -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. ---
|
||||
@@ -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
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||