mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-03 11:33:56 +08:00
🗽 Sync 2026-10-16 05:41:31
This commit is contained in:
1 parent
8b172bbc49
commit
faf621b474
22 files changed
+1505
-635
No files matched your search
File renamed without changes.
File renamed without changes.
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=cloudflared-next
|
||||
PKG_VERSION:=2026.10.0
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=2026.10.1
|
||||
PKG_RELEASE:=4
|
||||
|
||||
# Architecture mapping for pre-built binaries
|
||||
ifeq ($(ARCH),x86_64)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
+13
-45
@@ -1,50 +1,18 @@
|
||||
#
|
||||
# Copyright (C) 2026 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-meow
|
||||
LUCI_TITLE:=meow package - LuCI interface
|
||||
LUCI_DEPENDS:=+luci-base +meow
|
||||
|
||||
# Keep in sync with meow/Makefile.
|
||||
PKG_BASE_VERSION?=0.22.0
|
||||
PKG_MAINTAINER:=meow-rs
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
# Rewritten automatically by .github/workflows/sync-upstream.yml; the LuCI
|
||||
# package always carries the same version as the core it drives.
|
||||
PKG_VERSION?=$(PKG_BASE_VERSION).202610130311
|
||||
PKG_RELEASE?=1
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
PKG_MAINTAINER:=Independent meow OpenWrt package <noreply@example.invalid>
|
||||
|
||||
define Package/luci-app-meow
|
||||
SECTION:=luci
|
||||
CATEGORY:=LuCI
|
||||
SUBMENU:=3. Applications
|
||||
TITLE:=LuCI support for meow
|
||||
URL:=https://github.com/meow-rs/meow-rs
|
||||
LICENSE:=MIT
|
||||
DEPENDS:=+luci-base +meow +curl
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/luci-app-meow/description
|
||||
LuCI interface for the meow OpenWrt package.
|
||||
|
||||
Adds Services -> meow (Overview, Panel, Configuration, Clients, Settings,
|
||||
Log). Runtime data comes from the meow REST API through authenticated LuCI
|
||||
RPC; curl is only used against the router's loopback API.
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/luci-app-meow/install
|
||||
$(INSTALL_DIR) $(1)
|
||||
cp -a ./root/. $(1)/
|
||||
$(INSTALL_DIR) $(1)/www/luci-static
|
||||
cp -a ./htdocs/luci-static/. $(1)/www/luci-static/
|
||||
find $(1)/usr/libexec -type f -exec chmod 0755 {} + 2>/dev/null || true
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,luci-app-meow))
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
Generated
+514
@@ -0,0 +1,514 @@
|
||||
{
|
||||
"name": "luci-app-meow-build",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "luci-app-meow-build",
|
||||
"devDependencies": {
|
||||
"esbuild": "0.28.2",
|
||||
"yaml": "2.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
|
||||
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
|
||||
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
|
||||
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
|
||||
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
|
||||
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
|
||||
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
|
||||
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
|
||||
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
|
||||
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
|
||||
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
|
||||
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.28.2",
|
||||
"@esbuild/android-arm": "0.28.2",
|
||||
"@esbuild/android-arm64": "0.28.2",
|
||||
"@esbuild/android-x64": "0.28.2",
|
||||
"@esbuild/darwin-arm64": "0.28.2",
|
||||
"@esbuild/darwin-x64": "0.28.2",
|
||||
"@esbuild/freebsd-arm64": "0.28.2",
|
||||
"@esbuild/freebsd-x64": "0.28.2",
|
||||
"@esbuild/linux-arm": "0.28.2",
|
||||
"@esbuild/linux-arm64": "0.28.2",
|
||||
"@esbuild/linux-ia32": "0.28.2",
|
||||
"@esbuild/linux-loong64": "0.28.2",
|
||||
"@esbuild/linux-mips64el": "0.28.2",
|
||||
"@esbuild/linux-ppc64": "0.28.2",
|
||||
"@esbuild/linux-riscv64": "0.28.2",
|
||||
"@esbuild/linux-s390x": "0.28.2",
|
||||
"@esbuild/linux-x64": "0.28.2",
|
||||
"@esbuild/netbsd-arm64": "0.28.2",
|
||||
"@esbuild/netbsd-x64": "0.28.2",
|
||||
"@esbuild/openbsd-arm64": "0.28.2",
|
||||
"@esbuild/openbsd-x64": "0.28.2",
|
||||
"@esbuild/openharmony-arm64": "0.28.2",
|
||||
"@esbuild/sunos-x64": "0.28.2",
|
||||
"@esbuild/win32-arm64": "0.28.2",
|
||||
"@esbuild/win32-ia32": "0.28.2",
|
||||
"@esbuild/win32-x64": "0.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
|
||||
"integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "luci-app-meow-build",
|
||||
"private": true,
|
||||
"scripts": { "build": "node vendor/build.cjs" },
|
||||
"devDependencies": { "esbuild": "0.28.2", "yaml": "2.9.1" }
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
# YAML document parser
|
||||
|
||||
`tools/meow_yaml.js` is a browser-only bundle of the ISC-licensed
|
||||
[yaml](https://eemeli.org/yaml/) document API. It retains comments, aliases and
|
||||
unrelated configuration while LuCI edits its managed keys. The bundled file
|
||||
and `meow_yaml.LICENSE` ship in the LuCI package; npm is not needed on OpenWrt.
|
||||
|
||||
Regenerate from pinned dependencies:
|
||||
|
||||
```sh
|
||||
cd openwrt/luci-app-meow
|
||||
npm ci
|
||||
npm run build
|
||||
```
|
||||
|
||||
The Settings map's save callback runs after LuCI parses form values and before
|
||||
UCI is saved. YAML validation/upload failures reject that save. If UCI saving
|
||||
subsequently fails, the callback's rollback restores the previous YAML unless
|
||||
another writer has changed it. The separate Configuration editor applies the
|
||||
same transformation before validating imported YAML.
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
const { buildSync } = require('esbuild');
|
||||
const { copyFileSync } = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const root = path.join(__dirname, '..');
|
||||
buildSync({
|
||||
entryPoints: [path.join(__dirname, 'yaml.js')],
|
||||
outfile: path.join(root, 'htdocs/luci-static/resources/tools/meow_yaml.js'),
|
||||
bundle: true, minify: true, platform: 'browser', format: 'iife',
|
||||
globalName: 'MeowYaml', target: 'es2020',
|
||||
banner: { js: "'use strict';\n'require baseclass';\n// Generated by npm ci && npm run build in openwrt/luci-app-meow; yaml 2.9.1 (ISC)." },
|
||||
footer: { js: 'return baseclass.extend(MeowYaml);' }
|
||||
});
|
||||
copyFileSync(path.join(root, 'node_modules/yaml/LICENSE'),
|
||||
path.join(root, 'htdocs/luci-static/resources/tools/meow_yaml.LICENSE'));
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export { parseDocument, isMap, isSeq } from 'yaml';
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-open-box
|
||||
PKG_VERSION:=0.1.333
|
||||
PKG_RELEASE:=30
|
||||
PKG_VERSION:=0.1.334
|
||||
PKG_RELEASE:=31
|
||||
|
||||
PKG_MAINTAINER:=liandu2024
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-openvpn-plus
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=3
|
||||
PKG_RELEASE:=4
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Manfred Jaider <info@masmbit.com>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/usr/sbin/nft -f
|
||||
|
||||
# Copyright (C) 2008-2026 The OpenWrt Project
|
||||
# Copyright (C) 2026 Manfred Jaider <info@masmbit.com>
|
||||
@@ -6,7 +6,7 @@
|
||||
# This is free software, licensed under the Apache License, Version 2.0.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
# luci-app-openvpn : specific openvpn flood protection protocol filters
|
||||
# luci-app-openvpn : OpenVPN Flood-Guard - security firewall rules for OpenVPN packet validation
|
||||
|
||||
# USEFUL COMMANDS FOR FIREWALL MANAGEMENT & LIVE MONITORING
|
||||
#
|
||||
@@ -19,65 +19,50 @@
|
||||
# - View entire dynamic fw4 table: nft -a list table inet fw4
|
||||
# - View main input chain with handles: nft -a list chain inet fw4 input
|
||||
# - View main forward chain with handles: nft -a list chain inet fw4 forward
|
||||
# - View your custom OpenVPN sub-chain: nft -a list chain inet fw4 ovpn_filter_instance0
|
||||
# - Quick view of custom sub-chain: nft -a list chain inet fw4 ovpn_filter_instance0 | sed -E -n 's/.*(openvpn[^"]+).*counter (packets [0-9]+ bytes [0-9]+).*(# handle [0-9]+).*/\3 -> "\1" counter \2/p'
|
||||
# - View your custom OpenVPN sub-chain: nft -a list chain inet fw4 openvpn_floodguard_instance0
|
||||
# - Quick view of custom sub-chain: nft -a list chain inet fw4 openvpn_floodguard_instance0 | sed -E -n 's/.*(openvpn[^"]+).*counter (packets [0-9]+ bytes [0-9]+).*(# handle [0-9]+).*/\3 -> "\1" counter \2/p'
|
||||
#
|
||||
# * Live Security & Attacker Tracking (The most useful commands)
|
||||
# - View active IPv4 blocked IPs & timers: nft list set inet fw4 ovpn_block_ipv4_instance0
|
||||
# - Flush/Clear a specific ban list now: nft flush set inet fw4 ovpn_block_ipv4_instance0
|
||||
# - View active IPv4 blocked IPs & timers: nft list set inet fw4 openvpn_block_ipv4_instance0
|
||||
# - Flush/Clear a specific ban list now: nft flush set inet fw4 openvpn_block_ipv4_instance0
|
||||
|
||||
fwPort=443
|
||||
fwProto="tcp"
|
||||
SUB_CHAIN="ovpn_filter_instance0"
|
||||
# Define named sets for blocked IPs with automatic timeout
|
||||
set openvpn_block_ipv4_instance0 {
|
||||
type ipv4_addr;
|
||||
flags timeout;
|
||||
};
|
||||
|
||||
# STEP 1: CLEAN UP OLD RULES AND SETS (Prevents duplicates and memory leaks)
|
||||
set openvpn_block_ipv6_instance0 {
|
||||
type ipv6_addr;
|
||||
flags timeout;
|
||||
};
|
||||
|
||||
# Find and delete the old jump rule handle inside the main input chain to unlock the sub-chain
|
||||
oldJumpHandle=$(nft -a list chain inet fw4 input | grep "jump $SUB_CHAIN" | grep -oE 'handle [0-9]+' | tail -n 1 | awk '{print $2}')
|
||||
if [ -n "$oldJumpHandle" ]; then
|
||||
nft "delete rule inet fw4 input handle $oldJumpHandle"
|
||||
fi
|
||||
|
||||
# Now the sub-chain is unlocked and can be safely deleted without resource busy errors
|
||||
nft "flush chain inet fw4 $SUB_CHAIN" 2>/dev/null
|
||||
nft "delete chain inet fw4 $SUB_CHAIN" 2>/dev/null
|
||||
nft "flush set inet fw4 ovpn_block_ipv4_instance0" 2>/dev/null
|
||||
nft "delete set inet fw4 ovpn_block_ipv4_instance0" 2>/dev/null
|
||||
nft "flush set inet fw4 ovpn_block_ipv6_instance0" 2>/dev/null
|
||||
nft "delete set inet fw4 ovpn_block_ipv6_instance0" 2>/dev/null
|
||||
|
||||
# STEP 2: BUILD FRESH NAMED SETS WITH AUTOMATIC TIMEOUT
|
||||
nft "add set inet fw4 ovpn_block_ipv4_instance0 { type ipv4_addr ; flags timeout ; }"
|
||||
nft "add set inet fw4 ovpn_block_ipv6_instance0 { type ipv6_addr ; flags timeout ; }"
|
||||
|
||||
# STEP 3: CREATE THE CLEAN SUB-CHAIN CONTAINER
|
||||
nft "add chain inet fw4 $SUB_CHAIN"
|
||||
|
||||
# STEP 4: BUILD THE CASCADE RULES (based on protocol)
|
||||
if [ "$fwProto" = "tcp" ]; then
|
||||
# Define the flood guard filter chain table
|
||||
chain openvpn_floodguard_instance0 {
|
||||
# === START OF TCP BLOCK ===
|
||||
|
||||
# TCP-S1: Instantly rejects traffic from IPv4/IPv6 addresses listed in the block sets.
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 ip saddr @ovpn_block_ipv4_instance0 log prefix \"openvpn(instance0)[firewall]{ban}: \" counter reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 ip6 saddr @ovpn_block_ipv6_instance0 log prefix \"openvpn(instance0)[firewall]{ban}: \" counter reject with tcp reset"
|
||||
tcp dport $ovpn_port ct packets < 12 ip saddr @openvpn_block_ipv4_instance0 log prefix "openvpn(instance0)[firewall]{ban}: " counter reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 ip6 saddr @openvpn_block_ipv6_instance0 log prefix "openvpn(instance0)[firewall]{ban}: " counter reject with tcp reset;
|
||||
|
||||
# TCP-S2: Blocks fragmented TCP packets before they can cause buffer overflows.
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 ip frag-off & 0x3fff != 0 log prefix \"openvpn(instance0)[firewall]{fragment}: \" counter reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 exthdr frag exists log prefix \"openvpn(instance0)[firewall]{fragment}: \" counter reject with tcp reset"
|
||||
tcp dport $ovpn_port ct packets < 12 ip frag-off & 0x3fff != 0x0 log prefix "openvpn(instance0)[firewall]{fragment}: " counter reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 exthdr frag exists log prefix "openvpn(instance0)[firewall]{fragment}: " counter reject with tcp reset;
|
||||
|
||||
# TCP-S3: Validates strict TCP headers and forces standard handshake behaviors.
|
||||
# - TCP Timestamps is required (This forces the TCP header size to be exactly 32 bytes for every data packets)
|
||||
# - TCP Timestamps is required (this forces the TCP header size to be exactly 32 bytes for every data packets)
|
||||
# - SYN packets must have maxseg, window and sack-perm option.
|
||||
# - Data packets are forbidden to have the maxseg option.
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 tcp option timestamp missing log prefix \"openvpn(instance0)[firewall]{timestamp}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 tcp flags & (syn) == syn tcp option maxseg missing log prefix \"openvpn(instance0)[firewall]{maxseg}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 tcp flags & (syn) == 0 tcp option maxseg exists log prefix \"openvpn(instance0)[firewall]{maxseg}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 tcp flags & (syn) == syn tcp option window missing log prefix \"openvpn(instance0)[firewall]{wscale}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 tcp flags & (syn) == syn tcp option sack-perm missing log prefix \"openvpn(instance0)[firewall]{sack}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 tcp option timestamp missing log prefix \"openvpn(instance0)[firewall]{timestamp}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 tcp flags & (syn) == syn tcp option maxseg missing log prefix \"openvpn(instance0)[firewall]{maxseg}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 tcp flags & (syn) == 0 tcp option maxseg exists log prefix \"openvpn(instance0)[firewall]{maxseg}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 tcp flags & (syn) == syn tcp option window missing log prefix \"openvpn(instance0)[firewall]{wscale}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 tcp flags & (syn) == syn tcp option sack-perm missing log prefix \"openvpn(instance0)[firewall]{sack}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv4 tcp option timestamp missing log prefix "openvpn(instance0)[firewall]{timestamp}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv4 tcp flags & syn == syn tcp option maxseg missing log prefix "openvpn(instance0)[firewall]{maxseg}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv4 tcp flags ! syn tcp option maxseg exists log prefix "openvpn(instance0)[firewall]{maxseg}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv4 tcp flags & syn == syn tcp option window missing log prefix "openvpn(instance0)[firewall]{wscale}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv4 tcp flags & syn == syn tcp option sack-perm missing log prefix "openvpn(instance0)[firewall]{sack}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv6 tcp option timestamp missing log prefix "openvpn(instance0)[firewall]{timestamp}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv6 tcp flags & syn == syn tcp option maxseg missing log prefix "openvpn(instance0)[firewall]{maxseg}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv6 tcp flags ! syn tcp option maxseg exists log prefix "openvpn(instance0)[firewall]{maxseg}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv6 tcp flags & syn == syn tcp option window missing log prefix "openvpn(instance0)[firewall]{wscale}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv6 tcp flags & syn == syn tcp option sack-perm missing log prefix "openvpn(instance0)[firewall]{sack}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
|
||||
# TCP-S4: Catches smart bots that delay data transmission to bypass early scanners.
|
||||
# * CRITICAL MINIMUM DATA SIZES (Worst-Case Real OpenVPN Client):
|
||||
@@ -90,69 +75,58 @@ if [ "$fwProto" = "tcp" ]; then
|
||||
# - IPv4 limit is set to < 700 Bytes. Bots with no data are smaller and get killed.
|
||||
# - IPv6 limit is set to < 900 Bytes. Bots with no data are smaller and get killed.
|
||||
# - Real worst-case OpenVPN users always stay safely above this byte radar.
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 ct packets > 9 meta nfproto ipv4 ct bytes < 700 log prefix \"openvpn(instance0)[firewall]{stealth}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 ct packets > 9 meta nfproto ipv6 ct bytes < 900 log prefix \"openvpn(instance0)[firewall]{stealth}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
tcp dport $ovpn_port ct packets < 12 ct packets > 9 meta nfproto ipv4 ct bytes < 700 log prefix "openvpn(instance0)[firewall]{stealth}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 ct packets > 9 meta nfproto ipv6 ct bytes < 900 log prefix "openvpn(instance0)[firewall]{stealth}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
|
||||
# TCP-S5: Blocks payload data hidden inside the very first SYN packet (80 bytes for IPv4 packets and max 100 bytes for IPv6)
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 tcp flags & (syn) == syn tcp option maxseg exists meta length > 80 log prefix \"openvpn(instance0)[firewall]{fastopen}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 tcp flags & (syn) == syn tcp option maxseg exists meta length > 100 log prefix \"openvpn(instance0)[firewall]{fastopen}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv4 tcp flags & syn == syn tcp option maxseg exists meta length > 80 log prefix "openvpn(instance0)[firewall]{fastopen}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv6 tcp flags & syn == syn tcp option maxseg exists meta length > 100 log prefix "openvpn(instance0)[firewall]{fastopen}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
|
||||
# TCP-S6: Deep Packet Inspection for bad protocol lengths and fake small sizes.
|
||||
# - Starts only when a data packet is bigger than 53 bytes (IPv4) or 73 bytes (IPv6).
|
||||
# - Reads the 16-bit OpenVPN packet length field at the exact bit offset pointer 256.
|
||||
# - Catches bad opcodes, web text (like GET/POST), and fake sizes (payload < 155, payload > 1499).
|
||||
# - Real users with tls-crypt-v2 are safe because encrypted traffic never matches this bad matrix.
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 tcp flags & (syn) == 0 meta length > 53 @th,256,16 < 0x009b log prefix \"openvpn(instance0)[firewall]{payload}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv4 tcp flags & (syn) == 0 meta length > 53 @th,256,16 > 0x05db log prefix \"openvpn(instance0)[firewall]{payload}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 tcp flags & (syn) == 0 meta length > 73 @th,256,16 < 0x009b log prefix \"openvpn(instance0)[firewall]{payload}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort ct packets < 12 meta nfproto ipv6 tcp flags & (syn) == 0 meta length > 73 @th,256,16 > 0x05db log prefix \"openvpn(instance0)[firewall]{payload}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset"
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv4 tcp flags ! syn meta length > 53 @th,256,16 < 0x9b log prefix "openvpn(instance0)[firewall]{payload}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv4 tcp flags ! syn meta length > 53 @th,256,16 > 0x5db log prefix "openvpn(instance0)[firewall]{payload}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv6 tcp flags ! syn meta length > 73 @th,256,16 < 0x9b log prefix "openvpn(instance0)[firewall]{payload}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
tcp dport $ovpn_port ct packets < 12 meta nfproto ipv6 tcp flags ! syn meta length > 73 @th,256,16 > 0x5db log prefix "openvpn(instance0)[firewall]{payload}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } reject with tcp reset;
|
||||
|
||||
# TCP-S7: Packets that survived all previous checks are genuine.
|
||||
nft "add rule inet fw4 $SUB_CHAIN tcp dport $fwPort accept"
|
||||
tcp dport $ovpn_port accept;
|
||||
|
||||
else
|
||||
# === END OF TCP BLOCK ===
|
||||
# === START OF UDP BLOCK ===
|
||||
|
||||
# UDP-S1: Instantly drops traffic from IPv4/IPv6 addresses listed in the block sets.
|
||||
# (Bleibt bei ct state new, damit bestehender established Tunnel-Traffic niemals blockiert wird!)
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new ip saddr @ovpn_block_ipv4_instance0 log prefix \"openvpn(instance0)[firewall]{ban}: \" counter drop"
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new ip6 saddr @ovpn_block_ipv6_instance0 log prefix \"openvpn(instance0)[firewall]{ban}: \" counter drop"
|
||||
udp dport $ovpn_port ct state new ip saddr @openvpn_block_ipv4_instance0 log prefix "openvpn(instance0)[firewall]{ban}: " counter drop;
|
||||
udp dport $ovpn_port ct state new ip6 saddr @openvpn_block_ipv6_instance0 log prefix "openvpn(instance0)[firewall]{ban}: " counter drop;
|
||||
|
||||
# UDP-S2: Blocks fragmented UDP packets before they can cause buffer overflows.
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv4 ip frag-off & 0x3fff != 0 log prefix \"openvpn(instance0)[firewall]{fragment}: \" counter drop"
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv6 exthdr frag exists log prefix \"openvpn(instance0)[firewall]{fragment}: \" counter drop"
|
||||
udp dport $ovpn_port ct state new ip frag-off & 0x3fff != 0x0 log prefix "openvpn(instance0)[firewall]{fragment}: " counter drop;
|
||||
udp dport $ovpn_port ct state new exthdr frag exists log prefix "openvpn(instance0)[firewall]{fragment}: " counter drop;
|
||||
|
||||
# UDP-S3: Smallest valid OpenVPN packet is 48 bytes. Anything smaller is dropped.
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new udp length < 48 log prefix \"openvpn(instance0)[firewall]{short}: \" counter drop"
|
||||
udp dport $ovpn_port ct state new udp length < 48 log prefix "openvpn(instance0)[firewall]{short}: " counter drop;
|
||||
|
||||
# UDP-S4: Drops and bans new IPv4 bots that scan with a zero checksum to save time.
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv4 udp checksum 0 log prefix \"openvpn(instance0)[firewall]{chksum}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } drop"
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv4 udp checksum 0 log prefix "openvpn(instance0)[firewall]{chksum}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } drop;
|
||||
|
||||
# UDP-S5: Whitelist valid initial OpenVPN opcodes P_CONTROL_HARD_RESET_CLIENT_V1,2,3 (skipping the 64-bit UDP header)
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv4 @th,64,8 & 0xf8 == 0x08 udp length > 250 accept"
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv4 @th,64,8 & 0xf8 == 0x38 udp length > 250 accept"
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv4 @th,64,8 & 0xf8 == 0x50 udp length > 250 accept"
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv6 @th,64,8 & 0xf8 == 0x08 udp length > 250 accept"
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv6 @th,64,8 & 0xf8 == 0x38 udp length > 250 accept"
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv6 @th,64,8 & 0xf8 == 0x50 udp length > 250 accept"
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv4 @th,64,8 & 0xf8 == 0x8 udp length > 250 accept;
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv4 @th,64,8 & 0xf8 == 0x38 udp length > 250 accept;
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv4 @th,64,8 & 0xf8 == 0x50 udp length > 250 accept;
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv6 @th,64,8 & 0xf8 == 0x8 udp length > 250 accept;
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv6 @th,64,8 & 0xf8 == 0x38 udp length > 250 accept;
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv6 @th,64,8 & 0xf8 == 0x50 udp length > 250 accept;
|
||||
|
||||
# UDP-S6: Instantly ban and drop any other packet that tries to start a new connection
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv4 log prefix \"openvpn(instance0)[firewall]{opcode}: \" counter update @ovpn_block_ipv4_instance0 { ip saddr timeout 5m } drop"
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort ct state new meta nfproto ipv6 log prefix \"openvpn(instance0)[firewall]{opcode}: \" counter update @ovpn_block_ipv6_instance0 { ip6 saddr timeout 5m } drop"
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv4 log prefix "openvpn(instance0)[firewall]{opcode}: " counter update @openvpn_block_ipv4_instance0 { ip saddr timeout 5m } drop;
|
||||
udp dport $ovpn_port ct state new meta nfproto ipv6 log prefix "openvpn(instance0)[firewall]{opcode}: " counter update @openvpn_block_ipv6_instance0 { ip6 saddr timeout 5m } drop;
|
||||
|
||||
# UDP-S7: Packets that survived all previous checks are genuine.
|
||||
nft "add rule inet fw4 $SUB_CHAIN udp dport $fwPort accept"
|
||||
|
||||
fi
|
||||
|
||||
# STEP 4: LINK THE SUB-CHAIN TO THE MAIN INPUT (single jump rule)
|
||||
|
||||
# Find the starting anchor in the main input chain (Loopback handle)
|
||||
mainAnchorHandle=$(nft -a list chain inet fw4 input | grep -E 'iif ["'\'']?lo["'\'']? accept' | grep -oE 'handle [0-9]+' | tail -n 1 | awk '{print $2}')
|
||||
|
||||
if [ -z "$mainAnchorHandle" ]; then
|
||||
mainAnchorHandle=35
|
||||
fi
|
||||
|
||||
# Inject the dynamic jump trigger right below the loopback anchor rule
|
||||
nft "add rule inet fw4 input handle $mainAnchorHandle $fwProto dport $fwPort jump $SUB_CHAIN"
|
||||
udp dport $ovpn_port accept;
|
||||
|
||||
# === END OF UDP BLOCK ===
|
||||
};
|
||||
;
|
||||
@@ -442,9 +442,10 @@ firewallservice () {
|
||||
fi
|
||||
|
||||
# 2. Call the standard OpenWrt system firewall controller safely
|
||||
if [ "$action_cmd" = "stop" ] || [ "$action_cmd" = "start" ] || [ "$action_cmd" = "restart" ]; then
|
||||
if [ "$action_cmd" = "stop" ] || [ "$action_cmd" = "start" ] || [ "$action_cmd" = "restart" ] || [ "$action_cmd" = "reload" ]; then
|
||||
/etc/init.d/firewall "$action_cmd" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1371,6 +1372,7 @@ cleanup() {
|
||||
if ! uci -q get openvpn."$instname" >/dev/null; then
|
||||
rm -f "$conf_file"
|
||||
rm -f "$conf_dir/${instname}.nft"
|
||||
rm -f "$conf_dir/${instname}.jmp.nft"
|
||||
rm -f "$conf_dir/${instname}.s2s.nft"
|
||||
rm -f "$key_dir/ca_${instname}.crt"
|
||||
rm -f "$key_dir/dh_${instname}.pem"
|
||||
@@ -1909,15 +1911,15 @@ ovpnservice_async() {
|
||||
rm -f "$pid_file"
|
||||
|
||||
# 2. Start the target command via worker wrapper to handle absolute lifecycle traps
|
||||
start-stop-daemon -S -b -m -p "$pid_file" -x "$0" -- ovpnservice_worker "$pid_file" "$action_cmd" "$inst"
|
||||
start-stop-daemon -S -b -m -p "$pid_file" -x "$0" -- ovpnservice_worker "$action_cmd" "$inst"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
ovpnservice_worker() {
|
||||
shift # Remove "ovpnservice_worker" from the arguments list
|
||||
local pid_file="$1"
|
||||
local cmd="$2"
|
||||
local inst="$3"
|
||||
local pid_file="/var/run/luci-app-openvpn-plus-ovpn-service.pid"
|
||||
local cmd="$1"
|
||||
local inst="$2"
|
||||
trap 'rm -f "$pid_file"' EXIT INT TERM
|
||||
if [ "$cmd" = "start" ]; then
|
||||
ovpnstart "ovpnstart" "$inst" 3
|
||||
@@ -1941,14 +1943,14 @@ firewallservice_async() {
|
||||
|
||||
# 2. Start the worker directly via the main script frame context
|
||||
# Inside the worker, we use a trap to cleanly remove the pid_file upon exit
|
||||
start-stop-daemon -S -b -m -p "$pid_file" -x "$0" -- firewallservice_worker "$pid_file" "$action_cmd"
|
||||
start-stop-daemon -S -b -m -p "$pid_file" -x "$0" -- firewallservice_worker "$action_cmd"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
firewallservice_worker() {
|
||||
shift # Remove "firewallservice_worker" from the arguments list
|
||||
local pid_file="$1"
|
||||
local cmd="$2"
|
||||
local cmd="$1"
|
||||
local pid_file="/var/run/luci-app-openvpn-plus-firewall-service.pid"
|
||||
trap 'rm -f "$pid_file"' EXIT INT TERM
|
||||
firewallservice "firewallservice" "$cmd" 3
|
||||
rm -f "$pid_file"
|
||||
@@ -1957,17 +1959,18 @@ firewallservice_worker() {
|
||||
|
||||
initkeys_async() {
|
||||
shift # Remove "initkeys_async" from the arguments list
|
||||
local pid_file="/var/run/openvpn.initkeys.pid"
|
||||
# 1. Check if the key generator is running
|
||||
if start-stop-daemon -K -q -p "/var/run/openvpn.initkeys.pid" -t 2>/dev/null; then
|
||||
if start-stop-daemon -K -q -p "$pid_file" -t 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
# 2. Start the background worker and create the PID file automatically
|
||||
start-stop-daemon -S -b -m -p "/var/run/openvpn.initkeys.pid" -x "$0" -- "initkeys_worker" "/var/run/openvpn.initkeys.pid"
|
||||
start-stop-daemon -S -b -m -p "$pid_file" -x "$0" -- "initkeys_worker"
|
||||
return 0
|
||||
}
|
||||
initkeys_worker() {
|
||||
shift # Remove "initkeys_worker" from the arguments list
|
||||
local pid_file="$1"
|
||||
local pid_file="/var/run/openvpn.initkeys.pid"
|
||||
# 3. Secure the PID file lifecycle instantly with a trap framework
|
||||
trap 'rm -f "$pid_file"' EXIT INT TERM
|
||||
# 4. Run the key creation function quietly in the background
|
||||
@@ -1980,14 +1983,23 @@ keygen_async() {
|
||||
shift # Remove "keygen" from the arguments list
|
||||
local type="$1"
|
||||
local instance="$2"
|
||||
if [ -z "$instance" ] || ! echo "$instance" | grep -Eq '^[A-Za-z0-9_-]+$'; then
|
||||
echo "ERROR: Invalid instance name." >&2
|
||||
return 1
|
||||
fi
|
||||
local pid_file="/var/run/openvpn.keygen-${instance}.pid"
|
||||
# 1. Start the background keygen worker and create the PID file automatically
|
||||
start-stop-daemon -S -b -m -p "/var/run/openvpn.keygen-${instance}.pid" -x "$0" -- "keygen_worker" "/var/run/openvpn.keygen-${instance}.pid" "$@"
|
||||
start-stop-daemon -S -b -m -p "$pid_file" -x "$0" -- "keygen_worker" "$instance" "$@"
|
||||
return 0
|
||||
}
|
||||
keygen_worker() {
|
||||
shift # Remove "keygen_worker" from the arguments list
|
||||
local pid_file="$1"
|
||||
shift # Remove "pid_file" from the arguments list
|
||||
local instance="$1"
|
||||
if [ -z "$instance" ] || ! echo "$instance" | grep -Eq '^[A-Za-z0-9_-]+$'; then
|
||||
return 1
|
||||
fi
|
||||
local pid_file="/var/run/openvpn.keygen-${instance}.pid"
|
||||
shift # Remove "instance" from the arguments list
|
||||
# 3. Secure the dynamic PID node tracking with a strict trap guard
|
||||
trap 'rm -f "$pid_file"' EXIT INT TERM
|
||||
# 4. Run the certificate engine pipeline with corrected original parameters ($@: type="$1", instance="$2")
|
||||
|
||||
@@ -189,6 +189,82 @@ const sanitizeInputText = function (value) {
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Cryptographic Asset Identity Enum
|
||||
*/
|
||||
const CRYPTO_TYPE = Object.freeze({
|
||||
CA_TRUE: 'CA_TRUE',
|
||||
STANDARD_CERTIFICATE: 'STANDARD_CERTIFICATE',
|
||||
PUBLIC_KEY: 'PUBLIC_KEY',
|
||||
PRIVATE_KEY: 'PRIVATE-KEY',
|
||||
TLS2_SERVER_KEY: 'TLS2_SERVER_KEY',
|
||||
TLS2_CLIENT_KEY: 'TLS2_CLIENT_KEY',
|
||||
DH_PARAMETERS: 'DH_PARAMETERS',
|
||||
INVALID_KEY: 'INVALID_KEY',
|
||||
});
|
||||
|
||||
/**
|
||||
* Reads, validates, and categorizes cryptographic files using a secure Enum mapper
|
||||
*/
|
||||
const getKeyMeta = async function (keyfile, luci_app_openvpn_plus_callback) {
|
||||
const result = {
|
||||
valid: false,
|
||||
type: CRYPTO_TYPE.INVALID_KEY,
|
||||
metadata: { type: _('Unknown Structure'), subject: '', expires: '', algo: '', curve: '' },
|
||||
raw: ''
|
||||
};
|
||||
|
||||
// 1. Guard check: Ensure keyfile is given and callback is a callable execution pipe
|
||||
if (!keyfile || typeof luci_app_openvpn_plus_callback !== 'function') {
|
||||
console.error("getKeyMeta execution aborted: keyfile is missing or callback parameter is not a valid function.");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 2. Safely trigger the verified callback mechanism
|
||||
const res = await luci_app_openvpn_plus_callback([CFG.LIBEXEC.keymeta, keyfile]);
|
||||
|
||||
// Early escape if the backend fails or outputs errors
|
||||
if (!res || res.length === 0 || res.startsWith('ERROR') || res.toUpperCase().indexOf('ERROR') !== -1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.raw = res;
|
||||
result.valid = true;
|
||||
const rawUpper = res.toUpperCase();
|
||||
|
||||
// DETERMINISTIC ENUM MAPPER (Maps backend strings directly to your explicit Enum keys)
|
||||
if (rawUpper.indexOf('AUTHORITY') !== -1 || rawUpper.indexOf('CA:TRUE') !== -1) {
|
||||
result.type = CRYPTO_TYPE.CA_TRUE;
|
||||
} else if (rawUpper.indexOf('STANDARD CERTIFICATE') !== -1) {
|
||||
result.type = CRYPTO_TYPE.STANDARD_CERTIFICATE;
|
||||
} else if (rawUpper.indexOf('PUBLIC-KEY') !== -1) {
|
||||
result.type = CRYPTO_TYPE.PUBLIC_KEY;
|
||||
} else if (rawUpper.indexOf('PRIVATE-KEY') !== -1) {
|
||||
result.type = CRYPTO_TYPE.PRIVATE_KEY;
|
||||
} else if (rawUpper.indexOf('TLSV2-SERVER-KEY') !== -1) {
|
||||
result.type = CRYPTO_TYPE.TLS2_SERVER_KEY;
|
||||
} else if (rawUpper.indexOf('TLSV2-CLIENT-KEY') !== -1) {
|
||||
result.type = CRYPTO_TYPE.TLS2_CLIENT_KEY;
|
||||
} else if (rawUpper.indexOf('DH PARAMETERS') !== -1) {
|
||||
result.type = CRYPTO_TYPE.DH_PARAMETERS;
|
||||
}
|
||||
|
||||
// Parse human-readable lines for UI panels
|
||||
const lines = res.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line === '') continue;
|
||||
|
||||
if (line.startsWith('TYPE:')) result.metadata.type = line.replace('TYPE:', '').trim();
|
||||
else if (line.startsWith('Subject:')) result.metadata.subject = line.replace('Subject:', '').trim();
|
||||
else if (line.startsWith('Not After :') || line.startsWith('Not After:')) result.metadata.expires = line.replace(/Not After\s*:\s*/, '').trim();
|
||||
else if (line.startsWith('Public-Key:') || line.startsWith('Private-Key:')) result.metadata.algo = line.trim();
|
||||
else if (line.startsWith('ASN1 OID:') || line.startsWith('NIST Curve:') || line.startsWith('Curve:')) result.metadata.curve = line.trim();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Show simple key editor that verifies and displays key metadata.
|
||||
*/
|
||||
@@ -208,70 +284,86 @@ const openKeyEditorModal = function (filename, instance_id, displayId, role, app
|
||||
});
|
||||
|
||||
// Update the quick info box content
|
||||
const quickInfoBoxUpdate = function (rawMeta) {
|
||||
const quickInfoBoxUpdate = function (keyInfo) {
|
||||
let keyTypeInfo = TXT.KEY.unknown_asset;
|
||||
let expirationInfo = TXT.KEY.not_applicable;
|
||||
let commonNameInfo = '';
|
||||
let isInvalid = false;
|
||||
const cleanMeta = String(rawMeta || '').trim();
|
||||
|
||||
// Parse expiration date safely
|
||||
const expiryMatch = cleanMeta.match(/Not After\s*:\s*([^\n\r]+)/i);
|
||||
if (expiryMatch && expiryMatch[1]) {
|
||||
expirationInfo = expiryMatch[1].trim();
|
||||
}
|
||||
// 1. Map directly from our clean metadata object if the key is valid
|
||||
if (keyInfo && keyInfo.valid === true) {
|
||||
const meta = keyInfo.metadata;
|
||||
const rawUpper = String(keyInfo.raw || '').toUpperCase();
|
||||
|
||||
// Extract Subject Common Name perfectly supporting optional spaces
|
||||
const cnMatch = cleanMeta.match(/Subject\s*:\s*CN\s*=\s*([^,\n\r]+)/i);
|
||||
if (cnMatch && cnMatch[1]) {
|
||||
commonNameInfo = cnMatch[1].trim();
|
||||
}
|
||||
if (meta.expires) {
|
||||
expirationInfo = meta.expires;
|
||||
}
|
||||
|
||||
// Identify key type and strength using clean text patterns
|
||||
if ((cleanMeta.indexOf('Public-Key: (4096 bit)') !== -1) || (cleanMeta.indexOf('Private-Key: (4096 bit)') !== -1)) {
|
||||
keyTypeInfo = 'RSA 4096 Bit';
|
||||
} else if (cleanMeta.indexOf('Public-Key: (256 bit)') !== -1 || cleanMeta.indexOf('prime256v1') !== -1) {
|
||||
keyTypeInfo = 'ECC Prime256v1';
|
||||
} else if ((cleanMeta.indexOf('Public-Key: (2048 bit)') !== -1) || (cleanMeta.indexOf('Private-Key: (2048 bit)') !== -1 || cleanMeta.indexOf('PRIVATE-KEY') !== -1)) {
|
||||
keyTypeInfo = 'RSA 2048 Bit';
|
||||
} else if (cleanMeta.indexOf('DH PARAMETERS') !== -1 || cleanMeta.indexOf('bit') !== -1) {
|
||||
const dhBits = cleanMeta ? cleanMeta.match(/([0-9]+)\s*bit/i) : null;
|
||||
const dbits_info = dhBits ? '(' + dhBits[1] + ' Bit)' : '';
|
||||
keyTypeInfo = 'Diffie-Hellman ' + dbits_info;
|
||||
} else if (cleanMeta.indexOf('TLSv2-Server-Key') !== -1) {
|
||||
keyTypeInfo = 'TLS Crypt v2';
|
||||
} else if (cleanMeta.indexOf('TLSv2-Client-Key') !== -1) {
|
||||
keyTypeInfo = 'TLS Crypt v2';
|
||||
} else if (cleanMeta.indexOf('Unsupported') !== -1) {
|
||||
keyTypeInfo = '<span style="color:var(--success-color-high, #00ac59); font-weight:bold;">' + ICON.WARNING + TXT.WARNING.key_decrepted + '</span>';
|
||||
isInvalid = true;
|
||||
} else if (cleanMeta.indexOf('ERROR') !== -1) {
|
||||
// Extract Subject Common Name perfectly supporting optional spaces
|
||||
const cnMatch = meta.subject.match(/CN\s*=\s*([^,\n\r]+)/i);
|
||||
if (cnMatch && cnMatch[1]) {
|
||||
commonNameInfo = cnMatch[1].trim();
|
||||
}
|
||||
|
||||
// Identify key type and strength using clean Enum keys
|
||||
switch (keyInfo.type) {
|
||||
case CRYPTO_TYPE.CA_TRUE:
|
||||
case CRYPTO_TYPE.STANDARD_CERTIFICATE:
|
||||
case CRYPTO_TYPE.PUBLIC_KEY:
|
||||
case CRYPTO_TYPE.PRIVATE_KEY:
|
||||
if (rawUpper.indexOf('4096 BIT') !== -1) {
|
||||
keyTypeInfo = 'RSA 4096 Bit';
|
||||
} else if (rawUpper.indexOf('256 BIT') !== -1 || rawUpper.indexOf('PRIME256V1') !== -1) {
|
||||
keyTypeInfo = 'ECC Prime256v1';
|
||||
} else {
|
||||
keyTypeInfo = 'RSA 2048 Bit';
|
||||
}
|
||||
break;
|
||||
case CRYPTO_TYPE.DH_PARAMETERS: {
|
||||
const dhBits = rawUpper.match(/([0-9]+)\s*BIT/i);
|
||||
const dbits_info = dhBits ? '(' + dhBits[1] + ' Bit)' : '';
|
||||
keyTypeInfo = 'Diffie-Hellman ' + dbits_info;
|
||||
break;
|
||||
}
|
||||
case CRYPTO_TYPE.TLS2_SERVER_KEY:
|
||||
case CRYPTO_TYPE.TLS2_CLIENT_KEY:
|
||||
keyTypeInfo = 'TLS Crypt v2';
|
||||
break;
|
||||
default:
|
||||
if (rawUpper.indexOf('UNSUPPORTED') !== -1) {
|
||||
keyTypeInfo = '<span style="color:var(--success-color-high, #00ac59); font-weight:bold;">' + ICON.WARNING + TXT.WARNING.key_decrepted + '</span>';
|
||||
isInvalid = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Add dynamic Role identity decorations to the type string
|
||||
if (!isInvalid) {
|
||||
const strRole = (role === OPENVPN.ROLE.SERVER) ? TXT.INFO.server : TXT.INFO.client;
|
||||
|
||||
if (keyInfo.type === CRYPTO_TYPE.CA_TRUE) {
|
||||
if (rawUpper.indexOf('SECRET-KEY-CA') !== -1) {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + TXT.KEY.master_ca + ' ' + TXT.KEY.certificate + ' ' + TXT.KEY.secret_ca + ']</span>';
|
||||
} else {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + TXT.KEY.master_ca + ' ' + TXT.KEY.certificate + ']</span>';
|
||||
}
|
||||
} else if (keyInfo.type === CRYPTO_TYPE.STANDARD_CERTIFICATE) {
|
||||
keyTypeInfo += ' - <span style="color:var(--primary-color-high, #1976d2); font-weight:bold;">[' + strRole + ' ' + TXT.KEY.certificate + ']</span>';
|
||||
} else if (keyInfo.type === CRYPTO_TYPE.PRIVATE_KEY) {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + strRole + ' ' + TXT.KEY.private_key + ']</span>';
|
||||
} else if (keyInfo.type === CRYPTO_TYPE.TLS2_SERVER_KEY) {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + TXT.KEY.tls_key_server + ']</span>';
|
||||
} else if (keyInfo.type === CRYPTO_TYPE.TLS2_CLIENT_KEY) {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + TXT.KEY.tls_key_client + ']</span>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback state if getKeyMeta marked the asset file as invalid
|
||||
keyTypeInfo = '<span style="color:var(--success-color-high, #00ac59); font-weight:bold;">' + ICON.ERROR + TXT.ERROR.format_corruption + '</span>';
|
||||
isInvalid = true;
|
||||
}
|
||||
|
||||
// Role identities to make the type text easy to read for the admin
|
||||
if (!isInvalid) {
|
||||
const strRole = (role === OPENVPN.ROLE.SERVER) ? TXT.INFO.server : TXT.INFO.client;
|
||||
|
||||
if (cleanMeta.indexOf('AUTHORITY') !== -1 || cleanMeta.indexOf('CA:TRUE') !== -1) {
|
||||
if (cleanMeta.indexOf('SECRET-KEY-CA') !== -1) {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + TXT.KEY.master_ca + ' ' + TXT.KEY.certificate + ' ' + TXT.KEY.secret_ca + ']</span>';
|
||||
} else {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + TXT.KEY.master_ca + ' ' + TXT.KEY.certificate + ']</span>';
|
||||
}
|
||||
} else if (cleanMeta.indexOf('STANDARD CERTIFICATE') !== -1) {
|
||||
keyTypeInfo += ' - <span style="color:var(--primary-color-high, #1976d2); font-weight:bold;">[' + strRole + ' ' + TXT.KEY.certificate + ']</span>';
|
||||
} else if (cleanMeta.indexOf('Private-Key') !== -1 || cleanMeta.indexOf('PRIVATE-KEY') !== -1) {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + strRole + ' ' + TXT.KEY.private_key + ']</span>';
|
||||
} else if (cleanMeta.indexOf('TLSv2-Server-Key') !== -1) {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + TXT.KEY.tls_key_server + ']</span>';
|
||||
} else if (cleanMeta.indexOf('TLSv2-Client-Key') !== -1) {
|
||||
keyTypeInfo += ' - <span style="color:var(--success-color-high, #00ac59); font-weight:bold;">[' + TXT.KEY.tls_key_client + ']</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Apply styles and render HTML nodes dynamically
|
||||
// 3. Apply styles and render HTML nodes dynamically
|
||||
if (isInvalid) {
|
||||
quickInfoBox.style.borderLeftColor = 'var(--success-color-high, #00ac59)';
|
||||
quickInfoBox.innerHTML = keyTypeInfo;
|
||||
@@ -288,17 +380,22 @@ const openKeyEditorModal = function (filename, instance_id, displayId, role, app
|
||||
quickInfoBox.style.display = 'block';
|
||||
};
|
||||
|
||||
// Read file and parse metadata
|
||||
secureFileCallbacks.secureReadFile(absolutePath).then(function (content) {
|
||||
// Read file and parse metadata asynchronously
|
||||
secureFileCallbacks.secureReadFile(absolutePath).then(async function (content) {
|
||||
// 1. Fill the textarea workspace panel instantly
|
||||
modalKeyTextArea.value = content ? content.trim() + '\n' : '--- EMPTY OR BLANK KEY FILE ---';
|
||||
|
||||
if (!content || content.trim() === '') return;
|
||||
// 2. Early escape if the file is completely empty to prevent unneeded backend queries
|
||||
if (!content || content.trim() === '') {
|
||||
quickInfoBoxUpdate({ valid: false });
|
||||
return;
|
||||
}
|
||||
|
||||
secureFileCallbacks.luci_app_openvpn_plus([CFG.LIBEXEC.keymeta, filename]).then(function (res) {
|
||||
if (res.length > 0) {
|
||||
quickInfoBoxUpdate(res);
|
||||
}
|
||||
});
|
||||
// 3. FIX: 'async function' allows using 'await' here flawlessly!
|
||||
const keyInfo = await getKeyMeta(filename, secureFileCallbacks.luci_app_openvpn_plus);
|
||||
|
||||
// 4. Update the visual UI panel metrics
|
||||
quickInfoBoxUpdate(keyInfo);
|
||||
});
|
||||
|
||||
const modalSaveApplyBtn = E('button', {
|
||||
@@ -306,45 +403,45 @@ const openKeyEditorModal = function (filename, instance_id, displayId, role, app
|
||||
'style': 'margin-right:10px;'
|
||||
}, [TXT.BTN.save_apply]);
|
||||
|
||||
modalSaveApplyBtn.addEventListener('click', function () {
|
||||
modalSaveApplyBtn.addEventListener('click', async function () {
|
||||
modalSaveApplyBtn.disabled = true;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.saving;
|
||||
|
||||
// Standardize all copy-pasted line endings instantly (Supports UNIX \n, Windows \r\n, and Mac \r)
|
||||
const sanitizedKeyContent = sanitizeInputText(modalKeyTextArea.value) + '\n';
|
||||
|
||||
// Write file and re-validate key metadata (Using our clean sanitized text string)
|
||||
secureFileCallbacks.secureWriteFile(absolutePath, sanitizedKeyContent)
|
||||
.then(function () {
|
||||
return secureFileCallbacks.luci_app_openvpn_plus([CFG.LIBEXEC.keymeta, filename]);
|
||||
})
|
||||
.then(function (keymeta) {
|
||||
try {
|
||||
// 1. Write the clean sanitized text string to disk
|
||||
await secureFileCallbacks.secureWriteFile(absolutePath, sanitizedKeyContent);
|
||||
|
||||
if (keymeta.indexOf('ERROR') !== -1) {
|
||||
quickInfoBoxUpdate(keymeta);
|
||||
modalSaveApplyBtn.disabled = false;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.save_apply;
|
||||
return Promise.reject(new Error('Editor validation failed'));
|
||||
}
|
||||
// 2. Re-validate key metadata using our smart callback engine
|
||||
const keyInfo = await getKeyMeta(filename, secureFileCallbacks.luci_app_openvpn_plus);
|
||||
|
||||
hasSaved = true;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.saved;
|
||||
// 3. Evaluate the cryptographic validation status
|
||||
if (keyInfo.valid === false) {
|
||||
quickInfoBoxUpdate(keyInfo); // Renders the error state inside the UI box
|
||||
modalSaveApplyBtn.disabled = false;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.save_apply;
|
||||
return; // Stop execution instantly
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
modalSaveApplyBtn.disabled = false;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.save_apply;
|
||||
if (keymeta !== '') {
|
||||
quickInfoBoxUpdate(keymeta);
|
||||
}
|
||||
}, 1200);
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (err.message !== 'Editor validation failed') {
|
||||
console.error('Editor save chain caught unhandled error context:', err);
|
||||
modalSaveApplyBtn.disabled = false;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.save_apply;
|
||||
}
|
||||
});
|
||||
// 4. Success path: Key is good!
|
||||
hasSaved = true;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.saved;
|
||||
|
||||
// Quick temporary visual feedback loop for the administrator
|
||||
setTimeout(function () {
|
||||
modalSaveApplyBtn.disabled = false;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.save_apply;
|
||||
quickInfoBoxUpdate(keyInfo);
|
||||
}, 1200);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Editor save chain caught unhandled error context:', err);
|
||||
modalSaveApplyBtn.disabled = false;
|
||||
modalSaveApplyBtn.textContent = TXT.BTN.save_apply;
|
||||
quickInfoBoxUpdate({ valid: false });
|
||||
}
|
||||
});
|
||||
|
||||
const modalDownloadKeyBtn = E('button', {
|
||||
@@ -1244,6 +1341,8 @@ const openKeyGenModal = function (instance_id, displayId, role, appData, uciSave
|
||||
return L.Class.extend(
|
||||
{
|
||||
KEYTYPE: KEYTYPE,
|
||||
CRYPTO_TYPE: CRYPTO_TYPE,
|
||||
getKeyMeta: getKeyMeta,
|
||||
executeAsynchronousKeyGen: executeAsynchronousKeyGen,
|
||||
openKeyEditorModal: openKeyEditorModal,
|
||||
openAutomatedPostKeyGenModal: openAutomatedPostKeyGenModal,
|
||||
|
||||
@@ -82,8 +82,8 @@ const OPENVPN = Object.freeze({
|
||||
CLIENT: 'client'
|
||||
}),
|
||||
PROTO: Object.freeze({
|
||||
TCP: 'tcp',
|
||||
UDP: 'udp'
|
||||
tcp: 'tcp',
|
||||
udp: 'udp'
|
||||
}),
|
||||
PORT: Object.freeze({
|
||||
s1194: '1194'
|
||||
@@ -173,9 +173,15 @@ const nativeFileExecRpc = L.rpc.declare({
|
||||
const luci_app_openvpn_plus = async function (args, skipTrim) {
|
||||
const extraArgs = (typeof args === 'string') ? [args] : (Array.isArray(args) ? args : []);
|
||||
const res = await L.resolveDefault(nativeFileExecRpc(CFG.LIBEXEC.luci_app_openvpn_plus, extraArgs), {});
|
||||
|
||||
// CHECK EXIT CODE: If the backend returned anything else than 0, it failed!
|
||||
if (res && typeof res === 'object' && res.code !== undefined && res.code !== 0) {
|
||||
return 'ERROR: CODE_' + res.code;
|
||||
}
|
||||
if (res && typeof res === 'object' && typeof res.stdout === 'string') {
|
||||
if (skipTrim === true) {
|
||||
return res.stdout.trim();
|
||||
// Remove ONLY the very last trailing newline character cleanly
|
||||
return res.stdout.replace(/\r?\n$/, '');
|
||||
} else {
|
||||
return res.stdout.trim();
|
||||
}
|
||||
@@ -210,10 +216,10 @@ const rpcGetServiceStatus = async function () {
|
||||
const secureReadStatusFile = async function (filepath) {
|
||||
// PASS true AS SECOND PARAMETER TO KEEP ALL NEWLINES AND SPACES!
|
||||
const res = await luci_app_openvpn_plus([CFG.LIBEXEC.readstatus, filepath], true);
|
||||
if (res && res.length > 0) {
|
||||
return res;
|
||||
if (!res || res.length === 0 || res.startsWith('ERROR')) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -221,15 +227,11 @@ const secureReadStatusFile = async function (filepath) {
|
||||
*/
|
||||
const secureStatFile = async function (filepath) {
|
||||
const res = await luci_app_openvpn_plus([CFG.LIBEXEC.statfile, filepath]);
|
||||
try {
|
||||
if (res && res.startsWith("{")) {
|
||||
const statObj = JSON.parse(res);
|
||||
if (statObj && typeof statObj === 'object') {
|
||||
return statObj;
|
||||
}
|
||||
if (res && res.startsWith("{")) {
|
||||
const statObj = JSON.parse(res);
|
||||
if (statObj && typeof statObj === 'object') {
|
||||
return statObj;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to parse file statistics: ", CFG.LIBEXEC.luci_app_openvpn_plus + ' ' + CFG.LIBEXEC.statfile + ' ' + filepath + ' -> ' + err);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -277,14 +279,14 @@ const openVpnInfo = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if at least one OpenVPN instance is enabled in the configuration array
|
||||
* Checks if at least one OpenVPN instance is enabled in the configuration array and collects system capabilities securely.
|
||||
*/
|
||||
const getOpenVpnInfo = async function () {
|
||||
|
||||
// get mac address
|
||||
// 1. Get MAC address safely
|
||||
let rawMac = await luci_app_openvpn_plus([CFG.LIBEXEC.getmac]);
|
||||
|
||||
if (rawMac.length > 0 && rawMac !== '00:00:00:00:00:00') {
|
||||
if (rawMac && rawMac.length > 0 && !rawMac.startsWith('ERROR') && rawMac !== '00:00:00:00:00:00') {
|
||||
const cleanMac = rawMac.replace(/[^a-fA-F0-9]/g, '');
|
||||
|
||||
// Isolate the last 3 hex characters (12-bit entropy seed)
|
||||
@@ -297,31 +299,34 @@ const getOpenVpnInfo = async function () {
|
||||
}
|
||||
}
|
||||
|
||||
// get ovpn version
|
||||
// 2. Get OpenVPN version and capabilities safely
|
||||
const rawVersion = await luci_app_openvpn_plus([CFG.LIBEXEC.ovpnver]);
|
||||
if (rawVersion.length > 0) {
|
||||
// Looks for "OpenVPN" followed by spaces, and stops at the first space or bracket.
|
||||
// This safely ignores git tags like [git:DSM7-...] or OS tags.
|
||||
const ovpnMatch = rawVersion.match(/OpenVPN\s+([0-9.]+)/);
|
||||
if (ovpnMatch && ovpnMatch[1]) {
|
||||
openVpnInfo.version = ovpnMatch[1];
|
||||
}
|
||||
|
||||
// This searches for keywords like OpenSSL, mbedTLS, or wolfSSL followed by their numbers.
|
||||
const sslMatch = rawVersion.match(/(OpenSSL|mbedTLS|wolfSSL)\s+([0-9a-zA-Z.]+)/i);
|
||||
if (sslMatch) {
|
||||
// sslMatch[1] is the name (e.g. "OpenSSL"), sslMatch[2] is the version (e.g. "3.5.8")
|
||||
openVpnInfo.openssl = sslMatch[2];
|
||||
openVpnInfo.openssl_type = sslMatch[1];
|
||||
}
|
||||
|
||||
// Looks for "DCO version:". If it finds "N/A", we keep the default 'Disabled' state.
|
||||
const dcoMatch = rawVersion.match(/DCO version:\s+([0-9.]+)/);
|
||||
if (dcoMatch && dcoMatch[1]) {
|
||||
openVpnInfo.dco = dcoMatch[1];
|
||||
}
|
||||
// Abort if the version query returned a backend error or empty response
|
||||
if (!rawVersion || rawVersion.length === 0 || rawVersion.startsWith('ERROR')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks for "OpenVPN" followed by spaces, and stops at the first space or bracket.
|
||||
const ovpnMatch = rawVersion.match(/OpenVPN\s+([0-9.]+)/);
|
||||
if (ovpnMatch && ovpnMatch[1]) {
|
||||
openVpnInfo.version = ovpnMatch[1];
|
||||
}
|
||||
|
||||
// This searches for keywords like OpenSSL, mbedTLS, or wolfSSL followed by their numbers.
|
||||
const sslMatch = rawVersion.match(/(OpenSSL|mbedTLS|wolfSSL)\s+([0-9a-zA-Z.]+)/i);
|
||||
if (sslMatch) {
|
||||
openVpnInfo.openssl = sslMatch[2];
|
||||
openVpnInfo.openssl_type = sslMatch[1];
|
||||
}
|
||||
|
||||
// Looks for "DCO version:". If it finds "N/A", we keep the default 'Disabled' state.
|
||||
const dcoMatch = rawVersion.match(/DCO version:\s+([0-9.]+)/);
|
||||
if (dcoMatch && dcoMatch[1]) {
|
||||
openVpnInfo.dco = dcoMatch[1];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads system uptime from the proc filesystem asynchronously
|
||||
@@ -338,99 +343,99 @@ const getSystemUptime = async function () {
|
||||
* Reads network statistics using parallel RPC requests and updates each instance object
|
||||
*/
|
||||
const refreshNetStat = async function (instances) {
|
||||
try {
|
||||
// Check if the input is a valid array with data
|
||||
if (!Array.isArray(instances) || instances.length === 0) {
|
||||
return;
|
||||
// Check if the input is a valid array with data
|
||||
if (!Array.isArray(instances) || instances.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset all netstat fields to a default empty structure first
|
||||
for (let i = 0; i < instances.length; i++) {
|
||||
instances[i].netstat = {
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxPkts: 0,
|
||||
txPkts: 0,
|
||||
hasData: false
|
||||
};
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
|
||||
// Step 1: Start RPC requests for running instances at the same time
|
||||
for (let j = 0; j < instances.length; j++) {
|
||||
const inst = instances[j];
|
||||
if (inst && inst.isRunning === true && inst.devName) {
|
||||
promises.push(luci_app_openvpn_plus([CFG.LIBEXEC.getnetstat, inst.devName]));
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Wait for all parallel backend calls to finish
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
// Step 3: Parse the results and map them directly into the correct instance
|
||||
for (let k = 0; k < results.length; k++) {
|
||||
const res_getnetstat = results[k] ? String(results[k]).trim() : '';
|
||||
|
||||
// Catch backend execution errors immediately and skip this result row
|
||||
if (res_getnetstat === '' || res_getnetstat.startsWith('ERROR')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reset all netstat fields to a default empty structure first
|
||||
for (let i = 0; i < instances.length; i++) {
|
||||
instances[i].netstat = {
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxPkts: 0,
|
||||
txPkts: 0,
|
||||
hasData: false
|
||||
};
|
||||
}
|
||||
const partsMetrics = res_getnetstat.split('|');
|
||||
|
||||
const promises = [];
|
||||
// Check if the backend returned all 6 fields (rxb|txb|rxp|txp|status|iface)
|
||||
if (partsMetrics && partsMetrics.length === 6) {
|
||||
const ifaceName = partsMetrics[5];
|
||||
const isUp = (partsMetrics[4] === 'true');
|
||||
|
||||
// Step 1: Start RPC requests for running instances at the same time
|
||||
for (let j = 0; j < instances.length; j++) {
|
||||
const inst = instances[j];
|
||||
if (inst && inst.isRunning === true && inst.devName) {
|
||||
promises.push(luci_app_openvpn_plus([CFG.LIBEXEC.getnetstat, inst.devName]));
|
||||
}
|
||||
}
|
||||
// Find the matching instance by comparing the device name
|
||||
for (let m = 0; m < instances.length; m++) {
|
||||
const targetInst = instances[m];
|
||||
const expectedDev = targetInst.devName;
|
||||
|
||||
// Step 2: Wait for all parallel backend calls to finish
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
// Step 3: Parse the results and map them directly into the correct instance
|
||||
for (let k = 0; k < results.length; k++) {
|
||||
const res_getnetstat = results[k] ? String(results[k]).trim() : '';
|
||||
const partsMetrics = res_getnetstat.split('|');
|
||||
|
||||
// Check if the backend returned all 6 fields (rxb|txb|rxp|txp|status|iface)
|
||||
if (partsMetrics && partsMetrics.length === 6) {
|
||||
const ifaceName = partsMetrics[5];
|
||||
const isUp = (partsMetrics[4] === 'true');
|
||||
|
||||
// Find the matching instance by comparing the device name
|
||||
for (let m = 0; m < instances.length; m++) {
|
||||
const targetInst = instances[m];
|
||||
const expectedDev = targetInst.devName;
|
||||
|
||||
if (expectedDev === ifaceName) {
|
||||
targetInst.netstat.rxBytes = parseInt(partsMetrics[0], 10) || 0;
|
||||
targetInst.netstat.txBytes = parseInt(partsMetrics[1], 10) || 0;
|
||||
targetInst.netstat.rxPkts = parseInt(partsMetrics[2], 10) || 0;
|
||||
targetInst.netstat.txPkts = parseInt(partsMetrics[3], 10) || 0;
|
||||
targetInst.netstat.hasData = isUp;
|
||||
break;
|
||||
}
|
||||
if (expectedDev === ifaceName) {
|
||||
targetInst.netstat.rxBytes = parseInt(partsMetrics[0], 10) || 0;
|
||||
targetInst.netstat.txBytes = parseInt(partsMetrics[1], 10) || 0;
|
||||
targetInst.netstat.rxPkts = parseInt(partsMetrics[2], 10) || 0;
|
||||
targetInst.netstat.txPkts = parseInt(partsMetrics[3], 10) || 0;
|
||||
targetInst.netstat.hasData = isUp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Sanity check to fix DCO kernel sync delays against active session bytes
|
||||
for (let n = 0; n < instances.length; n++) {
|
||||
const activeInst = instances[n];
|
||||
// Step 4: Sanity check to fix DCO kernel sync delays against active session bytes
|
||||
for (let n = 0; n < instances.length; n++) {
|
||||
const activeInst = instances[n];
|
||||
|
||||
// Only compare if the instance is running and has connected clients
|
||||
if (activeInst && activeInst.isRunning === true && Array.isArray(activeInst.connectedClients)) {
|
||||
let totalClientRx = 0;
|
||||
let totalClientTx = 0;
|
||||
// Only compare if the instance is running and has connected clients
|
||||
if (activeInst && activeInst.isRunning === true && Array.isArray(activeInst.connectedClients)) {
|
||||
let totalClientRx = 0;
|
||||
let totalClientTx = 0;
|
||||
|
||||
// Sum up the bytes from all currently connected clients
|
||||
for (let c = 0; c < activeInst.connectedClients.length; c++) {
|
||||
const client = activeInst.connectedClients[c];
|
||||
if (client) {
|
||||
totalClientRx += client.bytesReceived || 0;
|
||||
totalClientTx += client.bytesSent || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If kernel statistics are lower than session bytes, force the session values
|
||||
if (activeInst.netstat.rxBytes < totalClientRx) {
|
||||
activeInst.netstat.rxBytes = totalClientRx;
|
||||
activeInst.netstat.hasData = true;
|
||||
}
|
||||
if (activeInst.netstat.txBytes < totalClientTx) {
|
||||
activeInst.netstat.txBytes = totalClientTx;
|
||||
activeInst.netstat.hasData = true;
|
||||
// Sum up the bytes from all currently connected clients
|
||||
for (let c = 0; c < activeInst.connectedClients.length; c++) {
|
||||
const client = activeInst.connectedClients[c];
|
||||
if (client) {
|
||||
totalClientRx += client.bytesReceived || 0;
|
||||
totalClientTx += client.bytesSent || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error refreshing network data:', err);
|
||||
// If kernel statistics are lower than session bytes, force the session values
|
||||
if (activeInst.netstat.rxBytes < totalClientRx) {
|
||||
activeInst.netstat.rxBytes = totalClientRx;
|
||||
activeInst.netstat.hasData = true;
|
||||
}
|
||||
if (activeInst.netstat.txBytes < totalClientTx) {
|
||||
activeInst.netstat.txBytes = totalClientTx;
|
||||
activeInst.netstat.hasData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses client IP addresses from the OpenVPN status log text file
|
||||
*/
|
||||
@@ -676,7 +681,7 @@ const parsePortFromConfig = function (role, content) {
|
||||
* Extracts the OpenVPN protocol string (udp/tcp) from the configuration text safely
|
||||
*/
|
||||
const parseProtoFromConfig = function (content) {
|
||||
if (!content) return OPENVPN.PROTO.UDP;
|
||||
if (!content) return OPENVPN.PROTO.udp;
|
||||
|
||||
let lines
|
||||
if (Array.isArray(content)) {
|
||||
@@ -702,12 +707,12 @@ const parseProtoFromConfig = function (content) {
|
||||
if (tokens[0].toLowerCase() === 'proto') {
|
||||
const protoVal = tokens[1].toLowerCase();
|
||||
// Handle standard openvpn proto values (udp, tcp, udp4, tcp4, udp6, tcp6)
|
||||
if (protoVal.indexOf(OPENVPN.PROTO.TCP) !== -1) return OPENVPN.PROTO.TCP;
|
||||
if (protoVal.indexOf(OPENVPN.PROTO.UDP) !== -1) return OPENVPN.PROTO.UDP;
|
||||
if (protoVal.indexOf(OPENVPN.PROTO.tcp) !== -1) return OPENVPN.PROTO.tcp;
|
||||
if (protoVal.indexOf(OPENVPN.PROTO.udp) !== -1) return OPENVPN.PROTO.udp;
|
||||
return protoVal;
|
||||
}
|
||||
}
|
||||
return OPENVPN.PROTO.UDP; // Default fallback matching standard OpenVPN defaults
|
||||
return OPENVPN.PROTO.udp; // Default fallback matching standard OpenVPN defaults
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1445,7 +1450,7 @@ const renderRemoteNode = function (role, isRunning, connectedClients, protoStr,
|
||||
// Get the first part and convert to lowercase for the check
|
||||
const firstPart = parts[0].toLowerCase();
|
||||
// If first part is a protocol prefix, process and clean it
|
||||
if (firstPart.indexOf(OPENVPN.PROTO.UDP) === 0 || firstPart.indexOf(OPENVPN.PROTO.TCP) === 0) {
|
||||
if (firstPart.indexOf(OPENVPN.PROTO.udp) === 0 || firstPart.indexOf(OPENVPN.PROTO.tcp) === 0) {
|
||||
// Remove everything after the dash (e.g. "tcp4-server" becomes "tcp4")
|
||||
currentProto = firstPart.split('-')[0];
|
||||
displayAddress = parts.slice(1).join(':'); // Keep only IP and port
|
||||
|
||||
@@ -225,8 +225,8 @@ const OPENVPN = Object.freeze({
|
||||
SITETOSITE: 'sitetosite'
|
||||
}),
|
||||
PROTO: Object.freeze({
|
||||
TCP: 'tcp',
|
||||
UDP: 'udp'
|
||||
tcp: 'tcp',
|
||||
udp: 'udp'
|
||||
}),
|
||||
PORT: Object.freeze({
|
||||
s1194: '1194',
|
||||
@@ -271,7 +271,7 @@ const WIZARD_PARAMS_TEMPLATE = Object.freeze({
|
||||
role: OPENVPN.ROLE.SERVER, // Target configuration profile role mapping (server or client)
|
||||
port: OPENVPN.PORT.n1194, // Internal numeric system port assignment for the local process listener
|
||||
portExtern: OPENVPN.PORT.n1194, // External port offset mapping used for firewall rule deployments
|
||||
proto: OPENVPN.PROTO.UDP, // Transport layer connection protocol string selection (udp or tcp)
|
||||
proto: OPENVPN.PROTO.udp, // Transport layer connection protocol string selection (udp or tcp)
|
||||
displayName: '', // Localized custom alias name assigned to distinguish the specific tunnel
|
||||
strategy: OPENVPN.STRATEGY.STANDARD, // Network traffic management profile (standard, redirect, or sitetosite)
|
||||
remoteServer: '', // Client role parameter: Target remote server public IP or domain string
|
||||
@@ -1147,10 +1147,7 @@ const importClientInstance = async function (elements, wizardData, nextBtn, prev
|
||||
}
|
||||
|
||||
try {
|
||||
const extractedParams = await wizardData.importOvpnClientProfileCallback(
|
||||
activeSubParams.importedRawOvpn,
|
||||
targetInstanceId
|
||||
);
|
||||
const extractedParams = await wizardData.importOvpnClientProfileCallback(activeSubParams.importedRawOvpn, targetInstanceId, wizardData.appData);
|
||||
|
||||
// Path A: Update security keys only
|
||||
if (extractedParams.isCryptoUpdateOnly === true) {
|
||||
@@ -1647,7 +1644,7 @@ const renderOptionSiteClient = function (buttons, messageBox) {
|
||||
E('span', {}, [
|
||||
ICON.ARROW + ' ' + TXT.INFO.server_address + ': ',
|
||||
E('code', { 'style': 'font-weight:bold; color:var(--primary-color-high, #1976d2);' },
|
||||
remoteServerMatch[1] + ':' + remoteServerMatch[2] + ' (' + (protoMatch ? protoMatch[1] : OPENVPN.PROTO.UDP) + ')'
|
||||
remoteServerMatch[1] + ':' + remoteServerMatch[2] + ' (' + (protoMatch ? protoMatch[1] : OPENVPN.PROTO.udp) + ')'
|
||||
)
|
||||
])
|
||||
]);
|
||||
@@ -1975,8 +1972,8 @@ const createWizardInputElements = function (buttons, wizardData, selectedScenari
|
||||
radioSiteClient.disabled = true;
|
||||
}
|
||||
|
||||
const radioUdp = E('input', { 'type': 'radio', 'name': 'wizard_proto_group', 'value': OPENVPN.PROTO.UDP, 'style': 'width:18px; height:18px; margin:0; cursor:pointer;' });
|
||||
const radioTcp = E('input', { 'type': 'radio', 'name': 'wizard_proto_group', 'value': OPENVPN.PROTO.TCP, 'style': 'width:18px; height:18px; margin:0; cursor:pointer;' });
|
||||
const radioUdp = E('input', { 'type': 'radio', 'name': 'wizard_proto_group', 'value': OPENVPN.PROTO.udp, 'style': 'width:18px; height:18px; margin:0; cursor:pointer;' });
|
||||
const radioTcp = E('input', { 'type': 'radio', 'name': 'wizard_proto_group', 'value': OPENVPN.PROTO.tcp, 'style': 'width:18px; height:18px; margin:0; cursor:pointer;' });
|
||||
const displayNameInput = E('input', { 'type': 'text', 'class': 'cbi-input-text', 'placeholder': TXT.MSG.placeholder_myhomevpn, 'style': 'width:100%;' });
|
||||
const ddnsInput = E('input', { 'type': 'text', 'class': 'cbi-input-text', 'placeholder': TXT.MSG.placeholder_mydomain_publicip, 'style': 'width:100%; font-weight:bold; color:var(--primary-color-high, #1976d2);' });
|
||||
const portInput = E('input', { 'type': 'text', 'class': 'cbi-input-text', 'style': 'font-family:var(--font-monospace, monospace)' });
|
||||
@@ -2060,7 +2057,7 @@ const createWizardNavigationButtons = function () {
|
||||
const updateDefaultWizardPorts = async function (protoValue, portForwardingAlert, portInput, externPortInput, scenario, wizardData) {
|
||||
let defaultPort;
|
||||
let defaultPortExtern;
|
||||
const isTcp = (protoValue === OPENVPN.PROTO.TCP);
|
||||
const isTcp = (protoValue === OPENVPN.PROTO.tcp);
|
||||
if (isTcp) {
|
||||
const ports = getPortsTcp(wizardData);
|
||||
defaultPort = ports.port
|
||||
@@ -2142,7 +2139,7 @@ const syncScenarioSubNodeVisibility = function (elements) {
|
||||
* Find out if UDP or TCP is selected
|
||||
*/
|
||||
const getActiveProtoValue = function (elements) {
|
||||
const protoValue = elements.radios.tcp.checked ? OPENVPN.PROTO.TCP : OPENVPN.PROTO.UDP;
|
||||
const protoValue = elements.radios.tcp.checked ? OPENVPN.PROTO.tcp : OPENVPN.PROTO.udp;
|
||||
return protoValue;
|
||||
};
|
||||
|
||||
@@ -2299,7 +2296,7 @@ const getPortsTcp = function (wizardData) {
|
||||
|
||||
// 1. Just count how many TCP instances are currently registered in total
|
||||
for (let i = 0; i < instances.length; i++) {
|
||||
if (instances[i] && instances[i].proto === OPENVPN.PROTO.TCP) {
|
||||
if (instances[i] && instances[i].proto === OPENVPN.PROTO.tcp) {
|
||||
tcpInstanceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -17,12 +17,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-wwand
|
||||
PKG_VERSION:=1.7.0_pre8
|
||||
PKG_RELEASE:=44
|
||||
PKG_VERSION:=1.7.0_pre9
|
||||
PKG_RELEASE:=45
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
|
||||
PKG_SOURCE_VERSION:=45ededed69f719e84145839fc0075d8222d18198
|
||||
PKG_SOURCE_VERSION:=ab79811095c81817d55ec9bf82a9fc978f34856d
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
|
||||
+103
-47
@@ -1,72 +1,73 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Copyright (C) 2026 ImmortalWrt.org
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=meow
|
||||
|
||||
# Upstream release baseline. Bump by hand when meow-rs tags a new release.
|
||||
PKG_BASE_VERSION?=0.22.0
|
||||
|
||||
# PKG_VERSION is rewritten automatically by
|
||||
# .github/workflows/sync-upstream.yml from the pinned upstream commit's
|
||||
# committer time (UTC, %Y%m%d%H%M). Do not edit this line by hand.
|
||||
# All-numeric dotted segments keep the version valid for both package
|
||||
# managers: apk -> meow-0.21.2.202610010430-r1.apk
|
||||
# ipk -> meow_0.21.2.202610010430-1_<arch>.ipk
|
||||
PKG_SOURCE_VERSION?=5d7156438bf68636a5b1d00869524726fff03633
|
||||
PKG_VERSION?=$(PKG_BASE_VERSION).202610130311
|
||||
PKG_RELEASE?=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/meow-rs/meow-rs/tar.gz/$(PKG_SOURCE_VERSION)?
|
||||
PKG_HASH:=skip
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||
|
||||
PKG_MAINTAINER:=Independent meow OpenWrt package <noreply@example.invalid>
|
||||
PKG_MAINTAINER:=Max Lv <max.c.lv@gmail.com>
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=rust/host cmake/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/rust/rust-package.mk
|
||||
|
||||
TAR_OPTIONS += --strip-components 1
|
||||
TAR_CMD=$(HOST_TAR) -C $(1) $(TAR_OPTIONS)
|
||||
|
||||
define Package/meow
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=meow high-performance rule-based proxy core
|
||||
URL:=https://github.com/meow-rs/meow-rs
|
||||
LICENSE:=MIT
|
||||
# Required by the default transparent-proxy mode implemented in
|
||||
# files/gateway.sh (nftables "socket transparent" + tproxy for UDP).
|
||||
# Both packages exist for OpenWrt 25.12 (openwrt-25.12 netfilter.mk);
|
||||
# they are published per target under targets/<t>/<s>/kmods/<kver>/.
|
||||
# If you need the package to install on a target whose kmods feed is
|
||||
# stale or mismatched, drop this line - gateway.sh already probes for
|
||||
# the modules at runtime and logs a hint instead of leaking traffic.
|
||||
DEPENDS:=+kmod-nft-socket +kmod-nft-tproxy
|
||||
DEPENDS:=@(aarch64||arm||x86_64||riscv64) +kmod-nft-socket +kmod-nft-tproxy +libstdcpp
|
||||
endef
|
||||
|
||||
define Package/meow/description
|
||||
meow Rust proxy core built from the upstream commit recorded in UPSTREAM.json.
|
||||
A high-performance, rule-based tunneling proxy kernel in Rust,
|
||||
compatible with mihomo (Clash Meta). Static binary; configuration lives in
|
||||
/etc/meow/config.yaml, service settings in /etc/config/meow.
|
||||
|
||||
/usr/bin/meow, the procd init script, UCI settings and the default config
|
||||
at /etc/meow/config.yaml (mixed HTTP/SOCKS5 proxy on :7890, REST API and
|
||||
web panel on :9090, rule mode with MATCH,DIRECT).
|
||||
|
||||
Transparent-proxy / gateway mode additionally needs kmod-nft-socket and
|
||||
kmod-nft-tproxy (pulled in as dependencies).
|
||||
Transparent-proxy / gateway mode additionally uses kmod-nft-socket and
|
||||
kmod-nft-tproxy.
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
endef
|
||||
define Package/meow/config
|
||||
if PACKAGE_meow
|
||||
config MEOW_MINIMAL
|
||||
bool "Build with minimal feature set (--no-default-features --features minimal)"
|
||||
default n
|
||||
help
|
||||
Build a smaller binary using the minimal feature bundle (< 8 MiB budget on aarch64)
|
||||
instead of the default full feature set.
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
config MEOW_EXTERNAL_UI_DOWNLOAD
|
||||
bool "Enable external-ui-download feature"
|
||||
default y
|
||||
help
|
||||
Allow meow to auto-download a third-party web dashboard from external-ui-url.
|
||||
|
||||
define Package/meow/install
|
||||
@test -s ./prebuilt-meow || { echo "ERROR: meow/prebuilt-meow is not populated yet; run build-core.yml first."; exit 1; }
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_DIR) $(1)/etc/meow
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_DIR) $(1)/usr/share/meow
|
||||
$(INSTALL_BIN) ./prebuilt-meow $(1)/usr/bin/meow
|
||||
$(INSTALL_BIN) ./files/meow.init $(1)/etc/init.d/meow
|
||||
$(INSTALL_DATA) ./files/meow.config $(1)/etc/config/meow
|
||||
$(INSTALL_DATA) ./files/config.yaml $(1)/etc/meow/config.yaml
|
||||
$(INSTALL_BIN) ./files/gateway.sh $(1)/usr/share/meow/gateway.sh
|
||||
$(INSTALL_BIN) ./files/arp-hijack.sh $(1)/usr/share/meow/arp-hijack.sh
|
||||
$(INSTALL_BIN) ./files/meow-arp.init $(1)/etc/init.d/meow-arp
|
||||
$(INSTALL_BIN) ./files/meow.uci-defaults $(1)/etc/uci-defaults/80_meow
|
||||
config MEOW_VERSION_SUFFIX
|
||||
string "Optional version suffix (MEOW_VERSION_SUFFIX)"
|
||||
default ""
|
||||
help
|
||||
Optional suffix appended to the version reported by `meow -v` (e.g. -alpha+5d71564).
|
||||
endif
|
||||
endef
|
||||
|
||||
define Package/meow/conffiles
|
||||
@@ -74,4 +75,59 @@ define Package/meow/conffiles
|
||||
/etc/meow/config.yaml
|
||||
endef
|
||||
|
||||
define Select/Features
|
||||
$(strip \
|
||||
$(if $(CONFIG_MEOW_MINIMAL),--no-default-features --features minimal) \
|
||||
$(if $(CONFIG_MEOW_EXTERNAL_UI_DOWNLOAD),--features external-ui-download) \
|
||||
)
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
( \
|
||||
pushd $(PKG_BUILD_DIR) ; \
|
||||
$(CARGO_PKG_CONFIG_VARS) \
|
||||
MEOW_VERSION_SUFFIX="$(call qstrip,$(CONFIG_MEOW_VERSION_SUFFIX))" \
|
||||
cargo build --release \
|
||||
--locked \
|
||||
-p meow-app \
|
||||
--bin meow \
|
||||
--target $(RUSTC_TARGET_ARCH) \
|
||||
$(call Select/Features) ; \
|
||||
popd ; \
|
||||
)
|
||||
endef
|
||||
|
||||
define Package/meow/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET_ARCH)/release/meow $(1)/usr/bin/meow
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/meow.init $(1)/etc/init.d/meow
|
||||
$(INSTALL_BIN) ./files/meow-arp.init $(1)/etc/init.d/meow-arp
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_DATA) ./files/meow.config $(1)/etc/config/meow
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/meow
|
||||
$(INSTALL_DATA) ./files/config.yaml $(1)/etc/meow/config.yaml
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/meow
|
||||
$(INSTALL_BIN) ./files/gateway.sh $(1)/usr/share/meow/gateway.sh
|
||||
$(INSTALL_BIN) ./files/arp-hijack.sh $(1)/usr/share/meow/arp-hijack.sh
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_BIN) ./files/meow.uci-defaults $(1)/etc/uci-defaults/80_meow
|
||||
endef
|
||||
|
||||
define Package/meow/prerm
|
||||
#!/bin/sh
|
||||
[ -n "$${IPKG_INSTROOT}" ] && exit 0
|
||||
/etc/init.d/meow-arp stop 2>/dev/null
|
||||
/etc/init.d/meow-arp disable 2>/dev/null
|
||||
/etc/init.d/meow stop 2>/dev/null
|
||||
/etc/init.d/meow disable || true
|
||||
exit 0
|
||||
endef
|
||||
|
||||
$(eval $(call RustBinPackage,meow))
|
||||
$(eval $(call BuildPackage,meow))
|
||||
Binary file not shown.
+3
-3
@@ -50,12 +50,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand
|
||||
PKG_VERSION:=1.7.0_pre28
|
||||
PKG_RELEASE:=78
|
||||
PKG_VERSION:=1.7.0_pre33
|
||||
PKG_RELEASE:=79
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||
PKG_SOURCE_VERSION:=2a09643571ddfe2c4b538c4123d3d5985b410fcd
|
||||
PKG_SOURCE_VERSION:=5281731de7f5465552a9bba1b909b082b3050fae
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
|
||||
Reference in new issue
Block a user