🗽 Sync 2026-10-16 05:41:31

This commit is contained in:
github-actions[bot] committed 2026-10-16 05:41:31 +08:00
1 parent 8b172bbc49
commit faf621b474
22 files changed
+1505 -635

No files matched your search

File renamed without changes.
+2 -2
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
node_modules/
+13 -45
View File
@@ -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
+514
View File
@@ -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"
}
}
}
}
+6
View File
@@ -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" }
}
+20
View File
@@ -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.
+14
View File
@@ -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'));
+1
View File
@@ -0,0 +1 @@
export { parseDocument, isMap, isSeq } from 'yaml';
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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