Compare commits

...
87 Commits
Author SHA1 Message Date
action 7ae707aaa6 update 2026-10-11 00:49:02
marry-jell / merge (push) Canceled after 0s
2026-10-11 00:49:02 +08:00
action 54d5be6086 update 2026-10-10 18:25:08 2026-10-10 18:25:08 +08:00
action f202e2088f update 2026-10-10 11:56:38 2026-10-10 11:56:38 +08:00
action 33877ad635 update 2026-10-10 04:29:04 2026-10-10 04:29:04 +08:00
action cc69bd0f95 update 2026-10-09 22:53:44 2026-10-09 22:53:44 +08:00
action beac9fdb5a update 2026-10-09 12:11:19 2026-10-09 12:11:19 +08:00
action d6b7af2a48 update 2026-10-09 04:58:55 2026-10-09 04:58:55 +08:00
action 9c17634d78 update 2026-10-08 23:08:26 2026-10-08 23:08:26 +08:00
action 2f8de8b88d update 2026-10-08 20:05:36 2026-10-08 20:05:36 +08:00
cwx 4d8fedd7c6 Add sparse clone for luci-app-honk 2026-10-08 20:05:06 +08:00
action a406d6528c update 2026-10-08 13:17:49 2026-10-08 13:17:49 +08:00
action 4c2363b0c6 update 2026-10-08 07:58:49 2026-10-08 07:58:49 +08:00
action 170f9b0957 update 2026-10-08 02:18:41 2026-10-08 02:18:41 +08:00
action bd061545ed update 2026-10-07 18:47:42 2026-10-07 18:47:42 +08:00
action 617f7ca2a6 update 2026-10-07 11:52:44 2026-10-07 11:52:44 +08:00
action d6cc6efa77 update 2026-10-07 04:44:36 2026-10-07 04:44:36 +08:00
action a2da411394 update 2026-10-06 22:40:04 2026-10-06 22:40:04 +08:00
action daaa6d2406 update 2026-10-06 12:26:53 2026-10-06 12:26:53 +08:00
action 4e01b07aa7 update 2026-10-06 04:10:42 2026-10-06 04:10:42 +08:00
action ec960a1df0 update 2026-10-05 19:05:58 2026-10-05 19:05:58 +08:00
action ce2c617af4 update 2026-10-05 11:38:51 2026-10-05 11:38:51 +08:00
action 11e98dacca update 2026-10-05 06:40:15 2026-10-05 06:40:15 +08:00
action 5c0c622001 update 2026-10-05 00:19:25 2026-10-05 00:19:25 +08:00
action c75ec11645 update 2026-10-04 18:21:54 2026-10-04 18:21:54 +08:00
action 595111c0e8 update 2026-10-04 11:53:55 2026-10-04 11:53:55 +08:00
action 1de7579d69 update 2026-10-04 06:37:06 2026-10-04 06:37:07 +08:00
action 6ef9b7e09f update 2026-10-04 02:56:49 2026-10-04 02:56:49 +08:00
action 880eb38e4c update 2026-10-03 23:35:33 2026-10-03 23:35:33 +08:00
action 7a2d332eaf update 2026-10-03 17:36:19 2026-10-03 17:36:19 +08:00
action ba8090b29d update 2026-10-03 07:30:26 2026-10-03 07:30:26 +08:00
action e14489bd54 update 2026-10-03 01:17:33 2026-10-03 01:17:33 +08:00
action ef3dd5ef36 update 2026-10-02 18:15:02 2026-10-02 18:15:02 +08:00
action 300b28d2ef update 2026-10-02 11:41:37 2026-10-02 11:41:37 +08:00
action a4813b97a5 update 2026-10-02 07:38:12 2026-10-02 07:38:12 +08:00
action c405cd4402 update 2026-10-02 01:53:04 2026-10-02 01:53:04 +08:00
action 82331a7c2e update 2026-10-01 18:39:33 2026-10-01 18:39:33 +08:00
action 7a316664d6 update 2026-10-01 11:42:26 2026-10-01 11:42:26 +08:00
action a1a00fc6d1 update 2026-10-01 07:23:35 2026-10-01 07:23:35 +08:00
action 4596096d87 update 2026-10-01 01:27:34 2026-10-01 01:27:34 +08:00
action 9f80d5fed4 update 2026-09-30 18:13:25 2026-09-30 18:13:25 +08:00
action 511f8cbd60 update 2026-09-30 11:37:03 2026-09-30 11:37:03 +08:00
action 43a8c343a1 update 2026-09-30 07:22:15 2026-09-30 07:22:15 +08:00
action dd96a29afe update 2026-09-30 01:29:31 2026-09-30 01:29:31 +08:00
action cb2fdad301 update 2026-09-29 18:19:55 2026-09-29 18:19:55 +08:00
action 8e341971ff update 2026-09-29 08:07:38 2026-09-29 08:07:38 +08:00
action 530405f036 update 2026-09-29 03:07:25 2026-09-29 03:07:25 +08:00
action 5a3dad58e1 update 2026-09-28 18:25:11 2026-09-28 18:25:11 +08:00
action 5b56b0238b update 2026-09-28 11:10:21 2026-09-28 11:10:21 +08:00
action 43b8c7d729 update 2026-09-28 06:45:19 2026-09-28 06:45:19 +08:00
action f601b769ca update 2026-09-28 00:14:44 2026-09-28 00:14:44 +08:00
action d1352f3b3d update 2026-09-27 17:44:52 2026-09-27 17:44:52 +08:00
action 855b91c0fd update 2026-09-27 15:24:08 2026-09-27 15:24:08 +08:00
action 64e40c30b9 update 2026-09-27 11:19:05 2026-09-27 11:19:05 +08:00
action 5f89ba5c98 update 2026-09-27 11:13:39 2026-09-27 11:13:39 +08:00
action 5a3acab7d3 update 2026-09-27 06:18:31 2026-09-27 06:18:31 +08:00
action cd9aae293c update 2026-09-27 02:55:36 2026-09-27 02:55:36 +08:00
action 384296452c update 2026-09-26 23:35:18 2026-09-26 23:35:18 +08:00
action bcc9ceb04a update 2026-09-26 17:00:06 2026-09-26 17:00:06 +08:00
action 97ae9f68da update 2026-09-26 11:07:27 2026-09-26 11:07:27 +08:00
action 7b88b273d0 update 2026-09-26 07:00:56 2026-09-26 07:00:56 +08:00
action 8971d1aa5c update 2026-09-26 00:21:07 2026-09-26 00:21:07 +08:00
action ba0a17863f update 2026-09-25 19:24:22
marry-jell / merge (push) Canceled after 0s
2026-09-25 19:24:22 +08:00
action e8f9e08c4a update 2026-09-25 17:17:20 2026-09-25 17:17:20 +08:00
action 5d73e6f09a update 2026-09-25 11:03:30 2026-09-25 11:03:30 +08:00
action 6ef6eec405 update 2026-09-25 06:56:42 2026-09-25 06:56:42 +08:00
action 45632307c8 update 2026-09-25 00:20:43 2026-09-25 00:20:43 +08:00
action e4cee331e9 update 2026-09-24 16:56:26 2026-09-24 16:56:26 +08:00
action b31a5c3878 update 2026-09-24 10:46:53 2026-09-24 10:46:53 +08:00
action 907a158127 update 2026-09-24 06:37:55 2026-09-24 06:37:55 +08:00
action 3d5ff73d1e update 2026-09-24 03:28:54 2026-09-24 03:28:54 +08:00
action e22304fb46 update 2026-09-23 23:57:29 2026-09-23 23:57:29 +08:00
action 77395e0373 update 2026-09-23 17:00:17 2026-09-23 17:00:17 +08:00
action 4c14ad0233 update 2026-09-23 10:56:39 2026-09-23 10:56:39 +08:00
action 96cbd4833e update 2026-09-23 06:38:58 2026-09-23 06:38:58 +08:00
action d37965131a update 2026-09-23 00:07:28 2026-09-23 00:07:28 +08:00
action 38830964e1 update 2026-09-22 20:01:19 2026-09-22 20:01:19 +08:00
cwx d695fe6395 Modify Git clone commands in jell.yml
Comment out the git_clone for openwrt-adguardhome and add a new git_clone for luci-app-adguardhome.
2026-09-22 20:00:51 +08:00
action 22202d10ca update 2026-09-22 18:42:30 2026-09-22 18:42:30 +08:00
cwx 098d214aef Add oxidns repository to jell.yml workflow 2026-09-22 18:42:03 +08:00
action a118e282d9 update 2026-09-22 18:19:58 2026-09-22 18:19:58 +08:00
cwx f6dde6fe3d Add cloning for luci-app-oxidns in jell.yml 2026-09-22 18:19:30 +08:00
action 35e3e3404d update 2026-09-22 16:57:57 2026-09-22 16:57:57 +08:00
action f0ef5fcc0f update 2026-09-22 10:56:13 2026-09-22 10:56:13 +08:00
action 1f8bbc1db6 update 2026-09-22 10:14:18 2026-09-22 10:14:18 +08:00
cwx dae92bde98 Optimize cloning of luci-theme-footstrap
Replaced git_clone with git_sparse_clone for luci-theme-footstrap to optimize the cloning process.
2026-09-22 10:13:51 +08:00
action 648031eba2 update 2026-09-22 06:58:02 2026-09-22 06:58:02 +08:00
action f1fdcb16b0 update 2026-09-22 01:42:13 2026-09-22 01:42:13 +08:00
918 changed files with 42444 additions and 75611 deletions

No files matched your search

+10 -2
View File
@@ -88,7 +88,8 @@ jobs:
git_clone https://github.com/kiddin9/aria2 aria2
git_clone https://github.com/kiddin9/autoshare && mvdir autoshare
git_clone https://github.com/kiddin9/luci-app-wizard
git_clone https://github.com/kiddin9/openwrt-adguardhome && mvdir openwrt-adguardhome
#git_clone https://github.com/kiddin9/openwrt-adguardhome && mvdir openwrt-adguardhome
git_clone https://github.com/w9315273/luci-app-adguardhome
git_clone https://github.com/kiddin9/openwrt-clouddrive2 && mvdir openwrt-clouddrive2
git_clone https://github.com/kiddin9/openwrt-filebrowser && mvdir openwrt-filebrowser
) &
@@ -153,16 +154,23 @@ jobs:
# luci-app-daed daed
# git_sparse_clone kix https://github.com/QiuSimons/luci-app-dae \
# luci-app-dae dae
git_sparse_clone master https://github.com/xiaoxiao29/luci-app-adguardhome \
AdGuardHome
git_sparse_clone master https://github.com/kenzok8/small \
dae daed luci-app-daede
git_sparse_clone master https://github.com/QiuSimons/luci-app-honk \
honk luci-app-honk
git_sparse_clone main https://github.com/djylb/nps-openwrt \
npc luci-app-npc
git_sparse_clone main https://github.com/smallprogram/openwrt-ghfu \
luci-app-ghfu
git_clone https://github.com/svenshi/luci-app-oxidns
git_clone https://github.com/caiwx86/openwrt-oxidns oxidns
git_clone https://github.com/eamonxg/luci-theme-shadcn
git_clone https://github.com/eamonxg/luci-theme-aurora
git_clone https://github.com/eamonxg/luci-app-aurora-config
git_clone https://github.com/VizzleTF/luci-theme-footstrap
git_sparse_clone main https://github.com/VizzleTF/luci-theme-footstrap \
luci-theme-footstrap
git_clone https://github.com/sirpdboy/luci-app-chatgpt-web
git_clone https://github.com/sirpdboy/luci-app-eqosplus
git_sparse_clone master "https://github.com/coolsnowwolf/lede" package/wwan package/lean package/network/services/shellsync package/qca/shortcut-fe && cp -rf wwan/*/* ./ ; rm -Rf wwan
+149
View File
@@ -0,0 +1,149 @@
#
# Copyright (C) 2015-2016 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE.txt for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=AdGuardHome
PKG_VERSION:=0.107.79
PKG_RELEASE:=1
# 上游自 v0.107 起为“单二进制 + go:embed 前端”:前端由 client_v2 (npm/webpack)
# 构建到源码根的 build/ 目录,再由 main.go 的 //go:embed build 打包进二进制。
# 这里使用官方 codeload 源码包(比 git clone 快,且可用 PKG_HASH 校验收包)。
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/AdGuardHome/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=5f4fa119f24ea741bb8cecfa74203165fe6b86b904cef05ff429fb227b07d425
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
PKG_LICENSE:=GPL-3.0-only
PKG_LICENSE_FILES:=LICENSE.txt
PKG_MAINTAINER:=Yang Feng <yangfeng229@gmail.com>
PKG_CONFIG_DEPENDS:=CONFIG_ADGUARDHOME_COMPRESS_UPX
# ---------------------------------------------------------------------------
# 工具链说明
#
# 本包不使用 OpenWrt 的 golang/host 与 node/host:
# * AdGuardHome 的 go.mod 要求 go >= 1.26.6,而 OpenWrt 24.10 的 golang feed
# 仍是 1.23.x;
# * node/host 需要从源码编译 Node,代价极高。
# 因此直接调用外部工具链(默认优先取容器内预装的 ~/.openwrt/tools),
# 可用下列变量覆盖:
# ADGH_GO Go 可执行文件(需 >= 1.26)
# ADGH_NODE_DIR 含 node/npm 的目录
# ADGH_NPM npm 可执行文件
# ADGH_NPM_REGISTRY npm 源(默认 npmmirror,国内更快)
# ADGH_GOPROXY Go 模块代理(默认 goproxy.cn)
# 工具链准备方式见 .devcontainer/README.md。
# ---------------------------------------------------------------------------
ADGH_TOOLS_DIR ?= $(HOME)/.openwrt/tools
ADGH_GO ?= $(if $(wildcard $(ADGH_TOOLS_DIR)/go/bin/go),$(ADGH_TOOLS_DIR)/go/bin/go,go)
ADGH_NODE_DIR ?= $(if $(wildcard $(ADGH_TOOLS_DIR)/node/bin/node),$(ADGH_TOOLS_DIR)/node/bin,$(if $(wildcard /usr/bin/node),/usr/bin,/usr/local/bin))
ADGH_NPM ?= $(ADGH_NODE_DIR)/npm
ADGH_NPM_REGISTRY ?= https://registry.npmmirror.com
ADGH_GOPROXY ?= https://goproxy.cn,direct
# OpenWrt ARCH -> GOARCH:纯 Go 交叉编译(CGO_ENABLED=0),无需 target 工具链
ifeq ($(ARCH),x86_64)
ADGH_GOARCH:=amd64
else ifeq ($(ARCH),i386)
ADGH_GOARCH:=386
else ifneq ($(filter aarch64%,$(ARCH)),)
ADGH_GOARCH:=arm64
else ifneq ($(filter arm%,$(ARCH)),)
ADGH_GOARCH:=arm
ADGH_GOARM:=$(if $(findstring _v5,$(ARCH)),5,$(if $(findstring _v6,$(ARCH)),6,7))
else ifneq ($(filter mips64el%,$(ARCH)),)
ADGH_GOARCH:=mips64le
ADGH_GOMIPS:=softfloat
else ifneq ($(filter mipsel%,$(ARCH)),)
ADGH_GOARCH:=mipsle
ADGH_GOMIPS:=softfloat
else ifneq ($(filter mips64%,$(ARCH)),)
ADGH_GOARCH:=mips64
ADGH_GOMIPS:=softfloat
else ifneq ($(filter mips%,$(ARCH)),)
ADGH_GOARCH:=mips
ADGH_GOMIPS:=softfloat
else ifneq ($(filter riscv64%,$(ARCH)),)
ADGH_GOARCH:=riscv64
else ifneq ($(filter powerpc64%,$(ARCH)),)
ADGH_GOARCH:=ppc64
else ifneq ($(filter powerpc%,$(ARCH)),)
ADGH_GOARCH:=ppc
else
ADGH_GOARCH:=unsupported
endif
ADGH_VERSION_PKG:=github.com/AdguardTeam/AdGuardHome/internal/version
ADGH_LDFLAGS:=-s -w \
-X $(ADGH_VERSION_PKG).version=v$(PKG_VERSION) \
-X $(ADGH_VERSION_PKG).channel=release \
-X $(ADGH_VERSION_PKG).committime=$(if $(SOURCE_DATE_EPOCH),$(SOURCE_DATE_EPOCH),0) \
$(if $(ADGH_GOARM),-X $(ADGH_VERSION_PKG).goarm=$(ADGH_GOARM)) \
$(if $(ADGH_GOMIPS),-X $(ADGH_VERSION_PKG).gomips=$(ADGH_GOMIPS))
include $(INCLUDE_DIR)/package.mk
define Package/AdGuardHome
SECTION:=net
CATEGORY:=Network
TITLE:=Network-wide ads and trackers blocking DNS server
URL:=https://github.com/AdguardTeam/AdGuardHome
DEPENDS:=+ca-bundle
endef
define Package/AdGuardHome/description
Free and open source, powerful network-wide ads and trackers blocking DNS server.
endef
define Package/$(PKG_NAME)/config
config ADGUARDHOME_COMPRESS_UPX
bool "Compress executable files with UPX"
default n
endef
define Build/Compile
@if ! $(ADGH_GO) version 2>/dev/null | grep -qE 'go1\.(2[6-9]|[3-9][0-9])'; then \
echo "ERROR: AdGuardHome $(PKG_VERSION) 需要 Go >= 1.26(go.mod 要求 1.26.6)"; \
echo " 当前工具链: $$($(ADGH_GO) version 2>&1)"; \
echo " 可用 ADGH_GO=/path/to/go 指定,准备方法见 .devcontainer/README.md"; \
exit 1; \
fi
@echo "AdGuardHome: 构建前端 client_v2 (npm)"
cd $(PKG_BUILD_DIR)/client_v2 && \
PATH="$(ADGH_NODE_DIR):$(PATH)" $(ADGH_NPM) ci \
--registry=$(ADGH_NPM_REGISTRY) --no-audit --no-fund && \
PATH="$(ADGH_NODE_DIR):$(PATH)" $(ADGH_NPM) run build-prod
@echo "AdGuardHome: 编译后端 (GOARCH=$(ADGH_GOARCH))"
cd $(PKG_BUILD_DIR) && \
CGO_ENABLED=0 GOOS=linux GOARCH=$(ADGH_GOARCH) \
GOARM=$(ADGH_GOARM) GOMIPS=$(ADGH_GOMIPS) \
GOPROXY=$(ADGH_GOPROXY) GOTOOLCHAIN=local \
$(ADGH_GO) build -trimpath -ldflags "$(ADGH_LDFLAGS)" \
-o $(PKG_BUILD_DIR)/AdGuardHome .
ifeq ($(CONFIG_ADGUARDHOME_COMPRESS_UPX),y)
@if [ -x $(STAGING_DIR_HOST)/bin/upx ]; then \
$(STAGING_DIR_HOST)/bin/upx --lzma --best $(PKG_BUILD_DIR)/AdGuardHome; \
else \
echo "WARNING: upx 不可用,跳过压缩"; \
fi
endif
endef
# luci 插件默认使用 /etc/config/adGuardConfig/AdGuardHome(uci: binpath),
# 因此二进制装到该路径;/usr/bin/AdGuardHome 以符号链接提供命令行入口,
# 避免在 ipk 里重复打包 40MB 的二进制。
define Package/AdGuardHome/install
$(INSTALL_DIR) $(1)/etc/config/adGuardConfig
$(INSTALL_BIN) $(PKG_BUILD_DIR)/AdGuardHome $(1)/etc/config/adGuardConfig/AdGuardHome
$(INSTALL_DIR) $(1)/usr/bin
$(LN) /etc/config/adGuardConfig/AdGuardHome $(1)/usr/bin/AdGuardHome
endef
$(eval $(call BuildPackage,AdGuardHome))
+5 -2
View File
@@ -9,7 +9,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=brlaser
PKG_VERSION:=v3
PKG_VERSION:=3
PKG_RELEASE:=1
PKG_SOURCE_URL:=https://github.com/pdewacht/brlaser.git
@@ -17,11 +17,14 @@ PKG_SOURCE_PROTO:=git
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_SOURCE_VERSION:=2a49e3287c70c254e7e3ac9dabe9d6a07218c3fa
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz
PKG_MIRROR_HASH:=skip
PKG_FIXUP:=autoreconf
PKG_INSTALL:=1
# Add CMAKE_POLICY_VERSION_MINIMUM to handle CMake 3.5+ compatibility
CMAKE_OPTIONS += -DCMAKE_POLICY_VERSION_MINIMUM=3.5
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
+4 -4
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=clashoo
PKG_SHORT_SHA:=5019cc0
PKG_COMMIT_DATE:=2026.09.19
PKG_SHORT_SHA:=e4dd968
PKG_COMMIT_DATE:=2026.10.08
PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA)
PKG_RELEASE:=2
@@ -11,8 +11,8 @@ PKG_SOURCE:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA).tar.gz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src
PKG_SOURCE_VERSION:=5019cc090ed7cafb76643f964a76b1e97aee2985
PKG_HASH:=52f127d0125a3fb8e17dc7fe48c8f01e831fbc56cf4c86d7674a11c990e71848
PKG_SOURCE_VERSION:=e4dd968de9e10ecc6770acfafd306edeb578eb52
PKG_HASH:=d566a425f6c87b3f0b9e252fc22df040909662ef7fc387a199203f805231bda9
PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA)
PKG_LICENSE:=GPL3.0+
+157 -85
View File
@@ -6,6 +6,10 @@ START=99
STOP=15
CORE_LOG_PATH="/var/log/clashoo/core.log"
SINGBOX_BIN_DIR="/usr/share/clashoo/bin"
SINGBOX_RUNTIME_DIR="/var/run/clashoo/sing-box"
SINGBOX_RUNTIME_CONFIG="$SINGBOX_RUNTIME_DIR/config.json"
SINGBOX_WORKDIR="$SINGBOX_RUNTIME_DIR/work"
. /usr/share/clashoo/runtime/dns_helpers.sh 2>/dev/null || true
@@ -67,11 +71,76 @@ _compute_start_wait() {
fi
}
# Wait up to $1 seconds for $2 (mihomo|sing-box) to be alive AND stable.
# Returns 0 if alive at end of window, 1 if dead/never-started.
selected_singbox_binary() {
local dcore
dcore="$(uci -q get clashoo.config.dcore 2>/dev/null)"
case "$dcore" in
4) echo "$SINGBOX_BIN_DIR/sing-box-stable" ;;
5) echo "$SINGBOX_BIN_DIR/sing-box-alpha" ;;
*) return 1 ;;
esac
}
_migrate_legacy_singbox_binary() {
local dcore target legacy staged
dcore="$(uci -q get clashoo.config.dcore 2>/dev/null)"
target="$(selected_singbox_binary)" || return 1
[ -e "$target" ] && return 0
case "$dcore" in
4) legacy="/usr/bin/sing-box-stable" ;;
5) legacy="/usr/bin/sing-box-alpha" ;;
*) return 1 ;;
esac
[ -x "$legacy" ] || return 1
mkdir -p "$SINGBOX_BIN_DIR" >/dev/null 2>&1 || return 1
staged="${target}.$$"
rm -f "$staged" >/dev/null 2>&1
cp -f "$legacy" "$staged" >/dev/null 2>&1 || {
rm -f "$staged" >/dev/null 2>&1
return 1
}
chmod 0755 "$staged" >/dev/null 2>&1 || {
rm -f "$staged" >/dev/null 2>&1
return 1
}
mv -f "$staged" "$target" >/dev/null 2>&1 || {
rm -f "$staged" >/dev/null 2>&1
return 1
}
return 0
}
_ensure_singbox_binary() {
local target
target="$(selected_singbox_binary)" || return 1
[ -e "$target" ] || _migrate_legacy_singbox_binary || return 1
[ -x "$target" ]
}
_singbox_pid_owns_private_binary() {
local pid stable alpha exe cmdline
pid="$1"
stable="$SINGBOX_BIN_DIR/sing-box-stable"
alpha="$SINGBOX_BIN_DIR/sing-box-alpha"
[ -d "/proc/$pid" ] || return 1
if command_exists readlink; then
exe="$(readlink -f "/proc/$pid/exe" 2>/dev/null)"
if [ "$exe" = "$stable" ] || [ "$exe" = "$alpha" ]; then
return 0
fi
fi
cmdline="$(tr '\000' ' ' < "/proc/$pid/cmdline" 2>/dev/null)"
case "$cmdline" in
"$stable"|"$stable "*|"$alpha"|"$alpha "*) return 0 ;;
esac
return 1
}
_singbox_instance_pid() {
ubus call service list "{\"name\":\"sing-box\",\"verbose\":true}" 2>/dev/null \
| jsonfilter -e '@["sing-box"].instances.*.pid' 2>/dev/null | head -n1
local _pid
_pid="$(ubus call service list '{"name":"clashoo","verbose":true}' 2>/dev/null \
| jsonfilter -e '@.clashoo.instances.singbox.pid' 2>/dev/null | head -n1)"
[ -n "$_pid" ] && _singbox_pid_owns_private_binary "$_pid" && echo "$_pid"
}
_singbox_running() {
@@ -80,20 +149,6 @@ _singbox_running() {
[ -n "$_pid" ] && [ -d "/proc/$_pid" ]
}
_wait_core_alive() {
local max="${1:-6}" want="${2:-mihomo}" i=0
while [ "$i" -lt "$max" ]; do
sleep 1
if [ "$want" = "sing-box" ] || [ "$want" = "singbox" ]; then
_singbox_running && return 0
else
_core_running && return 0
fi
i=$((i + 1))
done
return 1
}
# Roll back partial state when start failed after backend rules / DNS were applied.
# Reuses the same cleanup primitives as stop_service so behavior stays consistent.
_rollback_failed_start() {
@@ -249,35 +304,46 @@ sync_legacy_core_field() {
}
prepare_singbox_runtime() {
local active src redir_port tproxy_port mixed_port dns_port
local active src redir_port tproxy_port mixed_port dns_port tmp_config
active="$(uci -q get clashoo.config.singbox_active 2>/dev/null)"
src="/usr/share/clashoo/config/singbox/${active}"
tmp_config="${SINGBOX_RUNTIME_CONFIG}.tmp.$$"
mkdir -p /etc/sing-box /usr/share/sing-box "$(dirname "$CORE_LOG_PATH")" >/dev/null 2>&1
rm -f /usr/share/sing-box/cache.db >/dev/null 2>&1
mkdir -p "$SINGBOX_RUNTIME_DIR" "$SINGBOX_WORKDIR" "$(dirname "$CORE_LOG_PATH")" >/dev/null 2>&1 || return 1
rm -f "$tmp_config" >/dev/null 2>&1
if [ -n "$active" ] && [ -r "$src" ]; then
cp -f "$src" /etc/sing-box/config.json >/dev/null 2>&1 || return 1
elif [ ! -r /etc/sing-box/config.json ]; then
cp -f "$src" "$tmp_config" >/dev/null 2>&1 || {
rm -f "$tmp_config" >/dev/null 2>&1
return 1
}
elif [ -r "$SINGBOX_RUNTIME_CONFIG" ]; then
cp -f "$SINGBOX_RUNTIME_CONFIG" "$tmp_config" >/dev/null 2>&1 || {
rm -f "$tmp_config" >/dev/null 2>&1
return 1
}
else
_log_runtime_error "sing-box config.json is missing; please generate or select a sing-box profile first"
rm -f "$tmp_config" >/dev/null 2>&1
return 1
fi
# Core-only: skip clashoo normalize/takeover. Still version-migrate the
# imported config so an older-format config (e.g. from momo/homeproxy or a
# subscription) is accepted by the current sing-box core. The migrate is
# idempotent — a config already in the current format is left unchanged.
if [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" = "1" ]; then
[ -x /usr/bin/ucode ] && ucode /usr/share/clashoo/lib/migrate_singbox.uc /etc/sing-box/config.json >/dev/null 2>&1
uci -q set sing-box.main.enabled='1'
uci -q set sing-box.main.user='root'
uci -q set sing-box.main.conffile='/etc/sing-box/config.json'
uci -q set sing-box.main.workdir='/usr/share/sing-box'
uci -q commit sing-box >/dev/null 2>&1
return 0
fi
command -v ucode >/dev/null 2>&1 || {
_log_runtime_error "ucode is required to prepare sing-box runtime config"
rm -f "$tmp_config" >/dev/null 2>&1
return 1
}
# Migrate every imported config, including core-only mode, before any
# Clashoo-specific normalization. The temp file is in the runtime dir so
# a failed migration/normalization never replaces the last good config.
ucode /usr/share/clashoo/lib/migrate_singbox.uc "$tmp_config" >/dev/null 2>&1 || {
_log_runtime_error "sing-box runtime config migration failed"
rm -f "$tmp_config" >/dev/null 2>&1
return 1
}
if command -v ucode >/dev/null 2>&1; then
if [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" != "1" ]; then
local has_tun_device dash_port dash_pass
has_tun_device=1
runtime_has_tun_device || has_tun_device=0
redir_port="$(uci -q get clashoo.config.redir_port 2>/dev/null)"
@@ -294,44 +360,31 @@ prepare_singbox_runtime() {
# routing_mark 6666 must match fw4.sh CORE_ROUTING_MARK (0x1a0a).
# Never reuse PROXY_FWMARK (0x162) — it would pull egress to lo -> unreachable.
ucode /usr/share/clashoo/lib/normalize_singbox_config.uc \
/etc/sing-box/config.json "$redir_port" "$tproxy_port" "$mixed_port" "$has_tun_device" "6666" "$dns_port" "$dash_port" "$dash_pass" >/dev/null 2>&1 || {
"$tmp_config" "$redir_port" "$tproxy_port" "$mixed_port" "$has_tun_device" "6666" "$dns_port" "$dash_port" "$dash_pass" >/dev/null 2>&1 || {
_log_runtime_error "sing-box runtime config normalization failed"
rm -f "$tmp_config" >/dev/null 2>&1
return 1
}
fi
uci -q set sing-box.main.enabled='1'
uci -q set sing-box.main.user='root'
uci -q set sing-box.main.conffile='/etc/sing-box/config.json'
uci -q set sing-box.main.workdir='/usr/share/sing-box'
uci -q commit sing-box >/dev/null 2>&1
mv -f "$tmp_config" "$SINGBOX_RUNTIME_CONFIG" >/dev/null 2>&1 || {
rm -f "$tmp_config" >/dev/null 2>&1
return 1
}
return 0
}
_sync_singbox_binary() {
# Sync /usr/bin/sing-box to the dcore-selected channel (stable/alpha), coexisting via separate paths.
local dcore src
dcore="$(uci -q get clashoo.config.dcore 2>/dev/null)"
case "$dcore" in
4) src="/usr/bin/sing-box-stable" ;;
5) src="/usr/bin/sing-box-alpha" ;;
*) return 0 ;;
esac
[ -x "$src" ] || return 0
[ "$src" -ef /usr/bin/sing-box ] && return 0
ln -sf "$src" /usr/bin/sing-box
}
start_singbox_service() {
_sync_singbox_binary
if [ ! -x /usr/bin/sing-box ]; then
local bin
bin="$(selected_singbox_binary)" || return 1
if ! _ensure_singbox_binary; then
_log_runtime_error "sing-box core not found, please install the local sing-box binary first"
return 1
fi
prepare_singbox_runtime || return 1
# preflight dry-run with watchdog: bail on bad config without touching
# firewall/dnsmasq, and never let a hung check freeze startup/UI.
_run_with_timeout 15 /usr/bin/sing-box -c /etc/sing-box/config.json check >> "$CORE_LOG_PATH" 2>&1
_run_with_timeout 15 "$bin" -c "$SINGBOX_RUNTIME_CONFIG" check >> "$CORE_LOG_PATH" 2>&1
_pf_rc=$?
if [ "$_pf_rc" -ne 0 ]; then
_pf_reason="$(_preflight_reason)"
@@ -344,20 +397,29 @@ start_singbox_service() {
fi
return 1
fi
/etc/init.d/sing-box start >/dev/null 2>&1
procd_open_instance "singbox"
procd_set_param command "$bin" run -c "$SINGBOX_RUNTIME_CONFIG" -D "$SINGBOX_WORKDIR"
procd_set_param user root
procd_set_param file "$SINGBOX_RUNTIME_CONFIG"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000"
procd_set_param respawn 3600 5 5
procd_close_instance
}
stop_singbox_service() {
uci -q set sing-box.main.enabled='0' && uci -q commit sing-box >/dev/null 2>&1 || true
# Stop via /etc/init.d/sing-box stop (deregisters procd). Never pidof sing-box — co-installed plugins may share the binary.
[ -x /etc/init.d/sing-box ] && /etc/init.d/sing-box stop >/dev/null 2>&1 || true
local _wait=0 _pid
# procd_kill removes the named instance and disables its respawn; signaling
# the child through ubus would let procd recreate it behind our back.
procd_kill clashoo singbox >/dev/null 2>&1 || true
_pid="$(_singbox_instance_pid)"
[ -n "$_pid" ] || return 0
while [ "$_wait" -lt 3 ]; do
_pid="$(_singbox_instance_pid)"
[ -z "$_pid" ] && return 0
[ -d "/proc/$_pid" ] || return 0
_singbox_running || return 0
sleep 1
_wait=$((_wait + 1))
done
@@ -1939,9 +2001,11 @@ _finish_geo_bootstrap() {
_start_validate_core() {
local core bin
core="$(uci -q get clashoo.config.core 2>/dev/null)"
if is_singbox_selected; then
if [ ! -x /usr/bin/sing-box ]; then
bin="$(selected_singbox_binary)"
if [ -z "$bin" ] || ! _ensure_singbox_binary; then
log_msg "sing-box core not found, please install the local sing-box binary first" "找不到本地 sing-box 内核,请先安装 sing-box 二进制"
echo "Clashoo" > "$REAL_LOG"
return 1
@@ -2168,10 +2232,6 @@ _start_launch_core() {
}
boot(){
# sing-box is managed by Clashoo when selected; keep its own init.d from
# starting independently before Clashoo has prepared config/firewall/DNS.
uci -q set sing-box.main.enabled='0' && uci -q commit sing-box >/dev/null 2>&1 || true
if [ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ]; then
runtime_state_reset
runtime_state_set "health_status" "starting"
@@ -2251,15 +2311,27 @@ start_service(){
else
log_msg "Core-only mode: skipping firewall/DNS takeover" "仅内核模式:跳过防火墙与 DNS 接管"
fi
# Sync-wait: if sing-box dies within window, roll back applied state.
# /etc/init.d/sing-box start forks immediately so we can poll here.
if ! _wait_core_alive "$(_compute_start_wait)" sing-box; then
# procd does not launch the named instance until start_service returns
# and rc.common submits the completed service definition. Watch it in
# the background, just like the mihomo path, then roll back only if the
# registered private instance never becomes healthy.
(
exec 1000>&-
_bg_max="$(_compute_start_wait)"
_bg_wait=0
while [ "$_bg_wait" -lt "$_bg_max" ]; do
sleep 1
if _singbox_running; then
runtime_run_health_check_settle >/dev/null 2>&1
exit 0
fi
_bg_wait=$((_bg_wait + 1))
done
_rollback_failed_start
runtime_state_record_health "fail" "start:singbox_died_after_apply"
runtime_state_record_health "fail" "start:singbox_not_running"
echo " $(date "+%Y-%m-%d %H:%M:%S") - clashoo sing-box not detected within ${_bg_max}s, rolled back" >> /usr/share/clashoo/clashoo.txt
echo "Clashoo" > "$REAL_LOG"
return 1
fi
(exec 1000>&-; runtime_run_health_check_settle >/dev/null 2>&1) &
) &
echo "Clashoo" > "$REAL_LOG"
return 0
fi
@@ -2397,12 +2469,12 @@ reload_service()
{
if is_singbox_selected; then
if _singbox_running; then
resolve_effective_tp_modes
runtime_state_record_preflight "singbox"
prepare_singbox_runtime >/dev/null 2>&1
/etc/init.d/sing-box restart >/dev/null 2>&1
singbox_dns_change >/dev/null 2>&1
runtime_run_health_check_settle >/dev/null 2>&1
# Use rc.common's transaction wrapper so the complete Clashoo service
# definition is rebuilt before the named instance is registered again.
# This intentionally reapplies Clashoo's own firewall/DNS state; calling
# start_service directly would skip that wrapper and procd setup.
stop_singbox_service >/dev/null 2>&1
start
fi
elif _core_running; then
resolve_effective_tp_modes
@@ -6,19 +6,15 @@ set clashoo_china6 {
2001:250::/30,
2001:254::/33,
2001:255::/32,
2001:256::/32,
2001:678:12b8::/48,
2001:678:12c4::/48,
2001:678:12cc::/48,
2001:67c:a1c::/48,
2001:67c:ebc::/48,
2001:67c:2c1c::/48,
2001:7fa:5::/48,
2001:7fa:10::/48,
2001:c68::/32,
2001:cc0::/32,
2001:da8::/31,
2001:daa::/32,
2001:da8::/32,
2001:daa:1::/48,
2001:daa:2::/47,
2001:daa:4::/47,
2001:daa:6::/48,
2001:daa:9::/48,
2001:dc7::/45,
2001:dc7:9::/48,
2001:dc7:a::/47,
@@ -36,64 +32,11 @@ set clashoo_china6 {
2001:dc7:4000::/34,
2001:dc7:8000::/33,
2001:dd8:1::/48,
2001:dd8:5::/48,
2001:dd9::/48,
2001:df0:423::/48,
2001:df0:9c0::/48,
2001:df0:1bc0::/48,
2001:df0:25c0::/48,
2001:df0:2e00::/48,
2001:df0:2e80::/48,
2001:df0:85c0::/48,
2001:df0:9d40::/48,
2001:df0:ac40::/48,
2001:df0:bf80::/48,
2001:df0:d880::/48,
2001:df0:f8c0::/48,
2001:df1:5280::/48,
2001:df1:5b80::/48,
2001:df1:6180::/48,
2001:df1:d180::/48,
2001:df1:f480::/48,
2001:df2:5780::/48,
2001:df2:8bc0::/48,
2001:df2:a580::/48,
2001:df2:c240::/48,
2001:df2:d4c0::/48,
2001:df3:15c0::/48,
2001:df3:7440::/48,
2001:df3:a680::/48,
2001:df3:c680::/48,
2001:df3:d0c0::/48,
2001:df3:ef80::/48,
2001:df4:1880::/48,
2001:df4:2780::/48,
2001:df4:2e80::/48,
2001:df4:3d80::/48,
2001:df4:4b80::/48,
2001:df4:4d80::/48,
2001:df4:c180::/48,
2001:df4:c580::/48,
2001:df4:e140::/48,
2001:df4:e142::/47,
2001:df5:2fc0::/48,
2001:df5:4cc0::/48,
2001:df5:5f80::/48,
2001:df5:7800::/48,
2001:df6:100::/48,
2001:df6:5d00::/48,
2001:df6:6800::/48,
2001:df6:f400::/48,
2001:df7:2b80::/48,
2001:df7:6600::/48,
2001:e08::/32,
2001:e18::/32,
2001:e80::/32,
2001:e88::/32,
2001:f38::/32,
2001:f88::/32,
2001:4438::/32,
2001:4510::/29,
2001:4510:400::/40,
2001:4510:1480::/41,
2001:4511:1480::/41,
2400:1040::/32,
2400:1160::/32,
2400:12c0::/32,
@@ -1012,6 +955,7 @@ set clashoo_china6 {
2402:7540::/32,
2402:75c0::/32,
2402:7740::/32,
2402:7820::/32,
2402:7d00::/32,
2402:7d80::/32,
2402:8180::/32,
@@ -1185,12 +1129,7 @@ set clashoo_china6 {
2403:e80::/32,
2403:f00::/32,
2403:f80::/32,
2403:fc0:400::/38,
2403:fc0:c00::/38,
2403:fc0:1000::/36,
2403:fc0:2000::/35,
2403:fc0:4000::/34,
2403:fc0:8000::/33,
2403:fc0::/32,
2403:1180::/32,
2403:1340::/32,
2403:1440::/32,
@@ -1291,7 +1230,6 @@ set clashoo_china6 {
2403:4cc0::/32,
2403:4d80::/32,
2403:4ec0::/32,
2403:5040::/32,
2403:5080::/32,
2403:5280::/32,
2403:5380::/32,
@@ -1317,11 +1255,10 @@ set clashoo_china6 {
2403:62c0::/32,
2403:6380::/42,
2403:6380:41::/48,
2403:6380:43::/48,
2403:6380:44::/46,
2403:6380:48::/45,
2403:6380:50::/44,
2403:6380:60::/43,
2403:6380:70::/44,
2403:6380:80::/41,
2403:6380:100::/40,
2403:6380:200::/39,
@@ -1824,7 +1761,8 @@ set clashoo_china6 {
2405:84c0:8000::/48,
2405:84c0:8002::/48,
2405:84c0:8006::/48,
2405:84c0:8008::/45,
2405:84c0:800a::/47,
2405:84c0:800c::/46,
2405:84c0:8010::/48,
2405:84c0:8019::/48,
2405:84c0:801c::/46,
@@ -1997,7 +1935,13 @@ set clashoo_china6 {
2406:840:118::/45,
2406:840:120::/43,
2406:840:140::/42,
2406:840:180::/41,
2406:840:181::/48,
2406:840:182::/47,
2406:840:184::/46,
2406:840:188::/45,
2406:840:190::/44,
2406:840:1a0::/43,
2406:840:1c0::/42,
2406:840:201::/48,
2406:840:202::/47,
2406:840:204::/46,
@@ -2277,7 +2221,6 @@ set clashoo_china6 {
2406:840:9103::/48,
2406:840:9104::/46,
2406:840:9108::/45,
2406:840:9111::/48,
2406:840:9112::/47,
2406:840:9114::/46,
2406:840:9118::/45,
@@ -2299,7 +2242,9 @@ set clashoo_china6 {
2406:840:9667::/48,
2406:840:9668::/45,
2406:840:9670::/44,
2406:840:9680::/41,
2406:840:9690::/44,
2406:840:96a0::/43,
2406:840:96c0::/42,
2406:840:9800::/46,
2406:840:9808::/45,
2406:840:9810::/45,
@@ -2316,6 +2261,7 @@ set clashoo_china6 {
2406:840:9900::/42,
2406:840:9940::/43,
2406:840:9960::/47,
2406:840:9963::/48,
2406:840:9965::/48,
2406:840:9968::/46,
2406:840:996d::/48,
@@ -2337,7 +2283,10 @@ set clashoo_china6 {
2406:840:9fc0::/43,
2406:840:9fe0::/44,
2406:840:a000::/35,
2406:840:c000::/36,
2406:840:c000::/37,
2406:840:c800::/38,
2406:840:cd00::/40,
2406:840:ce00::/39,
2406:840:e000::/41,
2406:840:e090::/44,
2406:840:e0a0::/43,
@@ -2346,7 +2295,7 @@ set clashoo_china6 {
2406:840:e0cc::/47,
2406:840:e0ce::/48,
2406:840:e0d0::/44,
2406:840:e0e4::/46,
2406:840:e0e6::/47,
2406:840:e0e9::/48,
2406:840:e0ea::/47,
2406:840:e0ec::/46,
@@ -2520,10 +2469,6 @@ set clashoo_china6 {
2406:840:f600::/42,
2406:840:f640::/43,
2406:840:f670::/44,
2406:840:f680::/47,
2406:840:f682::/48,
2406:840:f684::/46,
2406:840:f688::/45,
2406:840:f690::/44,
2406:840:f6a0::/43,
2406:840:f6c0::/42,
@@ -2684,7 +2629,6 @@ set clashoo_china6 {
2406:840:fee8::/46,
2406:840:feec::/48,
2406:840:feee::/47,
2406:840:fef1::/48,
2406:840:ff00::/40,
2406:880::/32,
2406:8c0::/32,
@@ -3115,7 +3059,6 @@ set clashoo_china6 {
2408:4008::/29,
2408:4010::/30,
2408:4014::/31,
2408:4016:1::/48,
2408:4016:2::/47,
2408:4016:4::/46,
2408:4016:8::/45,
@@ -3281,10 +3224,39 @@ set clashoo_china6 {
2605:9d80:9042::/48,
2605:9d80:9071::/48,
2605:9d80:9092::/48,
2620:57:4004::/48,
2620:57:4004::/47,
2804:1e48:9002::/48,
2a03:f900::/29,
2a04:3e00::/48,
2a04:3e00:2::/47,
2a04:3e00:4::/46,
2a04:3e00:8::/45,
2a04:3e00:10::/44,
2a04:3e00:20::/43,
2a04:3e00:40::/42,
2a04:3e00:80::/41,
2a04:3e00:100::/40,
2a04:3e00:200::/39,
2a04:3e00:400::/38,
2a04:3e00:800::/37,
2a04:3e00:1001::/48,
2a04:3e00:1002::/48,
2a04:3e00:1004::/46,
2a04:3e00:1008::/45,
2a04:3e00:1010::/44,
2a04:3e00:1020::/43,
2a04:3e00:1040::/42,
2a04:3e00:1080::/41,
2a04:3e00:1100::/40,
2a04:3e00:1200::/39,
2a04:3e00:1400::/38,
2a04:3e00:1800::/37,
2a04:3e00:2000::/35,
2a04:3e00:4000::/34,
2a04:3e00:8000::/33,
2a04:3e01::/32,
2a04:3e02::/31,
2a04:3e04::/30,
2a04:f580:8010::/47,
2a04:f580:8090::/48,
2a04:f580:8210::/47,
@@ -3346,7 +3318,9 @@ set clashoo_china6 {
2a0a:d681:e200::/39,
2a0a:d681:e400::/38,
2a0a:d681:e800::/37,
2a0a:d681:f000::/36,
2a0a:d681:f000::/37,
2a0a:d681:f800::/38,
2a0a:d681:fe00::/40,
2a0a:d682::/33,
2a0a:d682:8000::/34,
2a0a:d682:c000::/36,
@@ -435,6 +435,7 @@ set clashoo_china {
43.241.48.0/22,
43.241.76.0/22,
43.241.80.0/20,
43.241.100.0/23,
43.241.112.0/22,
43.241.168.0/21,
43.241.176.0/21,
@@ -955,7 +956,6 @@ set clashoo_china {
61.29.128.0/18,
61.29.192.0/19,
61.29.224.0/20,
61.29.251.0/24,
61.45.128.0/18,
61.45.224.0/20,
61.47.128.0/18,
@@ -998,7 +998,6 @@ set clashoo_china {
71.132.0.0/18,
71.136.64.0/18,
71.137.0.0/18,
74.122.26.0/23,
81.68.0.0/14,
81.173.18.0/23,
81.173.20.0/22,
@@ -1906,6 +1905,7 @@ set clashoo_china {
103.99.53.0/24,
103.99.54.0/23,
103.99.56.0/22,
103.99.62.0/24,
103.99.104.0/22,
103.99.116.0/22,
103.99.120.0/22,
@@ -1956,6 +1956,7 @@ set clashoo_china {
103.104.104.0/22,
103.104.152.0/22,
103.104.188.0/22,
103.104.198.0/23,
103.104.252.0/22,
103.105.0.0/21,
103.105.12.0/22,
@@ -2325,7 +2326,6 @@ set clashoo_china {
103.169.108.0/23,
103.169.162.0/23,
103.169.202.0/23,
103.169.216.0/24,
103.170.4.0/23,
103.170.134.0/23,
103.170.212.0/23,
@@ -3063,71 +3063,7 @@ set clashoo_china {
111.235.160.0/19,
112.0.0.0/10,
112.64.0.0/14,
112.73.66.0/24,
112.73.68.0/24,
112.73.70.0/24,
112.73.74.0/24,
112.73.76.0/24,
112.73.79.0/24,
112.73.92.0/24,
112.73.97.0/24,
112.73.98.0/23,
112.73.100.0/22,
112.73.106.0/23,
112.73.108.0/24,
112.73.110.0/24,
112.73.112.0/23,
112.73.115.0/24,
112.73.116.0/24,
112.73.120.0/24,
112.73.122.0/24,
112.73.124.0/23,
112.73.127.0/24,
112.73.128.0/23,
112.73.130.0/24,
112.73.132.0/23,
112.73.136.0/24,
112.73.139.0/24,
112.73.140.0/24,
112.73.143.0/24,
112.73.145.0/24,
112.73.146.0/23,
112.73.148.0/22,
112.73.152.0/22,
112.73.157.0/24,
112.73.158.0/23,
112.73.160.0/24,
112.73.163.0/24,
112.73.166.0/23,
112.73.168.0/24,
112.73.170.0/24,
112.73.173.0/24,
112.73.174.0/24,
112.73.178.0/24,
112.73.181.0/24,
112.73.182.0/24,
112.73.190.0/24,
112.73.192.0/23,
112.73.196.0/24,
112.73.199.0/24,
112.73.200.0/23,
112.73.205.0/24,
112.73.207.0/24,
112.73.209.0/24,
112.73.210.0/24,
112.73.213.0/24,
112.73.218.0/24,
112.73.224.0/24,
112.73.228.0/24,
112.73.231.0/24,
112.73.232.0/23,
112.73.235.0/24,
112.73.238.0/24,
112.73.242.0/23,
112.73.244.0/23,
112.73.247.0/24,
112.73.250.0/23,
112.73.254.0/24,
112.73.232.0/24,
112.74.0.0/15,
112.80.0.0/12,
112.96.0.0/13,
@@ -3413,11 +3349,11 @@ set clashoo_china {
117.132.0.0/15,
117.134.0.0/17,
117.134.128.0/18,
117.134.205.0/24,
117.134.207.0/24,
117.134.208.0/23,
117.134.212.0/23,
117.134.216.0/21,
117.134.216.0/22,
117.134.222.0/23,
117.134.232.0/21,
117.134.240.0/20,
117.135.0.0/16,
@@ -3769,7 +3705,6 @@ set clashoo_china {
123.49.236.0/24,
123.49.240.0/24,
123.49.242.0/23,
123.49.245.0/24,
123.49.248.0/21,
123.50.160.0/19,
123.52.0.0/14,
@@ -3907,8 +3842,6 @@ set clashoo_china {
125.92.0.0/16,
125.93.0.0/20,
125.93.16.0/21,
125.93.24.0/23,
125.93.26.0/24,
125.93.28.0/22,
125.93.32.0/19,
125.93.64.0/18,
@@ -4032,7 +3965,6 @@ set clashoo_china {
142.86.224.0/20,
142.86.240.0/21,
142.86.248.0/22,
142.86.253.0/24,
142.86.254.0/23,
143.64.0.0/16,
144.0.0.0/16,
@@ -4077,7 +4009,8 @@ set clashoo_china {
147.78.132.0/24,
147.78.134.0/24,
148.70.0.0/16,
149.41.0.0/16,
149.41.64.0/18,
149.41.192.0/19,
150.0.0.0/16,
150.115.0.0/16,
150.121.0.0/16,
@@ -4258,6 +4191,7 @@ set clashoo_china {
163.52.28.0/23,
163.52.76.0/23,
163.52.108.0/23,
163.52.246.0/23,
163.53.0.0/20,
163.53.36.0/22,
163.53.40.0/21,
@@ -4469,7 +4403,6 @@ set clashoo_china {
183.184.0.0/13,
183.192.0.0/10,
185.2.48.0/24,
185.2.51.0/24,
185.75.173.0/24,
185.75.174.0/24,
185.192.56.0/22,
Binary file not shown.
Binary file not shown.
@@ -153,21 +153,25 @@ resolve_via() {
}
curl_subscription() {
local url tmp hdr err ua extra_resolve http_code rc
local url tmp hdr err ua host ip http_code rc
url="$1"
tmp="$2"
hdr="$3"
err="$4"
ua="$5"
extra_resolve="$6"
host="$6"
ip="$7"
rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1
# shellcheck disable=SC2086
set --
if [ -n "$host" ] && [ -n "$ip" ]; then
set -- --resolve "$host:443:$ip" --resolve "$host:80:$ip"
fi
http_code="$(curl -sSL --connect-timeout 15 --max-time 60 \
--speed-time 30 --speed-limit 1 --retry 2 \
-H "User-Agent: ${ua}" -D "$hdr" -o "$tmp" \
$extra_resolve \
"$@" \
-w '%{http_code}' "$url" 2>"$err")"
rc=$?
printf '%s\n' "$http_code"
@@ -176,7 +180,7 @@ curl_subscription() {
download_subscription() {
local url target tmp hdr err rc http_code info_line ua err_msg
local host ip dns extra
local host ip dns
url="$1"
target="$2"
tmp="${TMP_PREFIX}.yaml"
@@ -187,25 +191,27 @@ download_subscription() {
[ -n "$ua" ] || ua="$DEFAULT_SUB_UA"
if command -v curl >/dev/null 2>&1; then
http_code="$(curl_subscription "$url" "$tmp" "$hdr" "$err" "$ua" "")"
http_code="$(curl_subscription "$url" "$tmp" "$hdr" "$err" "$ua" "" "")"
rc=$?
if [ "$rc" -ne 0 ] || [ "$http_code" = "000" ]; then
host="$(extract_host "$url")"
case "$host" in
''|[-.]*|*[!A-Za-z0-9.-]*) host='' ;;
esac
if [ -n "$host" ]; then
for dns in 223.5.5.5 119.29.29.29 1.1.1.1 8.8.8.8; do
ip="$(resolve_via "$host" "$dns")"
[ -n "$ip" ] || continue
log_update "DNS 回退:${host} -> ${ip} (@${dns})"
extra="--resolve ${host}:443:${ip} --resolve ${host}:80:${ip}"
http_code="$(curl_subscription "$url" "$tmp" "$hdr" "$err" "$ua" "$extra")"
http_code="$(curl_subscription "$url" "$tmp" "$hdr" "$err" "$ua" "$host" "$ip")"
rc=$?
[ "$rc" -eq 0 ] && [ "$http_code" = "200" ] && break
done
fi
fi
else
wget -q --tries=4 --timeout=20 \
wget -q --timeout=20 \
--user-agent="$ua" "$url" -O "$tmp" 2>"$err"
rc=$?
http_code=""
@@ -101,8 +101,25 @@ if (keep_fakeip) {
delete cfg['dns']['fake-ip-filter'];
} else {
let push_filter = function(f) {
if (f == 'geosite:cn') { f = 'rule-set:cn_domain'; need_cn_rs = true; }
else if (f == 'rule-set:cn_domain') need_cn_rs = true;
f = trim(f || '');
if (!length(f)) return;
if (filter_mode == 'rule') {
if (match(f, /,(fake-ip|real-ip)$/)) { push(cfg['dns']['fake-ip-filter'], f); return; }
if (index(f, ',') >= 0) { push(cfg['dns']['fake-ip-filter'], f + ',real-ip'); return; }
if (f == 'geosite:cn') { f = 'RULE-SET,cn_domain,real-ip'; need_cn_rs = true; }
else if (match(f, /^geosite:/)) { f = 'GEOSITE,' + substr(f, 8) + ',real-ip'; }
else if (match(f, /^rule-set:/)) {
let rs = substr(f, 9);
if (rs == 'cn_domain') need_cn_rs = true;
f = 'RULE-SET,' + rs + ',real-ip';
}
else if (match(f, /^\+\./)) { f = 'DOMAIN-SUFFIX,' + substr(f, 2) + ',real-ip'; }
else if (match(f, /\*/)) { f = 'DOMAIN-WILDCARD,' + f + ',real-ip'; }
else { f = 'DOMAIN,' + f + ',real-ip'; }
} else {
if (f == 'geosite:cn') { f = 'rule-set:cn_domain'; need_cn_rs = true; }
else if (f == 'rule-set:cn_domain') need_cn_rs = true;
}
push(cfg['dns']['fake-ip-filter'], f);
};
let filters = a('fake_ip_filter');
@@ -7,15 +7,21 @@ SECRET=$(uci get clashoo.config.dash_pass 2>/dev/null)
LAN_IP=$(uci get network.lan.ipaddr 2>/dev/null |awk -F '/' '{print $1}' 2>/dev/null)
PORT=$(uci get clashoo.config.dash_port 2>/dev/null)
urlencode() {
local data
if [ "$#" != 1 ]; then
return 1
fi
data=$(curl -s -o /dev/null -w %{url_effective} --get --data-urlencode "$1" "")
if [ ! -z "$data" ]; then
echo "${data##/?}"
fi
return 0
printf '%s' "$1" | hexdump -v -e '1/1 "%u "' | awk '
{
for (i = 1; i <= NF; i++) {
b = $i + 0
if ((b >= 48 && b <= 57) || (b >= 65 && b <= 90) ||
(b >= 97 && b <= 122) || b == 45 || b == 46 ||
b == 95 || b == 126)
printf "%c", b
else
printf "%%%02X", b
}
}'
}
cat $HISTORY_PATH |while read line
do
@@ -28,4 +34,4 @@ do
fi
done >/dev/null 2>&1
curl -m 5 --retry 2 -H "Authorization: Bearer ${SECRET}" -H "Content-Type:application/json" -X DELETE http://"$LAN_IP":"$PORT"/connections >/dev/null 2>&1
fi
fi
@@ -58,8 +58,8 @@ fetch_text() {
return $?
fi
if command -v wget >/dev/null 2>&1; then
[ -n "$proxy" ] && { http_proxy="$proxy" https_proxy="$proxy" wget -qO- --timeout="$REQUEST_TIMEOUT" --tries=1 "$url"; return $?; }
wget -qO- --timeout="$REQUEST_TIMEOUT" --tries=1 "$url"
[ -n "$proxy" ] && { http_proxy="$proxy" https_proxy="$proxy" wget -qO- --timeout="$REQUEST_TIMEOUT" "$url"; return $?; }
wget -qO- --timeout="$REQUEST_TIMEOUT" "$url"
return $?
fi
return 127
@@ -74,10 +74,10 @@ download_file() {
return $?
fi
if [ -n "$proxy" ]; then
http_proxy="$proxy" https_proxy="$proxy" wget -qO "$out" --timeout="$REQUEST_TIMEOUT" --tries=1 "$url"
http_proxy="$proxy" https_proxy="$proxy" wget -qO "$out" --timeout="$REQUEST_TIMEOUT" "$url"
return $?
fi
wget -qO "$out" --timeout="$REQUEST_TIMEOUT" --tries=1 "$url"
wget -qO "$out" --timeout="$REQUEST_TIMEOUT" "$url"
}
download_url() {
@@ -377,7 +377,6 @@ restore_config_backup() {
clashoo_was_running() {
/etc/init.d/clashoo status >/dev/null 2>&1 && return 0
/etc/init.d/sing-box status >/dev/null 2>&1 && return 0
return 1
}
@@ -9,6 +9,11 @@ TARGET_DCORE="$1"
CORETYPE=${TARGET_DCORE:-$(uci get clashoo.config.dcore 2>/dev/null)}
MIRROR_PREFIX=$(uci get clashoo.config.core_mirror_prefix 2>/dev/null)
CUSTOM_CORE_URL=$(uci get clashoo.config.core_download_url 2>/dev/null)
SINGBOX_STABLE_TARGET="${CLASHOO_SINGBOX_STABLE_TARGET:-/usr/share/clashoo/bin/sing-box-stable}"
SINGBOX_ALPHA_TARGET="${CLASHOO_SINGBOX_ALPHA_TARGET:-/usr/share/clashoo/bin/sing-box-alpha}"
SINGBOX_TMP_TEMPLATE="${CLASHOO_SINGBOX_TMP_TEMPLATE:-/tmp/clashoo-singbox.XXXXXX}"
CORE_DOWNLOAD_LOCK_DIR="${CLASHOO_CORE_DOWNLOAD_LOCK_DIR:-/var/lock/clashoo-core-download.lock}"
CORE_DOWNLOAD_LOCK_HELD=0
CORE_INSTALLED=0
CONNECT_TIMEOUT=15
REQUEST_TIMEOUT=30
@@ -24,6 +29,64 @@ write_log() {
echo " $(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$LOG_FILE"
}
core_download_pid_alive() {
local pid
pid="$1"
[ -d "/proc/$pid" ] && return 0
kill -0 "$pid" >/dev/null 2>&1
}
acquire_core_download_lock() {
local owner parent
parent="$(dirname "$CORE_DOWNLOAD_LOCK_DIR")"
mkdir -p "$parent" >/dev/null 2>&1 || return 1
while :; do
if mkdir "$CORE_DOWNLOAD_LOCK_DIR" 2>/dev/null; then
printf '%s\n' "$$" > "$CORE_DOWNLOAD_LOCK_DIR/pid" || {
rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1 || true
return 1
}
CORE_DOWNLOAD_LOCK_HELD=1
return 0
fi
if [ ! -e "$CORE_DOWNLOAD_LOCK_DIR/pid" ]; then
# A fresh owner creates the directory before writing its PID. Only
# remove an actually empty directory; otherwise treat it as busy.
if rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1; then
continue
fi
return 1
fi
owner="$(sed -n '1p' "$CORE_DOWNLOAD_LOCK_DIR/pid" 2>/dev/null)"
case "$owner" in
'' ) return 1 ;;
*[!0-9]*)
rm -f "$CORE_DOWNLOAD_LOCK_DIR/pid" >/dev/null 2>&1 || return 1
rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1 || return 1
;;
*)
if core_download_pid_alive "$owner"; then
return 1
fi
rm -f "$CORE_DOWNLOAD_LOCK_DIR/pid" >/dev/null 2>&1 || return 1
rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1 || return 1
;;
esac
done
}
release_core_download_lock() {
local owner
[ "$CORE_DOWNLOAD_LOCK_HELD" = "1" ] || return 0
owner="$(sed -n '1p' "$CORE_DOWNLOAD_LOCK_DIR/pid" 2>/dev/null)"
if [ "$owner" = "$$" ]; then
rm -f "$CORE_DOWNLOAD_LOCK_DIR/pid" >/dev/null 2>&1
rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1 || true
fi
CORE_DOWNLOAD_LOCK_HELD=0
}
finalize() {
if [ "$CORE_INSTALLED" = "1" ]; then
if [ -n "$TARGET_DCORE" ]; then
@@ -55,6 +118,7 @@ finalize() {
fi
fi
rm -f /var/run/core_update >/dev/null 2>&1
release_core_download_lock
}
timed_out() {
@@ -112,7 +176,7 @@ fetch_url_try() {
return $?
fi
if command -v wget >/dev/null 2>&1; then
wget -qO- --timeout="$CONNECT_TIMEOUT" --no-check-certificate --user-agent="Clash/OpenWRT" "$url"
wget -qO- --timeout="$CONNECT_TIMEOUT" --user-agent="Clash/OpenWRT" "$url"
return $?
fi
return 127
@@ -120,8 +184,12 @@ fetch_url_try() {
# Route core downloads through the running core in kernel-only mode (shared
# logic in proxy_lib.sh: handles smart core + sing-box's own profile port).
. /usr/share/clashoo/update/proxy_lib.sh
detect_proxy() { clashoo_detect_proxy; }
if [ "${CLASHOO_CORE_DOWNLOAD_LIB_ONLY:-0}" = "1" ]; then
detect_proxy() { :; }
else
. /usr/share/clashoo/update/proxy_lib.sh
detect_proxy() { clashoo_detect_proxy; }
fi
download_file_try() {
url="$1"
@@ -136,7 +204,7 @@ download_file_try() {
return $?
fi
if command -v wget >/dev/null 2>&1; then
wget -q --timeout="$REQUEST_TIMEOUT" --tries=1 --no-check-certificate --user-agent="Clash/OpenWRT" "$url" -O "$out"
wget -q --timeout="$REQUEST_TIMEOUT" --user-agent="Clash/OpenWRT" "$url" -O "$out"
return $?
fi
return 127
@@ -148,7 +216,7 @@ head_url_try() {
curl -fsSIL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" -A "Clash/OpenWRT" "$url" >/dev/null 2>&1
return $?
fi
wget -q --spider --timeout="$CONNECT_TIMEOUT" --no-check-certificate --user-agent="Clash/OpenWRT" "$url"
wget -q --spider --timeout="$CONNECT_TIMEOUT" --user-agent="Clash/OpenWRT" "$url"
}
map_mihomo_arch() {
@@ -323,60 +391,163 @@ detect_openwrt_arch() {
map_openwrt_arch "$MODELTYPE"
}
install_singbox_openwrt_asset() {
local pkg work rootdir
singbox_target_path() {
case "$CORETYPE" in
5) printf '%s\n' "$SINGBOX_ALPHA_TARGET" ;;
*) printf '%s\n' "$SINGBOX_STABLE_TARGET" ;;
esac
}
extract_tar_file() {
local archive dest
archive="$1"
dest="$2"
case "$archive" in
*.gz) tar -xzf "$archive" -C "$dest" >/dev/null 2>&1 ;;
*.xz) tar -xJf "$archive" -C "$dest" >/dev/null 2>&1 || tar -xf "$archive" -C "$dest" >/dev/null 2>&1 ;;
*.zst)
if command -v unzstd >/dev/null 2>&1; then
unzstd -c "$archive" 2>/dev/null | tar -xf - -C "$dest" >/dev/null 2>&1
elif command -v zstd >/dev/null 2>&1; then
zstd -dc "$archive" 2>/dev/null | tar -xf - -C "$dest" >/dev/null 2>&1
else
return 1
fi
;;
*) tar -xf "$archive" -C "$dest" >/dev/null 2>&1 || tar -xzf "$archive" -C "$dest" >/dev/null 2>&1 ;;
esac
}
extract_ar_data_tar() {
local archive dest offset name size member
archive="$1"
dest="$2"
offset=8
[ "$(dd if="$archive" bs=8 count=1 2>/dev/null)" = '!<arch>' ] || return 1
while :; do
# Read fixed-width ar fields directly. Do not put the full 60-byte
# header in command substitution: shell command substitution strips
# trailing newlines/spaces and makes length checks unreliable in ash.
name="$(dd if="$archive" bs=1 skip="$offset" count=16 2>/dev/null | sed 's/[[:space:]].*$//')"
name="${name%/}"
size="$(dd if="$archive" bs=1 skip=$((offset + 48)) count=10 2>/dev/null | tr -d '[:space:]')"
case "$size" in
''|*[!0-9]*) return 1 ;;
esac
member="$dest/$name"
case "$name" in
data.tar.*)
dd if="$archive" of="$member" bs=1 skip=$((offset + 60)) count="$size" 2>/dev/null || return 1
printf '%s\n' "$member"
return 0
;;
esac
offset=$((offset + 60 + size))
[ $((offset % 2)) -eq 0 ] || offset=$((offset + 1))
done
}
extract_singbox_payload() {
local pkg rootdir outer workdir payload data_tar candidate
pkg="$1"
work="/tmp/singbox-openwrt-pkg"
rootdir="/tmp/singbox-openwrt-root"
rm -rf "$work" "$rootdir" 2>/dev/null
mkdir -p "$work" "$rootdir"
tar -xzf "$pkg" -C "$work" >/dev/null 2>&1 || return 1
[ -f "$work/data.tar.gz" ] || return 1
tar -xzf "$work/data.tar.gz" -C "$rootdir" >/dev/null 2>&1 || return 1
[ -f "$rootdir/usr/bin/sing-box" ] || return 1
if ! install_with_rollback "$rootdir/usr/bin/sing-box" "/usr/bin/sing-box"; then
rootdir="$2"
outer="${rootdir}.outer"
workdir="$(dirname "$rootdir")"
payload="$workdir/sing-box.payload"
data_tar=""
rm -f "$payload" 2>/dev/null
rm -rf "$rootdir" "$outer" 2>/dev/null
if ! mkdir -p "$rootdir" "$outer"; then
rm -rf "$rootdir" "$outer" 2>/dev/null
return 1
fi
if [ -f "$rootdir/etc/init.d/sing-box" ]; then
cp -f "$rootdir/etc/init.d/sing-box" /etc/init.d/sing-box >/dev/null 2>&1 || true
chmod 755 /etc/init.d/sing-box >/dev/null 2>&1 || true
# IPK assets are ar/tar containers with a compressed data.tar.* member;
# official fallback archives contain sing-box directly below a version dir.
if tar -tf "$pkg" 2>/dev/null | grep -Eq '(^|/)data\.tar\.(gz|xz|zst)$'; then
extract_tar_file "$pkg" "$outer" || {
rm -rf "$rootdir" "$outer" 2>/dev/null
return 1
}
for candidate in "$outer"/data.tar.*; do
[ -f "$candidate" ] || continue
data_tar="$candidate"
break
done
if [ -z "$data_tar" ] || ! extract_tar_file "$data_tar" "$rootdir"; then
rm -rf "$rootdir" "$outer" 2>/dev/null
return 1
fi
else
data_tar="$(extract_ar_data_tar "$pkg" "$outer" 2>/dev/null || true)"
if [ -n "$data_tar" ]; then
if ! extract_tar_file "$data_tar" "$rootdir"; then
rm -rf "$rootdir" "$outer" 2>/dev/null
return 1
fi
else
if ! extract_tar_file "$pkg" "$rootdir"; then
rm -rf "$rootdir" "$outer" 2>/dev/null
return 1
fi
fi
fi
if [ -f "$rootdir/etc/config/sing-box" ] && [ ! -f /etc/config/sing-box ]; then
cp -f "$rootdir/etc/config/sing-box" /etc/config/sing-box >/dev/null 2>&1 || true
candidate="$(find "$rootdir" -type f -name sing-box 2>/dev/null | head -n 1)"
if [ -n "$candidate" ] && [ -f "$candidate" ]; then
if ! cp -f "$candidate" "$payload"; then
rm -rf "$rootdir" "$outer" 2>/dev/null
return 1
fi
rm -rf "$rootdir" "$outer" 2>/dev/null
printf '%s\n' "$payload"
return 0
fi
if [ -f "$rootdir/etc/sing-box/config.json" ] && [ ! -f /etc/sing-box/config.json ]; then
mkdir -p /etc/sing-box >/dev/null 2>&1
cp -f "$rootdir/etc/sing-box/config.json" /etc/sing-box/config.json >/dev/null 2>&1 || true
fi
rm -rf "$work" "$rootdir" >/dev/null 2>&1
return 0
rm -rf "$rootdir" "$outer" 2>/dev/null
return 1
}
install_singbox_openwrt_apk_asset() {
local pkg
cleanup_singbox_extract_workdir() {
local workdir rootdir
workdir="$1"
rootdir="$workdir/root"
rm -rf "$rootdir" "${rootdir}.outer" "$workdir" 2>/dev/null
}
install_singbox_openwrt_asset() {
local pkg target workdir rootdir bin extract_rc rc
pkg="$1"
command -v apk >/dev/null 2>&1 || return 1
apk add --allow-untrusted --force-overwrite "$pkg" >/dev/null 2>&1 || return 1
[ -x /usr/bin/sing-box ] || return 1
return 0
target="${2:-$(singbox_target_path)}"
workdir="$(mktemp -d "$SINGBOX_TMP_TEMPLATE" 2>/dev/null)" || return 1
rootdir="$workdir/root"
bin="$(extract_singbox_payload "$pkg" "$rootdir")"
extract_rc=$?
if [ "$extract_rc" -eq 0 ]; then
install_with_rollback "$bin" "$target" "${VER:-}"
rc=$?
else
rc=1
fi
cleanup_singbox_extract_workdir "$workdir"
return "$rc"
}
install_singbox_tar_asset() {
local archive bin
local archive target workdir rootdir bin extract_rc rc
archive="$1"
rm -rf /tmp/singbox-extract >/dev/null 2>&1
mkdir -p /tmp/singbox-extract
tar -xzf "$archive" -C /tmp/singbox-extract >/dev/null 2>&1 || return 1
bin="$(find /tmp/singbox-extract -name 'sing-box' -type f 2>/dev/null | head -n 1)"
[ -n "$bin" ] || return 1
install_with_rollback "$bin" "/usr/bin/sing-box"
target="${2:-$(singbox_target_path)}"
workdir="$(mktemp -d "$SINGBOX_TMP_TEMPLATE" 2>/dev/null)" || return 1
rootdir="$workdir/root"
bin="$(extract_singbox_payload "$archive" "$rootdir")"
extract_rc=$?
if [ "$extract_rc" -eq 0 ]; then
install_with_rollback "$bin" "$target" "${VER:-}"
rc=$?
else
rc=1
fi
cleanup_singbox_extract_workdir "$workdir"
return "$rc"
}
fetch_latest_tag() {
@@ -466,7 +637,7 @@ fetch_latest_tag() {
if command -v curl >/dev/null 2>&1; then
tag="$(curl -fsSIL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" -A "Clash/OpenWRT" "$u" 2>/dev/null | sed -n 's#^[Ll]ocation: .*/releases/tag/\([^[:space:]\r]*\).*#\1#p' | head -n 1)"
else
tag="$(wget -S --spider --timeout="$CONNECT_TIMEOUT" --no-check-certificate --user-agent="Clash/OpenWRT" "$u" 2>&1 | sed -n 's#^ Location: .*/releases/tag/\([^[:space:]]*\).*#\1#p' | head -n 1)"
tag="$(wget -S --spider --timeout="$CONNECT_TIMEOUT" --user-agent="Clash/OpenWRT" "$u" 2>&1 | sed -n 's#^ Location: .*/releases/tag/\([^[:space:]]*\).*#\1#p' | head -n 1)"
fi
case "$tag" in
v*[-]*)
@@ -513,7 +684,7 @@ fetch_latest_tag() {
if command -v curl >/dev/null 2>&1; then
tag="$(curl -fsSIL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" -A "Clash/OpenWRT" "$u" 2>/dev/null | sed -n 's#^[Ll]ocation: .*/releases/tag/\([^[:space:][:cntrl:]]*\).*#\1#p' | head -n 1)"
else
tag="$(wget -S --spider --timeout="$CONNECT_TIMEOUT" --no-check-certificate --user-agent="Clash/OpenWRT" "$u" 2>&1 | sed -n 's#^ Location: .*/releases/tag/\([^[:space:]]*\).*#\1#p' | head -n 1)"
tag="$(wget -S --spider --timeout="$CONNECT_TIMEOUT" --user-agent="Clash/OpenWRT" "$u" 2>&1 | sed -n 's#^ Location: .*/releases/tag/\([^[:space:]]*\).*#\1#p' | head -n 1)"
fi
[ -n "$tag" ] && { echo "$tag"; return 0; }
@@ -756,12 +927,25 @@ pick_mihomo_asset() {
}
install_binary() {
local src dst dir staged
src="$1"
dst="$2"
mkdir -p "$(dirname "$dst")"
rm -f "$dst"
mv "$src" "$dst"
chmod 755 "$dst"
dir="$(dirname "$dst")"
staged="$dir/.$(basename "$dst").tmp.$$"
mkdir -p "$dir" || return 1
rm -f "$staged"
cp -f "$src" "$staged" || {
rm -f "$staged"
return 1
}
chmod 755 "$staged" || {
rm -f "$staged"
return 1
}
mv -f "$staged" "$dst" || {
rm -f "$staged"
return 1
}
}
backup_binary() {
@@ -785,6 +969,7 @@ restore_binary() {
}
verify_binary() {
local bin
bin="$1"
[ -x "$bin" ] || return 1
"$bin" -v >/dev/null 2>&1 && return 0
@@ -792,6 +977,18 @@ verify_binary() {
return 1
}
verify_binary_version() {
local bin expected output actual
bin="$1"
expected="$2"
[ -n "$expected" ] || return 0
output="$({ "$bin" version 2>/dev/null || "$bin" -v 2>/dev/null; } || true)"
[ -n "$output" ] || return 1
actual="$(printf '%s\n' "$output" | sed -n 's/^[[:space:]]*sing-box[[:space:]][[:space:]]*version[[:space:]][[:space:]]*\([^[:space:]][^[:space:]]*\).*/\1/p' | head -n 1)"
[ -n "$actual" ] || return 1
[ "$actual" = "$expected" ]
}
installed_version_matches() {
version_file="$1"
expected_version="$2"
@@ -801,11 +998,13 @@ installed_version_matches() {
}
install_with_rollback() {
local tmpfile target expected_version
tmpfile="$1"
target="$2"
expected_version="${3:-}"
chmod 755 "$tmpfile" 2>/dev/null
if ! verify_binary "$tmpfile"; then
if ! verify_binary "$tmpfile" || ! verify_binary_version "$tmpfile" "$expected_version"; then
write_log "新内核预热校验失败,保留当前内核"
rm -f "$tmpfile" 2>/dev/null
return 1
@@ -822,6 +1021,18 @@ install_with_rollback() {
return 0
}
# Unit tests source the extraction helpers without entering the normal UCI and
# network download path. This hook has no effect in production execution.
if [ "${CLASHOO_CORE_DOWNLOAD_LIB_ONLY:-0}" = "1" ]; then
return 0 2>/dev/null || exit 0
fi
if ! acquire_core_download_lock; then
write_log "已有内核下载任务运行,拒绝并发执行"
exit 1
fi
trap finalize EXIT
# UCI template defaults to amd64-compatible. Auto-detect when MODELTYPE
# is missing, or when MODELTYPE is an amd64 variant but the device is not.
if [ -z "$MODELTYPE" ]; then
@@ -844,7 +1055,6 @@ fi
rm -f /tmp/clash.gz /tmp/clash /usr/share/clashoo/core_down_complete 2>/dev/null
touch /var/run/core_update 2>/dev/null
trap finalize EXIT
write_log "内核下载任务启动"
write_log "下载架构:${MODELTYPE:-未设置}"
@@ -898,28 +1108,17 @@ if [ "$CORETYPE" = "4" ] || [ "$CORETYPE" = "5" ]; then
[ -z "$TAG" ] && write_log "获取 sing-box 预发布版版本号失败" && exit 1
fi
VER="${TAG#v}"
SINGBOX_TARGET="$(singbox_target_path)"
installed=0
if [ -n "$OPENWRT_ARCH" ]; then
if command -v apk >/dev/null 2>&1; then
ASSET="sing-box_${VER}_openwrt_${OPENWRT_ARCH}.apk"
URL="https://github.com/SagerNet/sing-box/releases/download/${TAG}/${ASSET}"
write_log "优先尝试 OpenWrt APK 包: ${ASSET}"
if download_with_mirrors "$URL" /tmp/singbox-openwrt.apk raw && install_singbox_openwrt_apk_asset /tmp/singbox-openwrt.apk; then
installed=1
else
write_log "OpenWrt APK 包安装失败,尝试 OpenWrt IPK 包"
fi
fi
if [ "$installed" -ne 1 ]; then
ASSET="sing-box_${VER}_openwrt_${OPENWRT_ARCH}.ipk"
URL="https://github.com/SagerNet/sing-box/releases/download/${TAG}/${ASSET}"
write_log "尝试 OpenWrt IPK 包: ${ASSET}"
if download_with_mirrors "$URL" /tmp/singbox-openwrt.ipk raw && install_singbox_openwrt_asset /tmp/singbox-openwrt.ipk; then
installed=1
else
write_log "OpenWrt IPK 包安装失败,尝试 tar 包兜底"
fi
ASSET="sing-box_${VER}_openwrt_${OPENWRT_ARCH}.ipk"
URL="https://github.com/SagerNet/sing-box/releases/download/${TAG}/${ASSET}"
write_log "尝试提取 OpenWrt IPK 包: ${ASSET}"
if download_with_mirrors "$URL" /tmp/singbox-openwrt.ipk raw && install_singbox_openwrt_asset /tmp/singbox-openwrt.ipk "$SINGBOX_TARGET"; then
installed=1
else
write_log "OpenWrt IPK 提取失败,尝试官方 musl tar 包"
fi
fi
@@ -927,7 +1126,7 @@ if [ "$CORETYPE" = "4" ] || [ "$CORETYPE" = "5" ]; then
for ASSET in "sing-box-${VER}-linux-${SINGBOX_ARCH}-musl.tar.gz" "sing-box-${VER}-linux-${SINGBOX_ARCH}.tar.gz"; do
URL="https://github.com/SagerNet/sing-box/releases/download/${TAG}/${ASSET}"
write_log "尝试下载 sing-box 归档包: ${ASSET}"
if download_with_mirrors "$URL" /tmp/singbox.tar.gz gzip && install_singbox_tar_asset /tmp/singbox.tar.gz; then
if download_with_mirrors "$URL" /tmp/singbox.tar.gz gzip && install_singbox_tar_asset /tmp/singbox.tar.gz "$SINGBOX_TARGET"; then
installed=1
break
fi
@@ -940,16 +1139,14 @@ if [ "$CORETYPE" = "4" ] || [ "$CORETYPE" = "5" ]; then
fi
CORE_INSTALLED=1
# 稳定版与 Alpha 分路径归档,避免互相覆盖;同时保留旧字段供老逻辑读
# 稳定版与 Alpha 分路径保存,避免互相覆盖;版本文件保持兼容。
if [ "$CORETYPE" = "4" ]; then
cp -f /usr/bin/sing-box /usr/bin/sing-box-stable 2>/dev/null
printf '%s\n' "$TAG" > "/usr/share/clashoo/singbox_stable_version"
else
cp -f /usr/bin/sing-box /usr/bin/sing-box-alpha 2>/dev/null
printf '%s\n' "$TAG" > "/usr/share/clashoo/singbox_alpha_version"
fi
printf '%s\n' "$TAG" > "/usr/share/clashoo/singbox_version"
rm -f /tmp/singbox.tar.gz /tmp/singbox-openwrt.ipk /tmp/singbox-openwrt.apk
rm -f /tmp/singbox.tar.gz /tmp/singbox-openwrt.ipk
rm -rf /tmp/singbox-extract /tmp/singbox-openwrt-pkg /tmp/singbox-openwrt-root
touch /usr/share/clashoo/core_down_complete
write_log "sing-box 更新完成: $TAG"
@@ -67,7 +67,7 @@ _fetch() {
fi
return $?
fi
wget -q --timeout=120 --no-check-certificate --user-agent="Clash/OpenWRT" "$url" -O "$target"
wget -q --timeout=120 --user-agent="Clash/OpenWRT" "$url" -O "$target"
}
# 多源拉取:本机代理 → 国内镜像(gh-proxy / ghfast / jsdelivr)→ 原 URL 兜底
@@ -22,7 +22,7 @@ if command -v curl >/dev/null 2>&1; then
curl -fL --connect-timeout 15 --max-time 120 -A "Clash/OpenWRT" "$DOWNLOAD_URL" -o "$TMP_PATH" 2>/dev/null
dl_rc=$?
elif command -v wget >/dev/null 2>&1; then
wget -q --timeout=30 --tries=2 --no-check-certificate -U "Clash/OpenWRT" "$DOWNLOAD_URL" -O "$TMP_PATH" 2>/dev/null
wget -q --timeout=30 -U "Clash/OpenWRT" "$DOWNLOAD_URL" -O "$TMP_PATH" 2>/dev/null
dl_rc=$?
else
log "No curl or wget found"
@@ -79,13 +79,29 @@ download_zip() {
--proxy "$proxy" -A "Clash/OpenWRT" "$url" -o "$ZIP_FILE"; then
return 0
fi
if wget -q --timeout=60 --no-check-certificate --user-agent="Clash/OpenWRT" "$url" -O "$ZIP_FILE"; then
if wget -q --timeout=60 --user-agent="Clash/OpenWRT" "$url" -O "$ZIP_FILE"; then
return 0
fi
done
return 1
}
validate_zip_entries() {
entries="$TMP_ROOT/entries.txt"
if command -v unzip >/dev/null 2>&1; then
unzip -Z1 "$ZIP_FILE" >"$entries" 2>/dev/null || return 1
elif command -v bsdtar >/dev/null 2>&1; then
bsdtar -tf "$ZIP_FILE" >"$entries" 2>/dev/null || return 1
else
return 1
fi
[ -s "$entries" ] || return 1
awk '
/^\// || /^[A-Za-z]:/ || /(^|\/)\.\.(\/|$)/ || /\\/ || /\r/ { bad = 1 }
END { exit bad }
' "$entries"
}
extract_zip() {
if command -v unzip >/dev/null 2>&1; then
unzip -oq "$ZIP_FILE" -d "$UNPACK_DIR" >/dev/null 2>&1
@@ -141,6 +157,12 @@ if ! download_zip; then
exit 1
fi
if ! validate_zip_entries; then
set_state "error" "压缩包路径无效"
log_msg "Dashboard panel archive has unsafe entries" "面板压缩包含不安全路径"
exit 1
fi
extract_zip
extract_rc=$?
if [ "$extract_rc" -ne 0 ]; then
@@ -154,6 +176,12 @@ if [ "$extract_rc" -ne 0 ]; then
exit 1
fi
if find "$UNPACK_DIR" ! -type d ! -type f | grep -q .; then
set_state "error" "压缩包文件类型无效"
log_msg "Dashboard panel archive has unsafe file types" "面板压缩包含不安全文件类型"
exit 1
fi
SRC_DIR="$(find_web_root)"
if [ -z "$SRC_DIR" ] || [ ! -f "$SRC_DIR/index.html" ]; then
set_state "error" "文件无效"
@@ -1,4 +1,8 @@
# shellcheck shell=sh
# Clashoo's sing-box is private; HomeProxy owns the public system binary.
CLASHOO_SINGBOX_BIN_DIR="/usr/share/clashoo/bin"
CLASHOO_SINGBOX_RUNTIME_CONFIG="/var/run/clashoo/sing-box/config.json"
# Shared proxy detection for clashoo's own outbound requests.
#
# clashoo's self-initiated downloads (update checks, component/panel/core/geoip
@@ -16,7 +20,7 @@
# Port source depends on the running Clashoo kernel. The liveness gate uses the
# procd owner instead of a binary name, because other proxy plugins may run the
# same binaries:
# sing-box -> /etc/sing-box/config.json mixed inbound
# sing-box -> private runtime config mixed inbound
# (an imported profile may carry its own port)
# mihomo / clash-meta / smart -> /etc/clashoo/config.yaml mixed-port
# (a custom config's port may differ from uci)
@@ -25,11 +29,8 @@ clashoo_running_core_type() {
_cdp_type="$(uci -q get clashoo.config.core_type 2>/dev/null)"
case "$_cdp_type" in
singbox)
# Clashoo owns this service only after it has configured the
# packaged sing-box init script for its own runtime config.
[ "$(uci -q get sing-box.main.conffile 2>/dev/null)" = "/etc/sing-box/config.json" ] || return 1
_cdp_service="sing-box"
_cdp_filter='@["sing-box"].instances.*.pid'
_cdp_service="clashoo"
_cdp_filter='@.clashoo.instances.singbox.pid'
;;
*)
_cdp_type="mihomo"
@@ -41,6 +42,20 @@ clashoo_running_core_type() {
_cdp_pid="$(ubus call service list "{\"name\":\"$_cdp_service\",\"verbose\":true}" 2>/dev/null \
| jsonfilter -e "$_cdp_filter" 2>/dev/null | head -n1)"
[ -n "$_cdp_pid" ] && [ -d "/proc/$_cdp_pid" ] || return 1
if [ "$_cdp_type" = "singbox" ]; then
local _cdp_exe _cdp_cmdline
_cdp_exe="$(readlink -f "/proc/$_cdp_pid/exe" 2>/dev/null)"
case "$_cdp_exe" in
"$CLASHOO_SINGBOX_BIN_DIR/sing-box-stable"|"$CLASHOO_SINGBOX_BIN_DIR/sing-box-alpha") ;;
*)
_cdp_cmdline="$(tr '\000' ' ' < "/proc/$_cdp_pid/cmdline" 2>/dev/null)"
case "$_cdp_cmdline" in
"$CLASHOO_SINGBOX_BIN_DIR/sing-box-stable"|"$CLASHOO_SINGBOX_BIN_DIR/sing-box-stable "*|"$CLASHOO_SINGBOX_BIN_DIR/sing-box-alpha"|"$CLASHOO_SINGBOX_BIN_DIR/sing-box-alpha "*) ;;
*) return 1 ;;
esac
;;
esac
fi
printf '%s' "$_cdp_type"
}
@@ -48,7 +63,7 @@ clashoo_detect_proxy() {
_cdp_type="$(clashoo_running_core_type)" || return 0
_cdp_port=""
if [ "$_cdp_type" = "singbox" ]; then
_cdp_port="$(jsonfilter -i /etc/sing-box/config.json \
_cdp_port="$(jsonfilter -i "$CLASHOO_SINGBOX_RUNTIME_CONFIG" \
-e '@.inbounds[@.type="mixed"].listen_port' 2>/dev/null | head -n 1)"
else
# prefer mixed-port (HTTP+SOCKS) over plain port; a single two-pattern
@@ -188,7 +188,7 @@ download_to() {
DOWNLOAD_ERROR="$(curl_error "$last_rc")"
return 1
fi
wget -q --tries=4 --timeout=20 --user-agent="$ua" "$url" -O "$out"
wget -q --timeout=20 --user-agent="$ua" "$url" -O "$out"
rc=$?
[ "$rc" -eq 0 ] && return 0
DOWNLOAD_ERROR="网络错误(wget rc=${rc})"
@@ -209,8 +209,7 @@ update_info() {
}
service_running() {
[ -n "${CLASHOO_SERVICE_CMD:-}" ] && return 0
"$SERVICE_CMD" status >/dev/null 2>&1
[ "$("$SERVICE_CMD" status 2>/dev/null)" = "running" ]
}
template_output_name() {
@@ -7,12 +7,13 @@ NFT_DIR="${NFT_DIR:-/usr/share/clashoo/nftables}"
TARGET_V4="${NFT_DIR}/geoip_cn.nft"
TARGET_V6="${NFT_DIR}/geoip6_cn.nft"
FW4_SCRIPT="${FW4_SCRIPT:-/usr/share/clashoo/net/fw4.sh}"
RUNTIME_STATE_FILE="${RUNTIME_STATE_FILE:-/tmp/clashoo/runtime_state}"
PROC_ROOT="${PROC_ROOT:-/proc}"
TMP_V4="/tmp/china_ip.txt.$$"
TMP_V6="/tmp/china_ipv6.txt.$$"
OUT_V4="/tmp/geoip_cn.nft.$$"
OUT_V6="/tmp/geoip6_cn.nft.$$"
SINGBOX_BIN_DIR="${SINGBOX_BIN_DIR:-/usr/share/clashoo/bin}"
TMP_ROOT="$(mktemp -d /tmp/clashoo_china_ip.XXXXXX)"
TMP_V4="$TMP_ROOT/china_ip.txt"
TMP_V6="$TMP_ROOT/china_ipv6.txt"
OUT_V4="$TMP_ROOT/geoip_cn.nft"
OUT_V6="$TMP_ROOT/geoip6_cn.nft"
log() {
printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOG_FILE"
@@ -30,14 +31,14 @@ download_with_fallback() {
local output="$2"
local ip
if curl -fsSL "$url" -o "$output"; then
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
return 0
fi
case "$url" in
https://ispip.clang.cn/*)
for ip in 182.247.248.127 103.220.64.183; do
if curl -fsSL --resolve "ispip.clang.cn:443:${ip}" "$url" -o "$output"; then
if curl -fsSL --connect-timeout 10 --max-time 120 --resolve "ispip.clang.cn:443:${ip}" "$url" -o "$output"; then
log "DNS 异常,已使用 --resolve(${ip}) 回源下载"
return 0
fi
@@ -49,7 +50,7 @@ download_with_fallback() {
}
cleanup() {
rm -f "$TMP_V4" "$TMP_V6" "$OUT_V4" "$OUT_V6"
rm -rf "$TMP_ROOT"
}
trap cleanup EXIT INT TERM
@@ -61,6 +62,21 @@ render_nft_set() {
local set_type="$4"
awk -v set_name="$set_name" -v set_type="$set_type" '
function valid_cidr(value, parts, octets, i, prefix) {
if (split(value, parts, "/") != 2 || parts[2] !~ /^[0-9]+$/)
return 0
prefix = parts[2] + 0
if (set_type == "ipv4_addr") {
if (prefix > 32 || split(parts[1], octets, ".") != 4)
return 0
for (i = 1; i <= 4; i++)
if (octets[i] !~ /^[0-9]+$/ || length(octets[i]) > 3 || octets[i] + 0 > 255)
return 0
return 1
}
return set_type == "ipv6_addr" && prefix <= 128 &&
parts[1] ~ /^[0-9A-Fa-f:]+$/ && index(parts[1], ":") > 0
}
BEGIN {
print "set " set_name " {"
print "\ttype " set_type ";"
@@ -73,6 +89,12 @@ render_nft_set() {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0)
if ($0 == "")
next
if (!valid_cidr($0)) {
if (!invalid)
printf "invalid CIDR at line %d\n", NR > "/dev/stderr"
invalid = 1
next
}
if (!first)
print ","
printf "\t\t%s", $0
@@ -83,8 +105,13 @@ render_nft_set() {
print ""
print "\t}"
print "}"
if (invalid || first)
exit 1
}
' "$source_file" > "$output_file"
' "$source_file" > "$output_file" || return 1
# nft catches malformed IPv6 forms and invalid network prefixes before replacement.
{ printf 'table inet clashoo_validate {\n'; cat "$output_file"; printf '\n}\n'; } |
nft -c -f - >/dev/null 2>&1
}
url4="$(uci -q get clashoo.config.china_ip_url 2>/dev/null || true)"
@@ -107,7 +134,10 @@ download_with_fallback "$url4" "$TMP_V4"
}
changed=0
render_nft_set "$TMP_V4" "$OUT_V4" clashoo_china ipv4_addr
if ! render_nft_set "$TMP_V4" "$OUT_V4" clashoo_china ipv4_addr; then
log '大陆 IPv4 白名单包含无效 CIDR,保留原文件'
exit 1
fi
if cmp -s "$OUT_V4" "$TARGET_V4"; then
log '大陆 IPv4 白名单内容无变化'
else
@@ -119,7 +149,10 @@ fi
if download_with_fallback "$url6" "$TMP_V6"; then
if [ -s "$TMP_V6" ]; then
render_nft_set "$TMP_V6" "$OUT_V6" clashoo_china6 ipv6_addr
if ! render_nft_set "$TMP_V6" "$OUT_V6" clashoo_china6 ipv6_addr; then
log '大陆 IPv6 白名单包含无效 CIDR,保留原文件'
exit 1
fi
if cmp -s "$OUT_V6" "$TARGET_V6"; then
log '大陆 IPv6 白名单内容无变化'
else
@@ -136,29 +169,43 @@ else
fi
instance_pid() {
ubus call service list "{\"name\":\"$1\",\"verbose\":true}" 2>/dev/null |
jsonfilter -e "@[\"$1\"].instances.*.pid" 2>/dev/null | head -n1
local service="$1"
local instance="${2:-}"
local filter
if [ -n "$instance" ]; then
filter="@.${service}.instances.${instance}.pid"
else
filter="@[\"${service}\"].instances.*.pid"
fi
ubus call service list "{\"name\":\"$service\",\"verbose\":true}" 2>/dev/null |
jsonfilter -e "$filter" 2>/dev/null | head -n1
}
pid_running() {
[ -n "$1" ] && [ -d "$PROC_ROOT/$1" ]
}
clashoo_manages_singbox() {
[ -r "$RUNTIME_STATE_FILE" ] || return 1
grep -qx 'configured_core=singbox' "$RUNTIME_STATE_FILE" 2>/dev/null || return 1
case "$(sed -n 's/^health_detail=//p' "$RUNTIME_STATE_FILE" 2>/dev/null | head -n1)" in
service_stopped|service_disabled|boot_disabled|preflight:*|start:*) return 1 ;;
singbox_pid_owns_private_binary() {
local pid="$1" exe cmdline
pid_running "$pid" || return 1
exe="$(readlink -f "$PROC_ROOT/$pid/exe" 2>/dev/null)"
case "$exe" in
"$SINGBOX_BIN_DIR/sing-box-stable"|"$SINGBOX_BIN_DIR/sing-box-alpha") return 0 ;;
esac
return 0
cmdline="$(tr '\000' ' ' < "$PROC_ROOT/$pid/cmdline" 2>/dev/null)"
case "$cmdline" in
"$SINGBOX_BIN_DIR/sing-box-stable"|"$SINGBOX_BIN_DIR/sing-box-stable "*|"$SINGBOX_BIN_DIR/sing-box-alpha"|"$SINGBOX_BIN_DIR/sing-box-alpha "*) return 0 ;;
esac
return 1
}
selected_core_running() {
local name pid
if [ "$(uci -q get clashoo.config.core_type 2>/dev/null)" = "singbox" ]; then
bool_enabled "$(uci -q get sing-box.main.enabled 2>/dev/null)" || return 1
clashoo_manages_singbox || return 1
name="sing-box"
name="clashoo"
pid="$(instance_pid "$name" singbox)"
singbox_pid_owns_private_binary "$pid"
return $?
else
name="clashoo"
fi
+4 -4
View File
@@ -5,13 +5,13 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=dae
PKG_VERSION:=2026.09.20
PKG_RELEASE:=3
PKG_VERSION:=2026.09.24
PKG_RELEASE:=2
PKG_SOURCE:=dae-src-2026.09.20-2a846ee5a7e4.tar.gz
PKG_SOURCE:=dae-src-2026.09.24-17135da7c881.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=2a846ee5a7e40f2548f261bc3a0a5e4b1abe0f8324fa147354f574f7c410202c
PKG_HASH:=17135da7c881800738cc63566160d4ee6d59a8a70ae5043b8005c5d189b05752
PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILE:=LICENSE
+1 -1
View File
@@ -20,7 +20,7 @@ start_service() {
local config_file
config_get config_file "config" "config_file" "/etc/dae/config.dae"
"$PROG" validate -c "$config_file" || return 1
DAE_LOCATION_ASSET="/usr/share/v2ray" "$PROG" validate -c "$config_file" || return 1
local log_maxbackups log_maxsize
config_get log_maxbackups "config" "log_maxbackups" "1"
+4 -4
View File
@@ -5,13 +5,13 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=daed
PKG_VERSION:=2026.09.20
PKG_RELEASE:=4
PKG_VERSION:=2026.09.24
PKG_RELEASE:=2
PKG_SOURCE:=daed-src-2026.09.20-d00211e69e94.tar.gz
PKG_SOURCE:=daed-src-2026.09.24-527bb166b1ae.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=d00211e69e94dba09b0f3ab67251b0dad880c7b90fbf55840d3abe576b4453c2
PKG_HASH:=527bb166b1aea6a54330ebbc8be5b0418baed74636b838aeaa78c17b70f2f996
PKG_LICENSE:=AGPL-3.0-only MIT
PKG_LICENSE_FILES:=LICENSE wing/LICENSE
@@ -1,23 +0,0 @@
From: kenzok8 <kenzok8@noreply>
Date: 2026-06-23
Subject: [PATCH] daed: detach BPF hooks when stopping from dashboard
Detach BPF hooks when the dashboard stops dae through EmptyConfig.
Reported: https://github.com/kenzok8/openwrt-daede/issues/13
---
--- a/dae/run.go
+++ b/dae/run.go
@@ -138,6 +138,13 @@
/* dae-wing start */
newConf := newReloadMsg.Config
/* dae-wing end */
+
+ // openwrt-daede: dashboard stop reloads to EmptyConfig; detach hooks.
+ if newConf == EmptyConfig {
+ if e := c.DetachBpfHooks(); e != nil {
+ log.Errorln("[Stop] DetachBpfHooks:", e)
+ }
+ }
// New logger.
oldLogOutput := log.Out
log = logrus.New()
@@ -1,18 +0,0 @@
From: kenzok8 <kenzok8@noreply>
Date: 2026-06-29
Subject: [PATCH] daed: use bounded probes for manual latency tests
Use the existing bounded probe for manual dashboard latency tests.
Reported: https://github.com/kenzok8/openwrt-daede/issues/20
---
--- a/graphql/service/node/latency_mutation_utils.go
+++ b/graphql/service/node/latency_mutation_utils.go
@@ -108,7 +108,7 @@ func testSingleNodeLatency(option *diale
}
defer d.Close()
- result, err := d.ProbeLatency()
+ result, err := d.ProbeLatencyFast()
if err != nil {
msg := err.Error()
resolver.MessageV = &msg
@@ -1,76 +1,80 @@
From: kenzok8 <kenzok8@noreply>
Date: 2026-09-23
Subject: [PATCH] daed: bound startup network wait on new wing
Return an error after 60 seconds and retry on the next generation.
---
diff --git a/dae/run.go b/dae/run.go
index d8f86c2..a0a665d 100644
index 7c9a5cc..7f00748 100644
--- a/dae/run.go
+++ b/dae/run.go
@@ -32,7 +32,8 @@ var ChReloadConfigs = make(chan *ReloadMessage)
var GracefullyExit = make(chan struct{})
var EmptyConfig *daeConfig.Config
var c *control.ControlPlane
@@ -19,6 +19,7 @@ import (
"github.com/daeuniverse/dae/control"
"github.com/daeuniverse/dae/pkg/config_parser"
"github.com/daeuniverse/dae/pkg/logger"
+ "github.com/daeuniverse/outbound/netproxy"
"github.com/daeuniverse/outbound/protocol/direct"
"github.com/mohae/deepcopy"
"github.com/sirupsen/logrus"
@@ -43,7 +44,8 @@ var EmptyConfig *daeConfig.Config
// active is the generation currently serving traffic. It is nil before the
// first generation is ready and while a reload has retired the old one.
var active atomic.Pointer[control.ControlPlane]
-var onceWaitingNetwork sync.Once
+var waitingNetworkMu sync.Mutex
+var networkReady bool
func init() {
sections, err := config_parser.Parse(`global{} routing{}`)
@@ -52,6 +53,19 @@ func ControlPlane() (*control.ControlPlane, error) {
@@ -64,6 +66,19 @@ func ControlPlane() (*control.ControlPlane, error) {
return c, nil
}
+func waitForNetwork(log *logrus.Logger) error {
+func waitForNetwork(log *logrus.Logger, directDialer netproxy.Dialer) error {
+ waitingNetworkMu.Lock()
+ defer waitingNetworkMu.Unlock()
+ if networkReady {
+ return nil
+ }
+ if err := WaitForNetwork(log); err != nil {
+ if err := WaitForNetwork(log, directDialer); err != nil {
+ return err
+ }
+ networkReady = true
+ return nil
+}
+
func Run(log *logrus.Logger, conf *daeConfig.Config, externGeoDataDirs []string, disableTimestamp bool, dry bool) (err error) {
defer close(GracefullyExit)
// Not really run dae.
@@ -221,6 +235,9 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c
// Deep copy to prevent modification.
conf = deepcopy.Copy(conf).(*daeConfig.Config)
+ if bpf == nil {
+ control.PurgeStaleTCFilters(log)
+ }
// Init Direct Dialers.
direct.InitDirectDialers(conf.Global.FallbackResolver)
@@ -228,9 +245,9 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c
// generation is one control plane with its listener. retired is set before
// the plane is torn down so a Serve error from a plane that is being replaced
// is not mistaken for the active datapath dying.
@@ -345,9 +360,9 @@ func newControlPlane(log *logrus.Logger, dnsCache map[string]*control.DnsCache,
if !conf.Global.DisableWaitingNetwork && len(conf.Global.WanInterface) > 0 {
// Wait for network for WAN ready.
- onceWaitingNetwork.Do(func() {
- WaitForNetwork(log)
- WaitForNetwork(log, directDialers.Symmetric)
- })
+ if err = waitForNetwork(log); err != nil {
+ if err = waitForNetwork(log, directDialers.Symmetric); err != nil {
+ return nil, err
+ }
}
/// Get subscription -> nodeList mapping.
diff --git a/dae/utils.go b/dae/utils.go
index 65965a2..dc43cbc 100644
index 2b4f6bf..93efb7e 100644
--- a/dae/utils.go
+++ b/dae/utils.go
@@ -94,8 +94,9 @@ func preprocessWanInterfaceAuto(params *daeConfig.Config) error {
return nil
}
-func WaitForNetwork(log *logrus.Logger) {
+func WaitForNetwork(log *logrus.Logger) error {
-func WaitForNetwork(log *logrus.Logger, directDialer netproxy.Dialer) {
+func WaitForNetwork(log *logrus.Logger, directDialer netproxy.Dialer) error {
epo := 5 * time.Second
+ deadline := time.Now().Add(60 * time.Second)
client := http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (c net.Conn, err error) {
@@ -115,6 +116,9 @@ func WaitForNetwork(log *logrus.Logger) {
@@ -114,6 +115,9 @@ func WaitForNetwork(log *logrus.Logger, directDialer netproxy.Dialer) {
}
log.Infoln("Waiting for network...")
for i := 0; ; i++ {
@@ -80,7 +84,7 @@ index 65965a2..dc43cbc 100644
resp, err := client.Get(cmd.CheckNetworkLinks[i%len(cmd.CheckNetworkLinks)])
if err != nil {
log.Debugln("CheckNetwork:", err)
@@ -134,4 +138,5 @@ func WaitForNetwork(log *logrus.Logger) {
@@ -133,4 +137,5 @@ func WaitForNetwork(log *logrus.Logger, directDialer netproxy.Dialer) {
time.Sleep(epo)
}
log.Infoln("Network online.")
@@ -1,22 +1,9 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kenzok8 <kenzok8@noreply>
Date: 2026-09-23
Subject: [PATCH] daed: strictly reconcile subscription updates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
订阅更新按节点链接精确对账,普通组里的失效节点删除,fixed 组固定节点转成独立节点保留。
数据库提交后按运行状态应用配置,补齐并发地址校验、导入失败回滚和 reload 超时恢复。
Reconcile nodes by link, preserve fixed-group nodes, and apply committed changes.
---
cmd/run.go | 6 +-
graphql/mutation.go | 9 +-
graphql/root_schema.go | 2 +-
graphql/service/config/mutation_utils.go | 59 ++++-
.../service/subscription/mutation_utils.go | 181 +++++++++++----
.../subscription/mutation_utils_test.go | 212 ++++++++++++++++++
6 files changed, 408 insertions(+), 61 deletions(-)
create mode 100644 graphql/service/subscription/mutation_utils_test.go
diff --git a/cmd/run.go b/cmd/run.go
index 7dce577..978f46c 100644
--- a/cmd/run.go
@@ -43,10 +30,10 @@ index 7dce577..978f46c 100644
}
diff --git a/graphql/mutation.go b/graphql/mutation.go
index fcbf440..adb747c 100644
index af065fd..d462ecc 100644
--- a/graphql/mutation.go
+++ b/graphql/mutation.go
@@ -256,14 +256,7 @@ func (r *MutationResolver) SelectConfig(args *struct {
@@ -256,16 +256,7 @@ func (r *MutationResolver) SelectConfig(args *struct {
func (r *MutationResolver) Run(args *struct {
Dry bool
}) (int32, error) {
@@ -56,7 +43,9 @@ index fcbf440..adb747c 100644
- tx.Rollback()
- return 0, err
- }
- tx.Commit()
- if err = tx.Commit().Error; err != nil {
- return 0, fmt.Errorf("commit run: %w", err)
- }
- return ret, nil
+ return config.Run(context.TODO(), args.Dry)
}
@@ -76,7 +65,7 @@ index 8b82db6..a96cace 100644
# updateSubscriptionLink is to update the subscription link without re-fetching nodes.
diff --git a/graphql/service/config/mutation_utils.go b/graphql/service/config/mutation_utils.go
index fd61554..feb94d3 100644
index e6bf9da..8ab756b 100644
--- a/graphql/service/config/mutation_utils.go
+++ b/graphql/service/config/mutation_utils.go
@@ -21,6 +21,7 @@ import (
@@ -163,7 +152,7 @@ index fd61554..feb94d3 100644
// Running -> false
diff --git a/graphql/service/subscription/mutation_utils.go b/graphql/service/subscription/mutation_utils.go
index e55e65b..e01a342 100644
index 7e6dc08..e87bf78 100644
--- a/graphql/service/subscription/mutation_utils.go
+++ b/graphql/service/subscription/mutation_utils.go
@@ -18,6 +18,7 @@ import (
@@ -174,7 +163,7 @@ index e55e65b..e01a342 100644
"github.com/daeuniverse/dae-wing/graphql/service/node"
"github.com/daeuniverse/dae/common/subscription"
"github.com/go-co-op/gocron"
@@ -171,8 +172,9 @@ func AutoUpdateVersionByIds(d *gorm.DB, ids []uint) (err error) {
@@ -175,8 +176,9 @@ func AutoUpdateVersionByIds(d *gorm.DB, ids []uint) (err error) {
}
var (
@@ -186,7 +175,7 @@ index e55e65b..e01a342 100644
)
func UpdateAll(ctx context.Context) {
@@ -258,68 +260,161 @@ func Update(ctx context.Context, _id graphql.ID) (r *Resolver, err error) {
@@ -262,68 +264,161 @@ func Update(ctx context.Context, _id graphql.ID) (r *Resolver, err error) {
return &Resolver{Subscription: m}, nil
}
@@ -212,7 +201,7 @@ index e55e65b..e01a342 100644
- tx := db.BeginTx(ctx)
- defer func() {
- if err == nil {
- tx.Commit()
- err = tx.Commit().Error
+ current := make(map[string]struct{}, len(existing))
+ var staleIds []uint
+ for _, n := range existing {
@@ -303,7 +292,7 @@ index e55e65b..e01a342 100644
}
- hasAnyCandidate := false
for _, r := range result {
- if r.Error == nil {
- if r.Error == nil || *r.Error == node.DuplicatedError.Error() {
- hasAnyCandidate = true
- break
+ if r.Error != nil {
@@ -388,7 +377,7 @@ index e55e65b..e01a342 100644
return &m, nil
}
@@ -399,6 +494,8 @@ func UpdateLink(ctx context.Context, _id graphql.ID, link string) (r *Resolver,
@@ -406,6 +501,8 @@ func UpdateLink(ctx context.Context, _id graphql.ID, link string) (r *Resolver,
if err != nil {
return nil, err
}
@@ -397,11 +386,11 @@ index e55e65b..e01a342 100644
tx := db.BeginTx(ctx)
defer func() {
diff --git a/graphql/service/subscription/mutation_utils_test.go b/graphql/service/subscription/mutation_utils_test.go
diff --git a/graphql/service/subscription/reconcile_patch_test.go b/graphql/service/subscription/reconcile_patch_test.go
new file mode 100644
index 0000000..18ff9b9
--- /dev/null
+++ b/graphql/service/subscription/mutation_utils_test.go
+++ b/graphql/service/subscription/reconcile_patch_test.go
@@ -0,0 +1,212 @@
+/*
+ * SPDX-License-Identifier: AGPL-3.0-only
@@ -1,44 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
Subject: [PATCH] daed: apply subscription cron changes without restart
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
改 cron 后先提交事务再重建调度器。
原来在事务里就去重建,调度器另开连接读到的还是旧表达式,必须重启 daed 才生效。
---
graphql/service/subscription/mutation_utils.go | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/graphql/service/subscription/mutation_utils.go b/graphql/service/subscription/mutation_utils.go
index e2aec32..4f6bcfc 100644
--- a/graphql/service/subscription/mutation_utils.go
+++ b/graphql/service/subscription/mutation_utils.go
@@ -561,10 +561,12 @@ func UpdateCron(ctx context.Context, _id graphql.ID, cronExp string, cronEnable
}
tx := db.BeginTx(ctx)
+ if tx.Error != nil {
+ return nil, tx.Error
+ }
+ committed := false
defer func() {
- if err == nil {
- tx.Commit()
- } else {
+ if !committed {
tx.Rollback()
}
}()
@@ -583,6 +585,10 @@ func UpdateCron(ctx context.Context, _id graphql.ID, cronExp string, cronEnable
}).Error; err != nil {
return nil, err
}
+ if err = tx.Commit().Error; err != nil {
+ return nil, err
+ }
+ committed = true
// Update scheduler
RemoveUpdateScheduler(id)
@@ -0,0 +1,28 @@
From: kenzok8 <kenzok8@noreply>
Date: 2026-09-23
Subject: [PATCH] daed: keep reload callbacks nonblocking
A timed-out request must not block the daemon when reload later finishes.
---
diff --git a/graphql/service/config/mutation_utils.go b/graphql/service/config/mutation_utils.go
index 5996eaa..64229e8 100644
--- a/graphql/service/config/mutation_utils.go
+++ b/graphql/service/config/mutation_utils.go
@@ -298,7 +298,7 @@ func ApplyIfRunning(ctx context.Context) error {
func runLocked(ctx context.Context, noLoad bool) (n int32, err error) {
//// Dry run.
if noLoad {
- ch := make(chan error)
+ ch := make(chan error, 1)
reloadMsg := &dae.ReloadMessage{
Config: dae.EmptyConfig,
Callback: ch,
@@ -523,7 +523,7 @@ func runLocked(ctx context.Context, noLoad bool) (n int32, err error) {
readTx = nil
/// Reload with current config.
- chReloadCallback := make(chan error)
+ chReloadCallback := make(chan error, 1)
reloadMsg := &dae.ReloadMessage{
Config: c,
Callback: chReloadCallback,
@@ -1,837 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kenzok8 <kenzok8@gmail.com>
Date: Sun, 20 Sep 2026 16:59:23 +0800
Subject: [PATCH] Adapt wing to new dae core
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
改动:适配新核心生命周期,并修复空配置停止和重载回滚。
原因:旧接口删除且新控制面交接流程已改变。
---
dae-core/config/marshal.go | 5 +
dae/run.go | 402 +++++++++++++-----
dae/run_handoff_test.go | 33 ++
dae/runtime.go | 6 +-
dae/utils.go | 9 +-
db/node_utils.go | 3 +-
graphql/service/config/mutation_utils.go | 9 +-
graphql/service/node/latency_cache.go | 2 +-
.../service/node/latency_mutation_utils.go | 59 ++-
graphql/service/routing/resolver.go | 2 +-
10 files changed, 415 insertions(+), 115 deletions(-)
create mode 100644 dae/run_handoff_test.go
diff --git a/dae-core/config/marshal.go b/dae-core/config/marshal.go
index f68006a..69a5325 100644
--- a/dae-core/config/marshal.go
+++ b/dae-core/config/marshal.go
@@ -42,6 +42,11 @@ type Marshaller struct {
buf bytes.Buffer
}
+// Bytes returns the serialized section buffer without exposing mutable state.
+func (m *Marshaller) Bytes() []byte {
+ return m.buf.Bytes()
+}
+
func (m *Marshaller) writeLine(depth int, line string) {
if depth < 0 {
depth = 0
diff --git a/dae/run.go b/dae/run.go
index a0a665d..96b579c 100644
--- a/dae/run.go
+++ b/dae/run.go
@@ -6,6 +6,8 @@
package dae
import (
+ "context"
+ "errors"
"fmt"
"net/netip"
"runtime"
@@ -28,6 +30,22 @@ type ReloadMessage struct {
Callback chan<- error
}
+func closeReloadResources(closers ...func() error) error {
+ var errs error
+ for _, closeResource := range closers {
+ if closeResource != nil {
+ errs = errors.Join(errs, closeResource())
+ }
+ }
+ return errs
+}
+
+func notifyReloadCallback(callback chan<- error, err error) {
+ if callback != nil {
+ callback <- err
+ }
+}
+
var ChReloadConfigs = make(chan *ReloadMessage)
var GracefullyExit = make(chan struct{})
var EmptyConfig *daeConfig.Config
@@ -77,88 +95,115 @@ func Run(log *logrus.Logger, conf *daeConfig.Config, externGeoDataDirs []string,
case nil:
break dryLoop
default:
- newConf.Callback <- nil
+ notifyReloadCallback(newConf.Callback, nil)
}
}
return nil
}
// New c.
- c, err = newControlPlane(log, nil, nil, conf, externGeoDataDirs)
+ processSessions := control.NewSessionManager(context.Background())
+ defer func() { _ = processSessions.Close() }()
+ c, err = newControlPlane(context.Background(), log, nil, nil, conf, externGeoDataDirs, false)
if err != nil {
return err
}
+ if err = c.AttachSessionManager(processSessions); err != nil {
+ _ = c.Close()
+ return err
+ }
+ datapathDetached := false
+ if conf == EmptyConfig {
+ if err = c.DetachBpfHooks(); err != nil {
+ _ = c.Close()
+ return fmt.Errorf("detach initial empty datapath: %w", err)
+ }
+ datapathDetached = true
+ }
- // Serve tproxy TCP/UDP server util signals.
var listener *control.Listener
+ if err = control.GetDaeNetns().With(func() error {
+ var listenErr error
+ listener, listenErr = c.Listen(conf.Global.TproxyPort)
+ if listenErr != nil {
+ return listenErr
+ }
+ return listener.ValidateCurrentNetns()
+ }); err != nil {
+ if listener != nil {
+ _ = listener.Close()
+ }
+ _ = c.Close()
+ return fmt.Errorf("listen in dae netns: %w", err)
+ }
+ readyChan := make(chan bool, 1)
+ serveDone := make(chan error, 1)
+ go func() { serveDone <- c.Serve(readyChan, listener) }()
+ if !<-readyChan {
+ serveErr := <-serveDone
+ _ = closeReloadResources(listener.Close, c.Close)
+ if serveErr == nil {
+ serveErr = fmt.Errorf("initial control plane was not ready")
+ }
+ return serveErr
+ }
+ log.Infoln("Ready")
go func() {
- readyChan := make(chan bool, 1)
- go func() {
- <-readyChan
- log.Infoln("Ready")
- }()
- control.GetDaeNetns().With(func() error {
- if listener, err = c.ListenAndServe(readyChan, conf.Global.TproxyPort); err != nil {
- log.Errorln("ListenAndServe:", err)
- }
- return err
- })
- // Exit
+ if serveErr := <-serveDone; serveErr != nil {
+ log.WithError(serveErr).Errorln("control plane serve stopped")
+ }
ChReloadConfigs <- nil
}()
reloading := false
- /* dae-wing start */
- var errReload error
- var chCallback chan<- error
- /* dae-wing end */
loop:
for newReloadMsg := range ChReloadConfigs {
switch newReloadMsg {
case nil:
- /* dae-wing start */
- // We will receive nil after control plane being Closed.
- // We'll judge if we are in a reloading.
- /* dae-wing end */
-
if reloading {
- if listener == nil {
- // Failed to listen. Exit.
- break loop
- }
- // Serve.
+ // The old generation's Serve loop reports its shutdown after the
+ // candidate has already taken over the cloned listener.
reloading = false
- log.Warnln("[Reload] Serve")
- readyChan := make(chan bool, 1)
- go func() {
- if err := c.Serve(readyChan, listener); err != nil {
- log.Errorln("ListenAndServe:", err)
- }
- // Exit
- ChReloadConfigs <- nil
- }()
- <-readyChan
- log.Warnln("[Reload] Finished")
- /* dae-wing start */
- chCallback <- errReload
- /* dae-wing end */
+ continue
} else {
- // Listening error.
break loop
}
default:
// Reload signal.
log.Warnln("[Reload] Received reload signal; prepare to reload")
- /* dae-wing start */
newConf := newReloadMsg.Config
- /* dae-wing end */
-
- // openwrt-daede: dashboard stop reloads to EmptyConfig; detach hooks.
- if newConf == EmptyConfig {
- if e := c.DetachBpfHooks(); e != nil {
- log.Errorln("[Stop] DetachBpfHooks:", e)
+ if datapathDetached && newConf == EmptyConfig {
+ notifyReloadCallback(newReloadMsg.Callback, nil)
+ continue
+ }
+ oldC := c
+ wasStopped := datapathDetached
+ failReload := func(cause, cleanupErr error) error {
+ cause = errors.Join(cause, cleanupErr)
+ if cleanupErr == nil && wasStopped {
+ if detachErr := oldC.DetachBpfHooks(); detachErr != nil {
+ cause = errors.Join(cause, fmt.Errorf("restore stopped datapath: %w", detachErr))
+ cleanupErr = detachErr
+ } else {
+ datapathDetached = true
+ }
}
+ notifyReloadCallback(newReloadMsg.Callback, cause)
+ if cleanupErr != nil {
+ return cause
+ }
+ return nil
}
+ if wasStopped {
+ if restoreErr := oldC.RestoreDatapathForReloadRollback(); restoreErr != nil {
+ if fatalErr := failReload(fmt.Errorf("prepare stopped datapath: %w", restoreErr), nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ datapathDetached = false
+ }
+
// New logger.
oldLogOutput := log.Out
log = logrus.New()
@@ -167,65 +212,223 @@ loop:
log.SetOutput(oldLogOutput) // FIXME: THIS IS A HACK.
logrus.SetOutput(oldLogOutput)
- // New control plane.
- obj := c.EjectBpf()
var dnsCache map[string]*control.DnsCache
if conf.Dns.IpVersionPrefer == newConf.Dns.IpVersionPrefer {
// Only keep dns cache when ip version preference not change.
dnsCache = c.CloneDnsCache()
}
- log.Warnln("[Reload] Load new control plane")
- newC, err := newControlPlane(log, obj, dnsCache, newConf, externGeoDataDirs)
+ log.Warnln("[Reload] Prepare new control plane")
+ freshState, err := c.SnapshotFreshDatapathState()
if err != nil {
- /* dae-wing start */
- errReload = err
- /* dae-wing end */
-
- log.WithFields(logrus.Fields{
- "err": err,
- }).Errorln("[Reload] Failed to reload; try to roll back configuration")
- // Load last config back.
- newC, err = newControlPlane(log, obj, dnsCache, conf, externGeoDataDirs)
- if err != nil {
- obj.Close()
- c.Close()
- log.WithFields(logrus.Fields{
- "err": err,
- }).Fatalln("[Reload] Failed to roll back configuration")
+ if fatalErr := failReload(err, nil); fatalErr != nil {
+ return fatalErr
}
- newConf = conf
- log.Errorln("[Reload] Last reload failed; rolled back configuration")
- } else {
- log.Warnln("[Reload] Stopped old control plane")
-
- /* dae-wing start */
- errReload = nil
- /* dae-wing end */
+ continue
}
+ newC, err := newControlPlane(context.Background(), log, freshState, dnsCache, newConf, externGeoDataDirs, true)
+ if err == nil {
+ err = newC.AttachSessionManager(processSessions)
+ }
+ if err != nil {
+ var cleanupErr error
+ if newC != nil {
+ cleanupErr = newC.Close()
+ }
+ if fatalErr := failReload(err, cleanupErr); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ stagedListener, err := listener.Clone()
+ if err != nil {
+ if fatalErr := failReload(fmt.Errorf("clone listener: %w", err), newC.Close()); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ if err = oldC.StopDNSListener(); err != nil {
+ cleanupErr := closeReloadResources(stagedListener.Close, newC.Close, oldC.RestartDNSListener)
+ if fatalErr := failReload(fmt.Errorf("stop old DNS listener: %w", err), cleanupErr); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ dnsStopped := true
+ readyChan := make(chan bool, 1)
+ serveDone := make(chan error, 1)
+ go func() { serveDone <- newC.Serve(readyChan, stagedListener) }()
+ ready := <-readyChan
+ if !ready {
+ serveErr := <-serveDone
+ if serveErr == nil {
+ serveErr = fmt.Errorf("prepared control plane was not ready")
+ }
+ cleanupErr := closeReloadResources(
+ stagedListener.Close,
+ newC.Close,
+ oldC.RestartDNSListener,
+ )
+ dnsStopped = false
+ if fatalErr := failReload(serveErr, cleanupErr); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ hookPrepared := false
+ provisionalOwner := false
+ hookFlipCommitted := false
+ hookSetAdopted := false
+ flowDatapathAdopted := false
+ rollbackCleanupFailed := false
+ rollbackCandidate := func(cause error) error {
+ var rollbackErr error
+ addRollbackErr := func(err error) {
+ if err != nil {
+ rollbackCleanupFailed = true
+ rollbackErr = errors.Join(rollbackErr, err)
+ }
+ }
+ if flowDatapathAdopted {
+ addRollbackErr(oldC.AdoptProcessFlowDatapath(newC))
+ }
+ if hookSetAdopted {
+ if ownerErr := newC.RestorePreparedTCHookSet(oldC); ownerErr != nil {
+ rollbackCleanupFailed = true
+ return errors.Join(cause, rollbackErr, fmt.Errorf("restore TC HookSet owner: %w", ownerErr))
+ }
+ }
+ if hookFlipCommitted {
+ addRollbackErr(newC.RollbackPreparedBpfHookFlip())
+ }
+ if hookPrepared && !hookSetAdopted && !hookFlipCommitted {
+ addRollbackErr(newC.ClearPreparedTCHookHandoff())
+ }
+ if provisionalOwner {
+ newC.UnregisterProvisionalRoutingEpochExecutionOwner()
+ }
+ addRollbackErr(newC.DetachBpfHooks())
+ addRollbackErr(stagedListener.Close())
+ addRollbackErr(newC.Close())
+ if dnsStopped {
+ addRollbackErr(oldC.RestartDNSListener())
+ dnsStopped = false
+ }
+ return errors.Join(cause, rollbackErr)
+ }
+ if err = newC.PrepareTCHookHandoff(oldC); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ hookPrepared = true
+ if err = newC.RegisterProvisionalRoutingEpochExecutionOwner(); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ provisionalOwner = true
+ if err = newC.CommitPreparedBpfHookFlip(); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ hookFlipCommitted = true
+ if err = newC.AdoptPreparedTCHookSet(oldC); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ hookSetAdopted = true
+ if err = newC.AdoptProcessFlowDatapath(oldC); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ flowDatapathAdopted = true
+ newC.UnregisterProvisionalRoutingEpochExecutionOwner()
+ provisionalOwner = false
+ if err = newC.FinalizePreparedTCHooks(); err != nil {
+ fatalErr := errors.Join(fmt.Errorf("finalize prepared TC hooks: %w", err), closeReloadResources(
+ listener.Close,
+ stagedListener.Close,
+ newC.Close,
+ oldC.Close,
+ ))
+ notifyReloadCallback(newReloadMsg.Callback, fatalErr)
+ return fatalErr
+ }
+ hookPrepared = false
+ hookFlipCommitted = false
+ hookSetAdopted = false
- // Inject bpf objects into the new control plane life-cycle.
- newC.InjectBpf(obj)
-
- // Prepare new context.
- oldC := c
+ oldListener := listener
c = newC
conf = newConf
+ listener = stagedListener
+ datapathDetached = false
+ retireErr := closeReloadResources(oldListener.Close, oldC.Close)
+ if newConf == EmptyConfig {
+ if retireErr != nil {
+ fatalErr := errors.Join(fmt.Errorf("retire previous control plane: %w", retireErr), closeReloadResources(stagedListener.Close, newC.Close))
+ notifyReloadCallback(newReloadMsg.Callback, fatalErr)
+ return fatalErr
+ }
+ if detachErr := newC.DetachBpfHooks(); detachErr != nil {
+ fatalErr := errors.Join(fmt.Errorf("detach stopped datapath: %w", detachErr), closeReloadResources(stagedListener.Close, newC.Close))
+ notifyReloadCallback(newReloadMsg.Callback, fatalErr)
+ return fatalErr
+ }
+ datapathDetached = true
+ } else if retireErr != nil {
+ log.WithError(retireErr).Warnln("[Reload] Failed to close old control plane")
+ }
reloading = true
- /* dae-wing start */
- chCallback = newReloadMsg.Callback
- /* dae-wing end */
-
- // Ready to close.
- oldC.Close()
+ notifyReloadCallback(newReloadMsg.Callback, nil)
+ go func(done <-chan error) {
+ if serveErr := <-done; serveErr != nil {
+ log.WithError(serveErr).Errorln("control plane serve stopped")
+ }
+ ChReloadConfigs <- nil
+ }(serveDone)
}
}
- if e := c.Close(); e != nil {
+ if e := closeReloadResources(listener.Close, c.Close); e != nil {
return fmt.Errorf("close control plane: %w", e)
}
return nil
}
-func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*control.DnsCache, conf *daeConfig.Config, externGeoDataDirs []string) (c *control.ControlPlane, err error) {
+func newControlPlane(ctx context.Context, log *logrus.Logger, bpf interface{}, dnsCache map[string]*control.DnsCache, conf *daeConfig.Config, externGeoDataDirs []string, prepared bool) (c *control.ControlPlane, err error) {
// Print configuration.
if log.IsLevelEnabled(logrus.DebugLevel) {
@@ -239,10 +442,6 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c
control.PurgeStaleTCFilters(log)
}
- // Init Direct Dialers.
- direct.InitDirectDialers(conf.Global.FallbackResolver)
- netutils.FallbackDns = netip.MustParseAddrPort(conf.Global.FallbackResolver)
-
if !conf.Global.DisableWaitingNetwork && len(conf.Global.WanInterface) > 0 {
// Wait for network for WAN ready.
if err = waitForNetwork(log); err != nil {
@@ -266,7 +465,10 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c
}
// New dae control plane.
- c, err = control.NewControlPlane(
+ directDialers := direct.NewDirectDialers(conf.Global.FallbackResolver)
+ systemDNSResolver := netutils.NewSystemDNSResolver(netip.MustParseAddrPort(conf.Global.FallbackResolver))
+ c, err = control.NewControlPlaneWithContextOptions(
+ ctx,
log,
bpf,
dnsCache,
@@ -276,6 +478,14 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c
&conf.Global,
&conf.Dns,
externGeoDataDirs,
+ control.ControlPlaneBuildOptions{
+ DelayDatapathCommit: prepared,
+ DelayDNSListenerStart: prepared,
+ IsReload: prepared || bpf != nil,
+ DirectDialer: directDialers.Symmetric,
+ FullconeDirectDialer: directDialers.Fullcone,
+ SystemDNSResolver: systemDNSResolver,
+ },
)
if err != nil {
return nil, err
diff --git a/dae/run_handoff_test.go b/dae/run_handoff_test.go
new file mode 100644
index 0000000..0ffb2ec
--- /dev/null
+++ b/dae/run_handoff_test.go
@@ -0,0 +1,33 @@
+package dae
+
+import (
+ "errors"
+ "reflect"
+ "testing"
+)
+
+func TestCloseReloadResourcesOrder(t *testing.T) {
+ var got []string
+ err := closeReloadResources(
+ func() error { got = append(got, "old-listener"); return nil },
+ func() error { got = append(got, "staged-listener"); return nil },
+ func() error { got = append(got, "new-control-plane"); return nil },
+ func() error { got = append(got, "old-control-plane"); return nil },
+ )
+ if err != nil {
+ t.Fatalf("closeReloadResources() error = %v", err)
+ }
+ want := []string{"old-listener", "staged-listener", "new-control-plane", "old-control-plane"}
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("close order = %v, want %v", got, want)
+ }
+}
+
+func TestCloseReloadResourcesJoinsErrors(t *testing.T) {
+ one := errors.New("old listener")
+ two := errors.New("old control plane")
+ err := closeReloadResources(func() error { return one }, func() error { return two })
+ if !errors.Is(err, one) || !errors.Is(err, two) {
+ t.Fatalf("joined error = %v, want both close errors", err)
+ }
+}
diff --git a/dae/runtime.go b/dae/runtime.go
index 2ab0d68..af46089 100644
--- a/dae/runtime.go
+++ b/dae/runtime.go
@@ -30,18 +30,16 @@ type RuntimeOverview struct {
}
func GetRuntimeOverview(windowSec int, maxPoints int) (*RuntimeOverview, error) {
- activeTCPConnections := 0
ctl, err := ControlPlane()
+ var snapshot control.RuntimeStatsSnapshot
if err != nil {
if !errors.Is(err, ErrControlPlaneNotInit) {
return nil, err
}
} else {
- activeTCPConnections = ctl.ActiveTCPConnections()
+ snapshot = ctl.SnapshotRuntimeStats(windowSec, maxPoints)
}
- snapshot := control.SnapshotRuntimeStats(activeTCPConnections, control.DefaultUdpEndpointPool.Count(), windowSec, maxPoints)
-
samples := make([]RuntimeTrafficSample, 0, len(snapshot.Samples))
for _, sample := range snapshot.Samples {
samples = append(samples, RuntimeTrafficSample{
diff --git a/dae/utils.go b/dae/utils.go
index dc43cbc..ca61c85 100644
--- a/dae/utils.go
+++ b/dae/utils.go
@@ -32,8 +32,11 @@ var (
EmptyGlobalSection = `global {}`
)
-func NecessaryOutbounds(routing *daeConfig.Routing) (outbounds []string) {
- f := daeConfig.FunctionOrStringToFunction(routing.Fallback)
+func NecessaryOutbounds(routing *daeConfig.Routing) (outbounds []string, err error) {
+ f, err := daeConfig.ParseFunctionOrString(routing.Fallback)
+ if err != nil {
+ return nil, err
+ }
outbounds = append(outbounds, f.Name)
for _, r := range routing.Rules {
outbound := r.Outbound.Name
@@ -42,7 +45,7 @@ func NecessaryOutbounds(routing *daeConfig.Routing) (outbounds []string) {
}
outbounds = append(outbounds, outbound)
}
- return daeCommon.Deduplicate(outbounds)
+ return daeCommon.Deduplicate(outbounds), nil
}
func ParseConfig(globalSection *string, dnsSection *string, routingSection *string) (*daeConfig.Config, error) {
diff --git a/db/node_utils.go b/db/node_utils.go
index b7b43b8..7ca8fc3 100644
--- a/db/node_utils.go
+++ b/db/node_utils.go
@@ -6,6 +6,7 @@
package db
import (
+ "context"
"strings"
"github.com/daeuniverse/dae-wing/common"
@@ -25,7 +26,7 @@ func NewNodeModel(link string, tag *string, subscriptionId *uint) (*Node, error)
}
_tag = *tag
}
- d, err := dialer.NewFromLink(&dialer.GlobalOption{
+ d, err := dialer.NewFromLinkContext(context.Background(), &dialer.GlobalOption{
Log: logrus.StandardLogger(),
}, dialer.InstanceOption{DisableCheck: false}, link, _tag)
if err != nil {
diff --git a/graphql/service/config/mutation_utils.go b/graphql/service/config/mutation_utils.go
index 59f0ebd..c7d6b78 100644
--- a/graphql/service/config/mutation_utils.go
+++ b/graphql/service/config/mutation_utils.go
@@ -298,7 +298,7 @@ func ApplyIfRunning(ctx context.Context) error {
func runLocked(ctx context.Context, noLoad bool) (n int32, err error) {
//// Dry run.
if noLoad {
- ch := make(chan error)
+ ch := make(chan error, 1)
reloadMsg := &dae.ReloadMessage{
Config: dae.EmptyConfig,
Callback: ch,
@@ -384,7 +384,10 @@ func runLocked(ctx context.Context, noLoad bool) (n int32, err error) {
}
/// Fill in necessary groups and nodes.
// Find groups needed by routing.
- outbounds := dae.NecessaryOutbounds(&c.Routing)
+ outbounds, err := dae.NecessaryOutbounds(&c.Routing)
+ if err != nil {
+ return 0, err
+ }
var groups []db.Group
q = d.Model(&db.Group{}).
Where("name in ?", outbounds).
@@ -523,7 +526,7 @@ func runLocked(ctx context.Context, noLoad bool) (n int32, err error) {
readTx = nil
/// Reload with current config.
- chReloadCallback := make(chan error)
+ chReloadCallback := make(chan error, 1)
reloadMsg := &dae.ReloadMessage{
Config: c,
Callback: chReloadCallback,
diff --git a/graphql/service/node/latency_cache.go b/graphql/service/node/latency_cache.go
index ba66794..e0ec91e 100644
--- a/graphql/service/node/latency_cache.go
+++ b/graphql/service/node/latency_cache.go
@@ -162,7 +162,7 @@ func refreshLatencyCache(ctx context.Context) error {
return err
}
- results := testLatencyResultsForNodes(option, nodes)
+ results := testLatencyResultsForNodes(ctx, option, nodes)
storeLatencyResults(results)
if ctl, err := dae.ControlPlane(); err == nil {
diff --git a/graphql/service/node/latency_mutation_utils.go b/graphql/service/node/latency_mutation_utils.go
index 056f9fa..5026b33 100644
--- a/graphql/service/node/latency_mutation_utils.go
+++ b/graphql/service/node/latency_mutation_utils.go
@@ -8,6 +8,8 @@ package node
import (
"context"
"io"
+ "net/netip"
+ "strings"
"sync"
"time"
@@ -15,6 +17,7 @@ import (
"github.com/daeuniverse/dae-wing/dae"
"github.com/daeuniverse/dae-wing/db"
dialer "github.com/daeuniverse/dae/component/outbound/dialer"
+ "github.com/daeuniverse/outbound/netproxy"
"github.com/graph-gophers/graphql-go"
"github.com/sirupsen/logrus"
)
@@ -32,12 +35,12 @@ func TestLatencies(ctx context.Context, ids *[]graphql.ID) ([]*LatencyResolver,
return nil, err
}
- results := testLatencyResultsForNodes(option, nodes)
+ results := testLatencyResultsForNodes(ctx, option, nodes)
storeLatencyResults(results)
return results, nil
}
-func testLatencyResultsForNodes(option *dialer.GlobalOption, nodes []db.Node) []*LatencyResolver {
+func testLatencyResultsForNodes(ctx context.Context, option *dialer.GlobalOption, nodes []db.Node) []*LatencyResolver {
results := make([]*LatencyResolver, len(nodes))
sem := make(chan struct{}, latencyProbeConcurrency)
var wg sync.WaitGroup
@@ -51,7 +54,7 @@ func testLatencyResultsForNodes(option *dialer.GlobalOption, nodes []db.Node) []
defer func() { <-sem }()
node := nodes[index]
- results[index] = testSingleNodeLatency(option, &node)
+ results[index] = testSingleNodeLatency(ctx, option, &node)
}()
}
@@ -93,14 +96,14 @@ func latencyProbeNodes(ctx context.Context, ids *[]graphql.ID) ([]db.Node, error
return nodes, nil
}
-func testSingleNodeLatency(option *dialer.GlobalOption, node *db.Node) *LatencyResolver {
+func testSingleNodeLatency(ctx context.Context, option *dialer.GlobalOption, node *db.Node) *LatencyResolver {
resolver := &LatencyResolver{
NodeID: node.ID,
AliveVal: false,
TestedAtV: time.Now(),
}
- d, err := dialer.NewFromLink(option, dialer.InstanceOption{DisableCheck: false}, node.Link, "")
+ d, err := dialer.NewFromLinkContext(ctx, option, dialer.InstanceOption{DisableCheck: false}, node.Link, "")
if err != nil {
msg := err.Error()
resolver.MessageV = &msg
@@ -108,7 +111,7 @@ func testSingleNodeLatency(option *dialer.GlobalOption, node *db.Node) *LatencyR
}
defer d.Close()
- result, err := d.ProbeLatencyFast()
+ result, err := probeLatencyFast(ctx, d)
if err != nil {
msg := err.Error()
resolver.MessageV = &msg
@@ -128,3 +131,47 @@ func testSingleNodeLatency(option *dialer.GlobalOption, node *db.Node) *LatencyR
}
return resolver
}
+
+func probeLatencyFast(ctx context.Context, d *dialer.Dialer) (*dialer.LatencyProbeResult, error) {
+ opt, err := d.TcpCheckOptionRaw.Option()
+ if err != nil {
+ return nil, err
+ }
+ network, err := netproxy.ParseMagicNetwork(d.TcpCheckOptionRaw.ResolverNetwork)
+ if err != nil {
+ return nil, err
+ }
+ result := &dialer.LatencyProbeResult{CheckedAt: time.Now()}
+ var messages []string
+ for _, probe := range []struct {
+ index int
+ ip netip.Addr
+ }{
+ {index: dialer.IdxTcp4, ip: opt.Ip4},
+ {index: dialer.IdxTcp6, ip: opt.Ip6},
+ } {
+ if !probe.ip.IsValid() {
+ continue
+ }
+ probeCtx, cancel := context.WithTimeout(ctx, 1500*time.Millisecond)
+ started := time.Now()
+ ok, probeErr := d.HttpCheck(probeCtx, probe.index, opt.Url, probe.ip, opt.Method, network.Mark, network.Mptcp)
+ cancel()
+ if ok {
+ latency := time.Since(started)
+ if !result.Alive || latency < result.Latency {
+ result.Latency = latency
+ }
+ result.Alive = true
+ } else if probeErr != nil {
+ messages = append(messages, probeErr.Error())
+ }
+ }
+ if !result.Alive {
+ result.Message = strings.Join(messages, "; ")
+ if result.Message == "" {
+ result.Message = "no latency result"
+ }
+ }
+ return result, nil
+}
diff --git a/graphql/service/routing/resolver.go b/graphql/service/routing/resolver.go
index 7822d64..3ed61aa 100644
--- a/graphql/service/routing/resolver.go
+++ b/graphql/service/routing/resolver.go
@@ -41,7 +41,7 @@ func (r *Resolver) Selected() bool {
return r.Model.Selected
}
-func (r *Resolver) ReferenceGroups() (outbounds []string) {
+func (r *Resolver) ReferenceGroups() (outbounds []string, err error) {
return dae.NecessaryOutbounds(r.DaeRouting)
}
+2 -2
View File
@@ -1,12 +1,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=docker
PKG_VERSION:=29.8.1
PKG_VERSION:=29.9.0
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/docker/cli/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=55bcae5053f0914118d229658e2ac3a877dbdead6cb2c322e525d0c1e8bf78d2
PKG_HASH:=853af65a119a538d7402515ad8d8f907effc5abe805b0dd6631ad4b2ecf26d5b
PKG_BUILD_DIR:=$(BUILD_DIR)/cli-$(PKG_VERSION)
PKG_GIT_SHORT_COMMIT:=$(shell $(CURDIR)/git-short-commit.sh 'github.com/docker/cli' 'v$(PKG_VERSION)' '$(TMP_DIR)/git-short-commit/$(PKG_NAME)-$(PKG_VERSION)')
+2 -2
View File
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=dockerd
PKG_VERSION:=29.8.1
PKG_VERSION:=29.9.0
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
PKG_LICENSE_FILES:=LICENSE
@@ -10,7 +10,7 @@ PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_GIT_URL:=github.com/moby/moby
PKG_GIT_REF:=docker-v$(PKG_VERSION)
PKG_SOURCE_URL:=https://codeload.$(PKG_GIT_URL)/tar.gz/$(PKG_GIT_REF)?
PKG_HASH:=94be9d6940b613676335fc494e617b4acf98676435b3744f32649ed72114bd58
PKG_HASH:=ee76bfc375c4e498e2405a26b2b0792eb3dd999a96b9f521af16989c3b95e9ac
PKG_GIT_SHORT_COMMIT:=$(shell $(CURDIR)/git-short-commit.sh '$(PKG_GIT_URL)' '$(PKG_GIT_REF)' '$(TMP_DIR)/git-short-commit/$(PKG_NAME)-$(PKG_VERSION)')
PKG_MAINTAINER:=Gerard Ryan <G.M0N3Y.2503@gmail.com>
+3 -3
View File
@@ -9,9 +9,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
PKG_SOURCE_DATE:=2026-09-17
PKG_SOURCE_VERSION:=127dd2a6d582528d6d61c4d838dc17385ef31abd
PKG_MIRROR_HASH:=932ef98a571d8bef6b26aca792154d989863a5746e4ceded2d37c9190793d1a0
PKG_SOURCE_DATE:=2026-10-02
PKG_SOURCE_VERSION:=3fef1f00031be0d3505e5338ca671ea3752e6fd8
PKG_MIRROR_HASH:=d5bfd72bd48ec013df53d5457556261c02a18a3dd83e5786dcc3f3a367750feb
PKG_LICENSE:=BSD 3-Clause
PKG_LICENSE_FILES:=LICENSE
+2 -2
View File
@@ -3,7 +3,7 @@
#ifndef OUT_LAST_COMMIT_POSITION_H_
#define OUT_LAST_COMMIT_POSITION_H_
#define LAST_COMMIT_POSITION_NUM 2575
#define LAST_COMMIT_POSITION "2575 (127dd2a6d582)"
#define LAST_COMMIT_POSITION_NUM 2590
#define LAST_COMMIT_POSITION "2590 (3fef1f00031b)"
#endif // OUT_LAST_COMMIT_POSITION_H_
+104
View File
@@ -0,0 +1,104 @@
# SPDX-License-Identifier: GPL-2.0-only
include $(TOPDIR)/rules.mk
PKG_NAME:=honk
PKG_VERSION:=2026.10.9_pre3
PKG_RELEASE:=1
# Raw upstream release tag. It keeps the dots in qualifiers (e.g. v0.0.1.beta.49)
# because that is what the GitHub release asset name uses.
HONK_RELEASE_TAG:=debug.2026.10.9.native-api.3
HONK_ASSET_TAG:=debug
# SHA256 of each release tarball, kept in sync by scripts/update_honk_version.sh.
HONK_HASH_X86_64:=179dac725195f04899ae871b9fbcf47deec4b99f6b30c539296d31fdcd9a8c2e
HONK_HASH_AARCH64:=6bcf738b8c618d9c87a412fa3dbb976e041349f6614ebd1ea2c7044ede3a4d48
PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILE:=LICENSE
PKG_MAINTAINER:=luci-app-honk maintainers
ifeq ($(ARCH),x86_64)
HONK_TARGET:=x86_64-unknown-linux-musl
HONK_HASH:=$(HONK_HASH_X86_64)
else ifeq ($(ARCH),aarch64)
HONK_TARGET:=aarch64-unknown-linux-musl
HONK_HASH:=$(HONK_HASH_AARCH64)
endif
# Default to the stock-malloc build: lower RSS high-water on small gateways.
# Use an empty suffix to switch back to the mimalloc build (faster QUIC).
HONK_SUFFIX:=-stock
HONK_ASSET:=honk-core-$(HONK_ASSET_TAG)-$(HONK_TARGET)$(HONK_SUFFIX).tar.gz
PKG_SOURCE:=$(HONK_ASSET)
PKG_SOURCE_URL:=https://github.com/Glassyiris/honk/releases/download/$(HONK_RELEASE_TAG)/
PKG_HASH:=$(HONK_HASH)
include $(INCLUDE_DIR)/package.mk
define Package/honk
SECTION:=net
CATEGORY:=Network
SUBMENU:=Web Servers/Proxies
TITLE:=honk - eBPF transparent proxy engine (dae-compatible)
URL:=https://github.com/Glassyiris/honk
DEPENDS:=@(x86_64||aarch64) \
+ca-bundle +ip-full \
+kmod-sched-core +kmod-sched-bpf +kmod-veth +kmod-nft-queue \
+v2ray-geoip +v2ray-geosite \
+@KERNEL_XDP_SOCKETS \
+HONK_USE_VMLINUX_BTF:vmlinux-btf
endef
define Package/honk/config
choice
prompt "BTF source for CO-RE"
default HONK_USE_KERNEL_BTF
depends on PACKAGE_honk
config HONK_USE_KERNEL_BTF
bool "Use kernel BTF (integrated)"
depends on KERNEL_DEBUG_INFO_BTF
config HONK_USE_VMLINUX_BTF
bool "Use vmlinux-btf package"
endchoice
endef
define Package/honk/conffiles
/etc/honk/config.dae
/etc/honk/config.d/
/etc/honk/state/
/etc/config/honk
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(TAR) -xzf $(DL_DIR)/$(PKG_SOURCE) -C $(PKG_BUILD_DIR) --strip-components=1
endef
define Build/Compile
true
endef
define Package/honk/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/honk-core $(1)/usr/bin/honk-core
$(INSTALL_DIR) $(1)/etc/honk/config.d
$(INSTALL_CONF) $(CURDIR)/files/config.dae $(1)/etc/honk/config.dae
$(INSTALL_CONF) $(CURDIR)/files/config.d/node.dae $(1)/etc/honk/config.d/node.dae
$(INSTALL_CONF) $(CURDIR)/files/config.d/route.dae $(1)/etc/honk/config.d/route.dae
$(INSTALL_CONF) $(CURDIR)/files/config.d/dns.dae $(1)/etc/honk/config.d/dns.dae
$(INSTALL_CONF) $(CURDIR)/files/config.d/api.dae $(1)/etc/honk/config.d/api.dae
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) $(CURDIR)/files/honk.config $(1)/etc/config/honk
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) $(CURDIR)/files/honk.init $(1)/etc/init.d/honk
endef
$(eval $(call BuildPackage,honk))
View File
Whitespace-only changes.
+23
View File
@@ -0,0 +1,23 @@
# dns.dae
dns {
bind: 'tcp+udp://[::]:53530'
client_subnet: auto
#ipversion_prefer: 4
upstream {
local: 'udp://127.0.0.1:53' -> direct
remote: 'tcp+udp://one.one.one.one:53'
}
routing {
request {
qtype(https) -> reject
qname(geosite: gfw) -> remote
fallback: local
}
response {
upstream(remote) -> accept
qname(geosite: private) -> accept
ip(geoip: cn) -> accept
fallback: remote
}
}
}
+35
View File
@@ -0,0 +1,35 @@
# node.dae
node {
node1: 'xxx'
node2: 'xxx'
}
subscription {
# 短形式:默认 86400s 刷新一次
sub: 'http://sub'
# 短形式 + UA:仍然只能默认 86400s
sub_ua: 'http://sub'(honk/1.0 like)
# 块形式:可以自定义定时刷新周期
sub2: {
url: 'http://sub'
ua: 'honk/1.0'
interval: '10000s' # 每 10000 秒刷新一次
}
# 禁用定期刷新
sub3: {
url: 'http://sub'
interval: 0
}
}
group {
proxy {
filter: subtag(sub) && !name(keyword: 'ExpireAt:')
filter: name(node1, node2)
policy: score
}
}
+27
View File
@@ -0,0 +1,27 @@
# route.dae
routing {
pname(dnsmasq, zerotier-one) -> direct(must)
dip(224.0.0.0/3, 'ff00::/8', geoip: private) -> direct
domain(geosite: category-bank-cn, geosite: boc@!cn, geosite: synology) -> direct
# block quic, if u node has poor quic support, enable this line
# l4proto(udp) && dport(443) -> block
domain(geosite: spotify) -> proxy
domain(geosite: category-doh) -> proxy
domain(geosite: tiktok) -> proxy
domain(geosite: category-ai-!cn, geosite: github) -> proxy
domain(geosite: gfw) -> proxy
dip(geoip: telegram) -> proxy
dip(geoip: cn) -> direct
# geoip:cn always using chiness asn, can use domain rule to override
# domain(geosite: cn) -> direct
# using proxy for ssh, http, https, and common ports, avoid (bt, or other p2p)
# dport(22, 80, 443, 8080, 6443, 9090) -> proxy
# fallback: direct
fallback: proxy
}
+31
View File
@@ -0,0 +1,31 @@
# config.dae
# Load all honk files placed in ./config.d/.
include {
config.d/*.dae
}
global {
log_level: warn
check_interval: 600s
check_tolerance: 30ms
lan_interface: br-lan
wan_interface: auto
auto_config_kernel_parameter: true
disable_waiting_network: false
data_dir: '/etc/honk'
log_file: '/var/log/honk/honk.log'
store_subscribe: true
# if u not using dns bind, set dial_mode to domain++ prefer
dial_mode: domain
bootstrap_resolver: '223.5.5.5:53'
tcp_check_url: 'https://www.gstatic.com/generate_204'
tcp_check_http_method: HEAD
udp_check_dns: 'dns.google.com:53', '8.8.8.8', '2001:4860:4860::8888'
}
assets {
geodata {
geosite: 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat'
geoip: 'https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat'
}
}
+7
View File
@@ -0,0 +1,7 @@
config honk 'config'
option enabled '0'
option advanced '0'
option dashboard 'none'
option config_file '/etc/honk/config.dae'
+122
View File
@@ -0,0 +1,122 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=96
STOP=10
extra_command "hot_reload" "Hot-reload service"
CONF="honk"
PROG="/usr/bin/honk-core"
# honk-core hardcodes dae-compatible datapath names (dae0/dae0peer + daens);
# these are the real interface/netns names, not leftover dae config.
HONK_HOST_IFACE="dae0"
HONK_PEER_IFACE="dae0peer"
HONK_NETNS_NAME="daens"
HONK_NETNS_PATH="/run/netns/$HONK_NETNS_NAME"
RESOLV_HONK="/tmp/resolv.conf.honk"
set_chmod_640() {
mkdir -p /etc/honk/config.d /etc/honk/state /var/log/honk
chmod -R u=rwX,go= /etc/honk 2>/dev/null || true
chmod 700 /etc/honk /etc/honk/config.d /etc/honk/state 2>/dev/null || true
}
cleanup_netns() {
ip netns del "$HONK_NETNS_NAME" 2>/dev/null || true
umount "$HONK_NETNS_PATH" 2>/dev/null || true
rm -f "$HONK_NETNS_PATH" 2>/dev/null || true
ip link del "$HONK_HOST_IFACE" 2>/dev/null || true
ip link del "$HONK_PEER_IFACE" 2>/dev/null || true
}
hijack_resolv_conf() {
grep -q ' /tmp/resolv\.conf ' /proc/mounts && return 0
local dns_list WANS
. /lib/functions/network.sh
unset -v __NETWORK_CACHE
__network_ifstatus WANS "" "[@.route[@.target='0.0.0.0' && !@.table]].interface" 2>/dev/null
set -- $WANS
if [ $# -eq 1 ]; then
dns_list=$(ubus call "network.interface.$1" status 2>/dev/null | \
jsonfilter -e '@["dns-server"][*]' 2>/dev/null)
if [ -z "$dns_list" ] && [ -f "/tmp/resolv.conf.d/resolv.conf.auto" ]; then
dns_list=$(awk '/^nameserver/{print $2}' "/tmp/resolv.conf.d/resolv.conf.auto")
fi
fi
[ -z "$dns_list" ] && dns_list="119.29.29.29 223.5.5.5"
[ -f /tmp/resolv.conf ] || touch /tmp/resolv.conf
grep -E '^(search|domain) ' /tmp/resolv.conf > "$RESOLV_HONK" 2>/dev/null
for dns in $dns_list; do
echo "nameserver $dns" >> "$RESOLV_HONK"
done
mount --bind "$RESOLV_HONK" /tmp/resolv.conf
mount -o remount,ro,bind /tmp/resolv.conf
}
restore_resolv_conf() {
while grep -q ' /tmp/resolv\.conf ' /proc/mounts; do
mount -o remount,rw,bind /tmp/resolv.conf 2>/dev/null
umount /tmp/resolv.conf 2>/dev/null || break
done
rm -f "$RESOLV_HONK"
}
start_service() {
local enabled config_file
config_load "$CONF"
config_get_bool enabled "config" "enabled" "0"
[ "$enabled" -eq "1" ] || return 1
config_get config_file "config" "config_file" "/etc/honk/config.dae"
if [ ! -x "$PROG" ]; then
logger -t honk "not starting: $PROG is missing or not executable"
return 1
fi
set_chmod_640
cleanup_netns
hijack_resolv_conf
procd_open_instance "$CONF"
procd_set_param env DAE_LOCATION_ASSET="/usr/share/v2ray"
procd_set_param command "$PROG" --config "$config_file"
procd_set_param netdev "$HONK_HOST_IFACE" "$HONK_PEER_IFACE"
procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000"
procd_set_param term_timeout 8
procd_set_param respawn 3600 5 5
procd_set_param stderr 1
procd_close_instance
}
service_stopped() {
restore_resolv_conf
cleanup_netns
}
reload_service() {
stop
start
}
service_triggers() {
procd_add_reload_trigger "$CONF"
}
hot_reload() {
set_chmod_640
if /etc/init.d/honk running; then
"$PROG" reload
else
start
fi
}
+2 -2
View File
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=hysteria
PKG_VERSION:=2.12.3
PKG_VERSION:=2.13.0
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)?
PKG_HASH:=2679b76ab9cacbfd7574a48453325843865a347f1925858c4fb4fee3be132147
PKG_HASH:=dfde427a93a0dc5ff65ddc1e239df3b1def54bf33f8b137c571daa7b27ecddd0
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-app-v$(PKG_VERSION)
PKG_LICENSE:=MIT
+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector-mod-email
PKG_VERSION:=1.7.4
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
include $(INCLUDE_DIR)/package.mk
+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector-mod-modem-restart
PKG_VERSION:=1.7.4
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
include $(INCLUDE_DIR)/package.mk
+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector-mod-telegram
PKG_VERSION:=1.7.4
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
include $(INCLUDE_DIR)/package.mk
@@ -57,7 +57,7 @@ function httpRequest(url, proxyType, proxyHost, proxyPort, proxyUser, proxyPassw
if(length(r) > 0) {
retCode = r[0];
}
data = replace(data, retCodeRegexp, '');
data = trim(replace(data, retCodeRegexp, ''));
}
}
return { retCode, data };
+3 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector
PKG_VERSION:=1.7.4
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
include $(INCLUDE_DIR)/package.mk
@@ -62,6 +62,8 @@ define Package/$(PKG_NAME)/install
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_public_ip.lua $(1)/usr/lib/lua/internet-detector/modules/mod_public_ip.lua
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_user_scripts.lua $(1)/usr/lib/lua/internet-detector/modules/mod_user_scripts.lua
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_regular_script.lua $(1)/usr/lib/lua/internet-detector/modules/mod_regular_script.lua
$(INSTALL_DIR) $(1)/lib/upgrade/keep.d
$(INSTALL_DATA) ./files/lib/upgrade/keep.d/internet-detector $(1)/lib/upgrade/keep.d/internet-detector
endef
define Package/$(PKG_NAME)/postinst
@@ -0,0 +1 @@
/etc/internet-detector/*
@@ -27,39 +27,39 @@ get_enabled_anonymous_secs() {
ipt_rule() {
if [ "$1" = "add" ]; then
iptables -w -t mangle -I OUTPUT -p udp -m multiport --sports 500,4500 -m comment --comment "${ipt_comment}" -j RETURN 2>/dev/null
iptables -w -I input_rule -p udp -m multiport --dports 500,4500 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I forwarding_rule -m policy --dir in --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I forwarding_rule -m policy --dir out --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I INPUT -p udp -m multiport --dports 500,4500 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I FORWARD -m policy --dir in --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I FORWARD -m policy --dir out --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
ip6tables -w -t mangle -I OUTPUT -p udp -m multiport --sports 500,4500 -m comment --comment "${ipt_comment}" -j RETURN 2>/dev/null
ip6tables -w -I input_rule -p udp -m multiport --dports 500,4500 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
ip6tables -w -I forwarding_rule -m policy --dir in --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
ip6tables -w -I forwarding_rule -m policy --dir out --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
ip6tables -w -I INPUT -p udp -m multiport --dports 500,4500 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
ip6tables -w -I FORWARD -m policy --dir in --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
ip6tables -w -I FORWARD -m policy --dir out --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
if [ "${vt_type}" = "L2TP" ]; then
iptables -w -t mangle -I OUTPUT -p udp --sport 1701 -m comment --comment "${ipt_comment}" -j RETURN 2>/dev/null
iptables -w -I input_rule -p udp --dport 1701 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I input_rule -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I input_rule -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I forwarding_rule -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I forwarding_rule -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I output_rule -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I output_rule -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -t nat -I postrouting_rule -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null
[ "$?" != "0" ] && iptables -w -t nat -I postrouting_rule -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null
iptables -w -t nat -I postrouting_rule -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null
[ "$?" != "0" ] && iptables -w -t nat -I postrouting_rule -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null
iptables -w -I INPUT -p udp --dport 1701 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I INPUT -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I INPUT -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I FORWARD -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I FORWARD -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I OUTPUT -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I OUTPUT -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -t nat -I POSTROUTING -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null
[ "$?" != "0" ] && iptables -w -t nat -I POSTROUTING -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null
iptables -w -t nat -I POSTROUTING -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null
[ "$?" != "0" ] && iptables -w -t nat -I POSTROUTING -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null
else
iptables -w -I input_rule -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I input_rule -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I forwarding_rule -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I forwarding_rule -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I output_rule -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I output_rule -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -t nat -I postrouting_rule -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null
[ "$?" != "0" ] && iptables -w -t nat -I postrouting_rule -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null
iptables -w -t nat -I postrouting_rule -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null
[ "$?" != "0" ] && iptables -w -t nat -I postrouting_rule -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null
iptables -w -I INPUT -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I INPUT -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I FORWARD -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I FORWARD -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I OUTPUT -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -I OUTPUT -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null
iptables -w -t nat -I POSTROUTING -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null
[ "$?" != "0" ] && iptables -w -t nat -I POSTROUTING -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null
iptables -w -t nat -I POSTROUTING -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null
[ "$?" != "0" ] && iptables -w -t nat -I POSTROUTING -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null
uci -q batch <<-EOF >/dev/null
@@ -80,13 +80,13 @@ ipt_rule() {
$1 -w -D $2 $index 2>/dev/null
done
}
ipt_del "iptables -w" "input_rule"
ipt_del "iptables -w" "forwarding_rule"
ipt_del "iptables -w" "output_rule"
ipt_del "iptables -w -t nat" "postrouting_rule"
ipt_del "iptables -w" "INPUT"
ipt_del "iptables -w" "FORWARD"
ipt_del "iptables -w" "OUTPUT"
ipt_del "iptables -w -t nat" "POSTROUTING"
ipt_del "iptables -w -t mangle" "OUTPUT"
ipt_del "ip6tables -w" "input_rule"
ipt_del "ip6tables -w" "forwarding_rule"
ipt_del "ip6tables -w" "INPUT"
ipt_del "ip6tables -w" "FORWARD"
ipt_del "ip6tables -w -t mangle" "OUTPUT"
uci -q batch <<-EOF >/dev/null
delete network.ipsec_server
@@ -123,6 +123,7 @@ gen_include() {
$(extract_rules6 filter)
$(extract_rules6 mangle)
EOT
exit 0
EOF
return 0
}
@@ -5,9 +5,16 @@ uci -q batch <<-EOF >/dev/null
set firewall.luci_app_ipsec_server=include
set firewall.luci_app_ipsec_server.type=script
set firewall.luci_app_ipsec_server.path=/var/etc/ipsecvpn.include
set firewall.luci_app_ipsec_server.reload=1
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.luci_app_ipsec_server.reload='1'
else
uci -q delete firewall.luci_app_ipsec_server.reload
uci -q delete firewall.luci_app_ipsec_server.fw4_compatible
fi
uci commit firewall
/etc/init.d/ipsec disable 2>/dev/null
/etc/init.d/ipsec stop 2>/dev/null
/etc/init.d/xl2tpd disable 2>/dev/null
@@ -129,11 +129,11 @@ start_instance() {
cat <<-EOF >> ${TMP_OVPN_PATH}/${cfgid}/iptables_add.sh
${TMP_OVPN_PATH}/${cfgid}/iptables_del.sh
#允许服务端配置静态路由表访问内网
iptables -w -t nat -I postrouting_rule -s \$(cat ${TMP_OVPN_PATH}/${cfgid}/network)/\$(cat ${TMP_OVPN_PATH}/${cfgid}/mask) -m comment --comment "${c_dev}" -j MASQUERADE
iptables -w -t nat -I postrouting_rule -o ${c_dev} -m comment --comment "${c_dev}" -j MASQUERADE
iptables -w -I input_rule -s \$(cat ${TMP_OVPN_PATH}/${cfgid}/network)/\$(cat ${TMP_OVPN_PATH}/${cfgid}/mask) -m comment --comment "${c_dev}" -j ${c_allow_access_action}
iptables -w -I forwarding_rule -i ${c_dev} -m comment --comment "${c_dev}" -j ACCEPT
iptables -w -I forwarding_rule -o ${c_dev} -m comment --comment "${c_dev}" -j ACCEPT
iptables -w -t nat -I POSTROUTING -s \$(cat ${TMP_OVPN_PATH}/${cfgid}/network)/\$(cat ${TMP_OVPN_PATH}/${cfgid}/mask) -m comment --comment "${c_dev}" -j MASQUERADE
iptables -w -t nat -I POSTROUTING -o ${c_dev} -m comment --comment "${c_dev}" -j MASQUERADE
iptables -w -I INPUT -s \$(cat ${TMP_OVPN_PATH}/${cfgid}/network)/\$(cat ${TMP_OVPN_PATH}/${cfgid}/mask) -m comment --comment "${c_dev}" -j ${c_allow_access_action}
iptables -w -I FORWARD -i ${c_dev} -m comment --comment "${c_dev}" -j ACCEPT
iptables -w -I FORWARD -o ${c_dev} -m comment --comment "${c_dev}" -j ACCEPT
EOF
cat <<-EOF >> ${TMP_OVPN_PATH}/${cfgid}/iptables_del.sh
@@ -143,9 +143,9 @@ start_instance() {
\$1 -w -D \$2 \$index 2>/dev/null
done
}
ipt_del "iptables -w" "input_rule"
ipt_del "iptables -w" "forwarding_rule"
ipt_del "iptables -w -t nat" "postrouting_rule"
ipt_del "iptables -w" "INPUT"
ipt_del "iptables -w" "FORWARD"
ipt_del "iptables -w -t nat" "POSTROUTING"
EOF
cat <<-EOF >> ${TMP_OVPN_PATH}/${cfgid}/ipchange.sh
@@ -162,6 +162,7 @@ start_instance() {
cat <<-EOF >> /var/etc/${CONFIG}.include
${TMP_OVPN_PATH}/${cfgid}/iptables_add.sh
exit 0
EOF
local c_routes=$(uci -q get ${CONFIG}.${cfgid}.routes)
@@ -5,8 +5,15 @@ uci -q batch <<-EOF >/dev/null
set firewall.openvpn_client=include
set firewall.openvpn_client.type=script
set firewall.openvpn_client.path=/var/etc/luci-app-openvpn-client.include
set firewall.openvpn_client.reload=1
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.openvpn_client.reload='1'
else
uci -q delete firewall.openvpn_client.reload
uci -q delete firewall.openvpn_client.fw4_compatible
fi
uci commit firewall
rm -rf /tmp/luci-*cache
exit 0
@@ -23,13 +23,13 @@ ipt_flag="OpenVPN Server"
ipt_rule() {
if [ "$1" = "add" ]; then
iptables -w -t nat -I postrouting_rule -s ${ip_segment%.*}.0/${ip_prefix} -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
iptables -w -I forwarding_rule -s ${ip_segment%.*}.0/${ip_prefix} -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -w -I input_rule -p ${proto} --dport ${port} -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -w -t nat -I POSTROUTING -s ${ip_segment%.*}.0/${ip_prefix} -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
iptables -w -I FORWARD -s ${ip_segment%.*}.0/${ip_prefix} -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -w -I INPUT -p ${proto} --dport ${port} -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -w -t mangle -I OUTPUT -p ${proto} --sport ${port} -m comment --comment "${ipt_flag}" -j RETURN 2>/dev/null
[ "${ipv6}" -eq 1 ] && {
ip6tables -w -I input_rule -p ${proto} --dport ${port} -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
ip6tables -w -I INPUT -p ${proto} --dport ${port} -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
ip6tables -w -t mangle -I OUTPUT -p ${proto} --sport ${port} -m comment --comment "${ipt_flag}" -j RETURN 2>/dev/null
}
else
@@ -39,11 +39,11 @@ ipt_rule() {
$1 -w -D $2 $index 2>/dev/null
done
}
ipt_del "iptables" "forwarding_rule"
ipt_del "iptables" "input_rule"
ipt_del "iptables -t nat" "postrouting_rule"
ipt_del "iptables" "FORWARD"
ipt_del "iptables" "INPUT"
ipt_del "iptables -t nat" "POSTROUTING"
ipt_del "iptables -t mangle" "OUTPUT"
ipt_del "ip6tables" "input_rule"
ipt_del "ip6tables" "INPUT"
ipt_del "ip6tables -t mangle" "OUTPUT"
fi
}
@@ -72,6 +72,7 @@ gen_include() {
$(extract_rules 6 mangle)
EOT
}
exit 0
EOF
return 0
}
@@ -8,12 +8,6 @@ done
uci -q commit firewall
uci -q batch <<-EOF >/dev/null
delete firewall.openvpn_server
set firewall.openvpn_server=include
set firewall.openvpn_server.type=script
set firewall.openvpn_server.path=/var/etc/luci-app-openvpn-server.include
set firewall.openvpn_server.reload=1
delete firewall.ovpn_server
set firewall.ovpn_server=zone
set firewall.ovpn_server.name='ovpn_server'
@@ -26,8 +20,20 @@ uci -q batch <<-EOF >/dev/null
set firewall.lantoovpnserver=forwarding
set firewall.lantoovpnserver.src='lan'
set firewall.lantoovpnserver.dest='ovpn_server'
commit firewall
delete firewall.openvpn_server
set firewall.openvpn_server=include
set firewall.openvpn_server.type=script
set firewall.openvpn_server.path=/var/etc/luci-app-openvpn-server.include
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.openvpn_server.reload='1'
else
uci -q delete firewall.openvpn_server.reload
uci -q delete firewall.openvpn_server.fw4_compatible
fi
uci commit firewall
rm -rf /tmp/luci-*cache
exit 0
@@ -21,8 +21,9 @@ localip=$(config_t_get service localip 10.0.1.1)
ipt_rule() {
if [ "$1" = "add" ]; then
iptables -t nat -I postrouting_rule -s ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
iptables -I forwarding_rule -s ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -w -t nat -I POSTROUTING -s ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
iptables -w -t nat -I POSTROUTING -d ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
iptables -w -I FORWARD -s ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
else
ipt_del() {
for i in $(seq 1 $($1 -nL $2 | grep -c "${ipt_flag}")); do
@@ -30,8 +31,8 @@ ipt_rule() {
$1 -w -D $2 $index 2>/dev/null
done
}
ipt_del "iptables" "forwarding_rule"
ipt_del "iptables -t nat" "postrouting_rule"
ipt_del "iptables" "FORWARD"
ipt_del "iptables -t nat" "POSTROUTING"
fi
}
@@ -49,6 +50,7 @@ gen_include() {
$(extract_rules filter)
$(extract_rules nat)
EOT
exit 0
EOF
return 0
}
@@ -5,8 +5,15 @@ uci -q batch <<-EOF >/dev/null
set firewall.luci_app_pppoe_server=include
set firewall.luci_app_pppoe_server.type=script
set firewall.luci_app_pppoe_server.path=/var/etc/luci-app-pppoe-server.include
set firewall.luci_app_pppoe_server.reload=1
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.luci_app_pppoe_server.reload='1'
else
uci -q delete firewall.luci_app_pppoe_server.reload
uci -q delete firewall.luci_app_pppoe_server.fw4_compatible
fi
uci commit firewall
rm -rf /tmp/luci-*cache
exit 0
@@ -18,10 +18,11 @@ get_enabled_anonymous_secs() {
ipt_rule() {
if [ "$1" = "add" ]; then
iptables -t nat -I postrouting_rule -s ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
iptables -I forwarding_rule -s ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -I input_rule -p tcp --dport 1723 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -t mangle -I OUTPUT -p tcp --sport 1723 -m comment --comment "${ipt_flag}" -j RETURN 2>/dev/null
iptables -w -t nat -I POSTROUTING -s ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
iptables -w -t nat -I POSTROUTING -d ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
iptables -w -I FORWARD -s ${localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -w -I INPUT -p tcp --dport 1723 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
iptables -w -t mangle -I OUTPUT -p tcp --sport 1723 -m comment --comment "${ipt_flag}" -j RETURN 2>/dev/null
else
ipt_del() {
for i in $(seq 1 $($1 -nL $2 | grep -c "${ipt_flag}")); do
@@ -29,9 +30,9 @@ ipt_rule() {
$1 -w -D $2 $index 2>/dev/null
done
}
ipt_del "iptables" "forwarding_rule"
ipt_del "iptables" "input_rule"
ipt_del "iptables -t nat" "postrouting_rule"
ipt_del "iptables" "FORWARD"
ipt_del "iptables" "INPUT"
ipt_del "iptables -t nat" "POSTROUTING"
ipt_del "iptables -t mangle" "OUTPUT"
fi
}
@@ -50,6 +51,7 @@ gen_include() {
$(extract_rules filter)
$(extract_rules nat)
EOT
exit 0
EOF
return 0
}
@@ -5,9 +5,16 @@ uci -q batch <<-EOF >/dev/null
set firewall.luci_app_pptp_server=include
set firewall.luci_app_pptp_server.type=script
set firewall.luci_app_pptp_server.path=/var/etc/pptpd.include
set firewall.luci_app_pptp_server.reload=1
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.luci_app_pptp_server.reload='1'
else
uci -q delete firewall.luci_app_pptp_server.reload
uci -q delete firewall.luci_app_pptp_server.fw4_compatible
fi
uci commit firewall
/etc/init.d/pptpd disable 2>/dev/null
/etc/init.d/pptpd stop 2>/dev/null
rm -rf /tmp/luci-*cache
@@ -55,6 +55,7 @@ gen_include() {
iptables-restore -n <<-EOT
$(extract_rules filter)
EOT
exit 0
EOF
return 0
}
@@ -5,9 +5,16 @@ uci -q batch <<-EOF >/dev/null
set firewall.luci_app_softethervpn=include
set firewall.luci_app_softethervpn.type=script
set firewall.luci_app_softethervpn.path=/var/etc/luci-app-softethervpn.include
set firewall.luci_app_softethervpn.reload=1
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.luci_app_softethervpn.reload='1'
else
uci -q delete firewall.luci_app_softethervpn.reload
uci -q delete firewall.luci_app_softethervpn.fw4_compatible
fi
uci commit firewall
/etc/init.d/softethervpnserver stop 2>/dev/null
/etc/init.d/softethervpnserver disable 2>/dev/null
@@ -49,6 +49,7 @@ gen_include() {
iptables-restore -n <<-EOT
$(extract_rules filter)
EOT
exit 0
EOF
return 0
}
@@ -5,9 +5,16 @@ uci -q batch <<-EOF >/dev/null
set firewall.ssr_mudb_server=include
set firewall.ssr_mudb_server.type=script
set firewall.ssr_mudb_server.path=/var/etc/ssr_mudb_server.include
set firewall.ssr_mudb_server.reload=1
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.ssr_mudb_server.reload='1'
else
uci -q delete firewall.ssr_mudb_server.reload
uci -q delete firewall.ssr_mudb_server.fw4_compatible
fi
uci commit firewall
chmod a+x /usr/share/ssr_mudb_server/* >/dev/null 2>&1
cp -rf /usr/bin/python3 /usr/bin/python
rm -rf /tmp/luci-*cache
@@ -81,6 +81,7 @@ start(){
iptables_w -t nat -I TIMECONTROL -i $interface -j TIMECONTROL_RULES
iptables_w -t nat -I PREROUTING -j TIMECONTROL
echo "/etc/init.d/${CONFIG} restart" > "/var/etc/${CONFIG}.include"
echo "exit 0" >> "/var/etc/${CONFIG}.include"
}
stop(){
@@ -5,8 +5,15 @@ uci -q batch <<-EOF >/dev/null
set firewall.luci_app_timecontrol=include
set firewall.luci_app_timecontrol.type=script
set firewall.luci_app_timecontrol.path=/var/etc/luci-app-timecontrol.include
set firewall.luci_app_timecontrol.reload=1
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.luci_app_timecontrol.reload='1'
else
uci -q delete firewall.luci_app_timecontrol.reload
uci -q delete firewall.luci_app_timecontrol.fw4_compatible
fi
uci commit firewall
rm -rf /tmp/luci-*cache
exit 0
@@ -192,6 +192,7 @@ add_rule()
mkdir -p /var/etc
echo -e "/etc/init.d/adbyby restart" > "/var/etc/adbyby.include"
echo -e "exit 0" >> "/var/etc/adbyby.include"
}
del_rule()
@@ -1,26 +1,29 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@adbyby[-1]
add ucitrack adbyby
set ucitrack.@adbyby[-1].init=adbyby
commit ucitrack
delete firewall.adblock
add firewall rule
rename firewall.@rule[-1]="adblock"
set firewall.@rule[-1].name="adblock"
set firewall.@rule[-1].target="DROP"
set firewall.@rule[-1].src="wan"
set firewall.@rule[-1].proto="tcp"
set firewall.@rule[-1].dest_port="8118"
delete firewall.adbyby
set firewall.adbyby=include
set firewall.adbyby.type=script
set firewall.adbyby.path=/var/etc/adbyby.include
set firewall.adbyby.reload=1
delete firewall.adblock
add firewall rule
rename firewall.@rule[-1]="adblock"
set firewall.@rule[-1].name="adblock"
set firewall.@rule[-1].target="DROP"
set firewall.@rule[-1].src="wan"
set firewall.@rule[-1].proto="tcp"
set firewall.@rule[-1].dest_port="8118"
commit firewall
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.adbyby.reload='1'
else
uci -q delete firewall.adbyby.reload
uci -q delete firewall.adbyby.fw4_compatible
fi
uci commit firewall
mkdir -p /etc/dnsmasq.d
/etc/init.d/adbyby stop
@@ -1,12 +1,5 @@
#!/bin/sh
touch /etc/config/arpbind
uci -q batch <<-EOF >/dev/null
delete ucitrack.@arpbind[-1]
add ucitrack arpbind
set ucitrack.@arpbind[-1].init=arpbind
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@autoreboot[-1]
add ucitrack autoreboot
set ucitrack.@autoreboot[-1].init=autoreboot
commit ucitrack
EOF
rm -rf /tmp/luci-*
exit 0
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@baidupcs-web[-1]
add ucitrack baidupcs-web
set ucitrack.@baidupcs-web[-1].init=baidupcs-web
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@cifs[-1]
add ucitrack cifs
set ucitrack.@cifs[-1].init=cifs
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@frp[-1]
add ucitrack frp
set ucitrack.@frp[-1].init=frp
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@frps[-1]
add ucitrack frps
set ucitrack.@frps[-1].init=frps
commit ucitrack
EOF
rm -rf /tmp/luci-indexcache /tmp/luci-modulecache
exit 0
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@mwan3helper[-1]
add ucitrack mwan3helper
set ucitrack.@mwan3helper[-1].init=mwan3helper
commit ucitrack
EOF
rm -f /tmp/luci*
exit 0
@@ -1,12 +1,5 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@nfs[-1]
add ucitrack nfs
set ucitrack.@nfs[-1].init=nfs
commit ucitrack
EOF
/etc/init.d/nfs enable && /etc/init.d/nfs restart
rm -f /tmp/luci-indexcache
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@nps[-1]
add ucitrack nps
set ucitrack.@nps[-1].init=nps
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0
@@ -1,12 +1,5 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@qbittorrent[-1]
add ucitrack qbittorrent
set ucitrack.@qbittorrent[-1].init=qbittorrent
commit ucitrack
EOF
mkdir /opt
rm -f /tmp/luci-indexcache*
@@ -1,12 +1,5 @@
#!/bin/sh
touch /etc/config/syncdial
uci -q batch <<-EOF >/dev/null
delete ucitrack.@syncdial[-1]
add ucitrack syncdial
set ucitrack.@syncdial[-1].exec='/bin/genwancfg'
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0
@@ -1,12 +1,5 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@turboacc[-1]
add ucitrack turboacc
set ucitrack.@turboacc[-1].init=turboacc
commit ucitrack
EOF
[ -n "$(lsmod | grep mtkhnat)" ] && \
uci set turboacc.config.sw_flow='0' && \
uci set turboacc.config.hw_flow='0'
@@ -1,12 +1,5 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@usb_printer[-1]
add ucitrack usb_printer
set ucitrack.@usb_printer[-1].init=usb_printer
commit ucitrack
EOF
[ -f /etc/init.d/p910nd ] && /etc/init.d/p910nd disable
exit 0
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@uugamebooster[-1]
add ucitrack uugamebooster
set ucitrack.@uugamebooster[-1].init=uugamebooster
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0
@@ -1,11 +1,6 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@vlmcsd[-1]
add ucitrack vlmcsd
set ucitrack.@vlmcsd[-1].init=kms
commit ucitrack
delete firewall.kms
add firewall rule
rename firewall.@rule[-1]="kms"
@@ -1,11 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@vsftpd[-1]
add ucitrack vsftpd
set ucitrack.@vsftpd[-1].init=vsftpd
commit ucitrack
EOF
rm -f /tmp/luci-vsftpd
exit 0
@@ -5,10 +5,16 @@ uci -q batch <<-EOF >/dev/null
set firewall.wrtbwmon=include
set firewall.wrtbwmon.type=script
set firewall.wrtbwmon.path='/etc/wrtbwmon.include'
set firewall.wrtbwmon.reload=1
commit firewall
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.wrtbwmon.reload='1'
else
uci -q delete firewall.wrtbwmon.reload
uci -q delete firewall.wrtbwmon.fw4_compatible
fi
uci commit firewall
/etc/init.d/wrtbwmon enable
/etc/init.d/wrtbwmon start
exit 0
@@ -1,6 +0,0 @@
uci batch <<EOF
delete ucitrack.@xlnetacc[-1]
add ucitrack xlnetacc
set ucitrack.@xlnetacc[-1].init=xlnetacc
commit ucitrack
EOF
@@ -1,18 +1,19 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@zerotier[-1]
add ucitrack zerotier
set ucitrack.@zerotier[-1].init=zerotier
commit ucitrack
delete firewall.zerotier
set firewall.zerotier=include
set firewall.zerotier.type=script
set firewall.zerotier.path=/etc/zerotier.start
set firewall.zerotier.reload=1
commit firewall
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.zerotier.reload='1'
else
uci -q delete firewall.zerotier.reload
uci -q delete firewall.zerotier.fw4_compatible
fi
uci commit firewall
rm -f /tmp/luci-indexcache
exit 0
+4 -4
View File
@@ -6,10 +6,10 @@ START=90
start(){
if [ ! -f "/tmp/vlmcsd.pid" ]; then
/usr/bin/vlmcsd -i /etc/vlmcsd/vlmcsd.ini -p /tmp/vlmcsd.pid -L 0.0.0.0:1688
iptables -D input_rule -p tcp --dport 1688 -j ACCEPT
iptables -A input_rule -p tcp --dport 1688 -j ACCEPT
iptables -w -D INPUT -p tcp --dport 1688 -j ACCEPT
iptables -w -A INPUT -p tcp --dport 1688 -j ACCEPT
sed -i '/## luci-app-vlmcsd/d' /etc/firewall.user
echo "iptables -A input_rule -p tcp --dport 1688 -j ACCEPT ## luci-app-vlmcsd" >> /etc/firewall.user
echo "iptables -w -A INPUT -p tcp --dport 1688 -j ACCEPT ## luci-app-vlmcsd" >> /etc/firewall.user
echo "KMS Server has started."
else
echo "KMS Server has already started."
@@ -23,7 +23,7 @@ stop(){
pid=`cat /tmp/vlmcsd.pid`
kill $pid
rm -f /tmp/vlmcsd.pid
iptables -D input_rule -p tcp --dport 1688 -j ACCEPT
iptables -w -D INPUT -p tcp --dport 1688 -j ACCEPT
sed -i '/## luci-app-vlmcsd/d' /etc/firewall.user
echo "KMS Server has stopped."
fi
-4
View File
@@ -61,10 +61,6 @@ define Package/luci-app-adguardhome/prerm
if [ -z "$${IPKG_INSTROOT}" ]; then
/etc/init.d/AdGuardHome disable
/etc/init.d/AdGuardHome stop
uci -q batch <<-EOF >/dev/null 2>&1
delete ucitrack.@AdGuardHome[-1]
commit ucitrack
EOF
fi
exit 0
endef
@@ -63,13 +63,18 @@ set_iptable()
local ipv6_server=$1
local tcp_server=$2
uci -q batch <<-EOF >/dev/null 2>&1
delete firewall.AdGuardHome
delete firewall.AdGuardHome
set firewall.AdGuardHome=include
set firewall.AdGuardHome.type=script
set firewall.AdGuardHome.path=/usr/share/AdGuardHome/firewall.start
set firewall.AdGuardHome.reload=1
commit firewall
EOF
if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then
uci -q set firewall.AdGuardHome.reload='1'
else
uci -q delete firewall.AdGuardHome.reload
uci -q delete firewall.AdGuardHome.fw4_compatible
fi
uci commit firewall
[ "$tcp_server" == "1" ] && iptables -t nat -I PREROUTING 1 -m comment --comment "AdGuardHome" -p tcp --dport 53 -j REDIRECT --to-ports $AdGuardHome_PORT
iptables -t nat -I PREROUTING 1 -m comment --comment "AdGuardHome" -p udp --dport 53 -j REDIRECT --to-ports $AdGuardHome_PORT
@@ -83,7 +88,7 @@ EOF
clear_iptable()
{
uci -q batch <<-EOF >/dev/null 2>&1
delete firewall.AdGuardHome
delete firewall.AdGuardHome
commit firewall
EOF
@@ -1,12 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null 2>&1
delete ucitrack.@AdGuardHome[-1]
add ucitrack AdGuardHome
set ucitrack.@AdGuardHome[-1].init=AdGuardHome
commit ucitrack
delete AdGuardHome.AdGuardHome.ucitracktest
EOF
rm -f /tmp/luci-indexcache
exit 0
@@ -1,4 +1,4 @@
{
"config": "adguardhome",
"init": "adguardhome"
"config": "AdGuardHome",
"init": "AdGuardHome"
}
+107
View File
@@ -0,0 +1,107 @@
name: build-apk
on:
push:
tags:
- 'v*'
- 'dev-*'
permissions:
contents: write
env:
TARGET: x86
SUBTARGET: 64
PKG_DIR_NAME: luci-app-adguardhome
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: 安装依赖
run: |
sudo apt update
sudo apt install -y build-essential clang flex bison g++ gawk \
gcc-multilib g++-multilib gettext git libncurses5-dev libssl-dev \
python3-setuptools rsync swig unzip zlib1g-dev file wget zstd jq
- name: 下载 SDK
run: |
set -euo pipefail
echo "🔍 获取 OpenWrt 25.12 最新版本..."
LATEST_TAG=$(
curl -fsSL https://api.github.com/repos/openwrt/openwrt/releases?per_page=50 \
| jq -r '.[].tag_name' \
| grep -E '^v25\.12\.[0-9]+(-rc[0-9]+)?$' \
| sort -V \
| tail -n 1
)
if [[ -z "${LATEST_TAG}" || "${LATEST_TAG}" == "null" ]]; then
echo "❌ 获取 OpenWrt 25.12.x tag 失败"
exit 1
fi
RELEASE="${LATEST_TAG#v}"
echo "检测到 25.12 系列最新版本: ${RELEASE}"
echo "📥 下载 SDK..."
BASE="https://downloads.openwrt.org/releases/${RELEASE}/targets/${TARGET}/${SUBTARGET}/"
SDK_TARBALL=$(curl -fsSL "$BASE" | grep -o 'openwrt-sdk-[^"]*\.tar\.zst' | head -n1)
mkdir -p _sdk
wget -q "${BASE}${SDK_TARBALL}" -O _sdk/sdk.tar.zst
tar --zstd -xf _sdk/sdk.tar.zst -C _sdk
SDK_DIR="$(find "$PWD/_sdk" -maxdepth 1 -type d -name 'openwrt-sdk-*' -print -quit)"
echo "SDK_DIR=${SDK_DIR}" >> "$GITHUB_ENV"
echo "✅ SDK 已解压到: ${SDK_DIR}"
- name: 构建包
run: |
cd "$SDK_DIR"
mkdir -p package/${PKG_DIR_NAME}
rsync -a --delete "$GITHUB_WORKSPACE/" "package/${PKG_DIR_NAME}/" \
--exclude '.git*' --exclude '_sdk'
./scripts/feeds update -a
./scripts/feeds install luci-base tar
touch .config
sed -i -E '/^(# )?CONFIG_(ALL|ALL_KMODS|ALL_NONSHARED|PACKAGE_TAR_XZ|PACKAGE_luci-app-adguardhome)(=| is not set)/d' .config
printf '%s\n' \
'CONFIG_ALL=n' \
'CONFIG_ALL_KMODS=n' \
'CONFIG_ALL_NONSHARED=n' \
'# CONFIG_PACKAGE_TAR_XZ is not set' \
'CONFIG_PACKAGE_luci-app-adguardhome=y' >> .config
make defconfig
if ! grep -Eq '^CONFIG_PACKAGE_luci-app-adguardhome=(y|m)$' .config; then
echo '::error::luci-app-adguardhome was not enabled after make defconfig'
grep -n 'luci-app-adguardhome' .config || true
sed -n '/^[[:space:]]*config PACKAGE_luci-app-adguardhome$/,/^[[:space:]]*help/p' tmp/.config-package.in
exit 1
fi
make package/compile -j"$(nproc)" V=sc
- name: 上传产物
uses: actions/upload-artifact@v4
with:
name: pkg-${{ github.ref_name }}
path: _sdk/**/bin/packages/**/${{ env.PKG_DIR_NAME }}*
if-no-files-found: error
- name: 发布到 Releases
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
prerelease: ${{ startsWith(github.ref_name, 'dev-') }}
make_latest: ${{ startsWith(github.ref_name, 'dev-') && 'false' || 'true' }}
files: _sdk/**/bin/packages/**/${{ env.PKG_DIR_NAME }}*
+1
View File
@@ -0,0 +1 @@
.DS_Store
+60 -30
View File
@@ -1,56 +1,86 @@
# Copyright (C) 2018-2019 Lienol
#
# This is free software, licensed under the Apache License, Version 2.0 .
# Copyright (C) 2025 w9315273
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-adguardhome
PKG_VERSION:=1.8-20221120
PKG_VERSION:=2.4.1
PKG_RELEASE:=1
PKG_MAINTAINER:=<https://github.com/rufengsuixing/luci-app-adguardhome>
LUCI_TITLE:=LuCI app for adguardhome
LUCI_DEPENDS:=+!wget&&!curl&&!wget-ssl:curl +adguardhome
LUCI_PKGARCH:=all
LUCI_DESCRIPTION:=LuCI support for adguardhome
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=w9315273
PKG_BUILD_DEPENDS:=luci-base/host
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-adguardhome
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=LuCI app for AdGuardHome
PKG_MAINTAINER:=https://github.com/w9315273/luci-app-adguardhome
PKGARCH:=all
DEPENDS:=+luci-base +rpcd-mod-ucode +uclient-fetch +tar
endef
define Package/luci-app-adguardhome/description
LuCI support for AdGuardHome (nftables-based DNS redirect).
This package does NOT include the AdGuardHome core binary.
endef
define Build/Prepare
endef
define Build/Compile
endef
define Package/luci-app-adguardhome/conffiles
/usr/share/AdGuardHome/links.txt
/etc/config/AdGuardHome
endef
define Package/luci-app-adguardhome/install
cp -pR ./root/* $(1)/
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n
po2lmo ./po/zh-cn/AdGuardHome.po \
$(1)/usr/lib/lua/luci/i18n/AdGuardHome.zh-cn.lmo
endef
define Package/luci-app-adguardhome/postinst
#!/bin/sh
/etc/init.d/AdGuardHome enable >/dev/null 2>&1
enable=$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null)
if [ "$enable" == "1" ]; then
/etc/init.d/AdGuardHome reload >/dev/null 2>&1
fi
rm -f /tmp/luci-indexcache
rm -f /tmp/luci-modulecache/*
[ -n "$${IPKG_INSTROOT}" ] && exit 0
/etc/init.d/AdGuardHome enable >/dev/null 2>&1
enable=$$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null)
if [ "$$enable" = "1" ]; then
/etc/init.d/AdGuardHome reload
fi
rm -f /tmp/luci-indexcache
rm -f /tmp/luci-modulecache/*
/etc/init.d/rpcd reload
exit 0
endef
define Package/luci-app-adguardhome/prerm
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
/etc/init.d/AdGuardHome disable
/etc/init.d/AdGuardHome stop >/dev/null 2>&1
uci -q batch <<-EOF >/dev/null 2>&1
delete ucitrack.@AdGuardHome[-1]
commit ucitrack
/etc/init.d/AdGuardHome disable
/etc/init.d/AdGuardHome stop 2>/dev/null || true
uci -q batch <<-EOF >/dev/null 2>&1
delete ucitrack.@AdGuardHome[-1]
commit ucitrack
EOF
fi
exit 0
endef
define Package/luci-app-adguardhome/postrm
#!/bin/sh
rm -rf /etc/AdGuardHome/
exit 0
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
$(eval $(call BuildPackage,luci-app-adguardhome))
+34 -88
View File
@@ -1,93 +1,39 @@
# luci-app-adguardhome
复杂的AdGuardHome的openwrt的luci界面
- 可以管理网页端口
- luci下载/更新核心版本(支持自定义链接下载)
- 如果为tar.gz文件需要与官方的文件结构一致
- 或者直接为主程序二进制
- upx 压缩核心(xz依赖,脚本自动下载,如果opkg源无法连接,请在编译时加入此包)
- dns重定向
- 作为dnsmasq的上游服务器(在AGH中统计到的ip都为127.0.0.1,无法统计客户端及对应调整设置,ssr-plus正常)
- 重定向53端口到 AdGuardHome(ipv6需要开启ipv6 nat redirect 否则如果客户端使用ipv6过滤无效,不以dnsmasq为上游ssr-plus失效)
- 使用53端口替换 dnsmasq(需要设置AGH的dnsip为0.0.0.0, AGH和dnsmasq的端口将被交换,不以dnsmasq为上游ssr-plus失效)
- 自定义执行文件路径(支持tmp,每次重启后自动下载bin)
- 自定义配置文件路径
- 自定义工作路径
- 自定义运行日志路径
- gfwlist 删除/添加/定义上游dns服务器 另外安利一下https://github.com/rufengsuixing/luci-app-autoipsetadder
- 修改网页登陆密码
- 倒序/正序 查看/删除/备份 每3秒更新显示运行日志 + 本地浏览器时区转换
- 手动修改配置文件
- 支持yaml编辑器
- 模板快速配置
- 系统升级保留勾选文件
- 开机启动后当网络准备好时重启adh(3分钟超时,主要用于防止过滤器更新失败)
- 关机时备份勾选的工作目录中的文件(须知:在ipk更新的时候也会触发备份)
- 计划任务(以下为默认值,时间和参数可以在计划任务中调整)
- 自动更新核心(最好谨慎使用)(3:30/天)
- 自动截短查询日志 (每小时 限制到2000行)
- 自动截短运行日志(3:30/天 限制到2000行)
- 自动更新ipv6主机并重启adh (每小时,无更新不重启)
- 自动更新gfw列表并重启adh (3:30/天,无更新不重启)
#### 已知问题:
- db数据库不支持放在不支持mmap的文件系统上比如 jffs2 data-stk-oo,请修改工作目录,本软件如果检测到jffs2会自动ln(软连接)到/tmp,将会导致重启丢失dns数据库
- ~~AdGuardHome 不支持ipset 设置,在使用ipset的情况下,无法替代dnsmasq只能作为dnsmasq上游存在,如果你想要这个功能就去投票吧<br>
https://github.com/AdguardTeam/AdGuardHome/issues/1191~~
- 反馈出现大量127.0.0.1查询了localhost的请求,问题出现原因是ddns插件,如果不用ddns插件,请删除或者注释掉\etc\hotplug.d\iface\95-ddns的内容,如果还有其他来自本机的异常查询情况,高级玩家可以使用kmod来查找原因https://github.com/rufengsuixing/kmod-plog-port
- 如果出现需要多次提交才有反应的现象请及时提交issue
#### 使用方法
- 下载release,使用opkg安装即可
- 或者编译op时clone本项目加入软件包并勾选
#### 关于压缩
本着较真的想法,我测试了在jffs2的压缩文件系统上进行upx压缩结果的内存占用与空间占用(单位kb,使用最好压缩)<br>
文件大小<br>
源文件 14112 使用upx 压缩后 5309 <br>
实际占用 6260 使用upx 压缩后 5324 差值为 936<br>
VmRSS运存占用值<br>
不压缩 14380 使用upx 压缩后 18496 差值 -4116 <br>
对于压缩文件系统来说开启收益有,但不大<br>
如果是非压缩文件系统,性价比还是比较高的<br>
所以压缩是用运存空间来换rom空间,觉得值得就可以开启
#### 关于ssr配合
- 方法一gfw代理:dns重定向-作为dnsmasq的上游服务器
- 方法二gfw代理:手动设置adh上游dns为自己即127.0.0.1:[自己监听的端口],然后使用 dns重定向-使用53端口替换dnsmasq,(因为端口互换后就是dnsmasq为上游了)
- 方法三国外ip代理:任意重定向方式,adh加入gfw列表,开启计划任务定时更新gfw即可
- 方法四gfw代理:dns重定向-重定向53端口到AdGuardHome,设置adh上游dns 为127.0.0.1:53
#### 项目已经基本稳定,有bug欢迎主动反馈
基于原版 `luci-app-adguardhome` 修改,主要针对现代 OpenWrt 环境进行了 `nftables` 适配,并从 **v2.4.0** 开始完成 LuCI 前端 JavaScript 化。
Complex openwrt AdGuardHome luci
当前 `main` 分支为 JavaScript 版本,旧版 Lua 实现保留在 [`lua`](https://github.com/w9315273/luci-app-adguardhome/tree/lua) 分支。
- can manage browser port
- download/update core in luci
- compress core with upx
- redirect dns
- as the upstream of dnsmasq
- redirect port 53 to AdGuardHome(ipv6 need to install ipv6 nat redirect or if client use ipv6 redirect is invalid)
- replace dnsmasq with port 53 (need to set AGH,dnsip=0.0.0.0,the port of dnsmasq and AGH will be exchange)
- change bin path
- change config path
- change work dir(support tmp,auto redownload after reboot)
- change runtime log path
- gfwlist query to specific dns server
- modify browser login passord
- Positive/reverse order see/del/backup runtime log which update every 3 second
- modify config manually(support yaml editor)
- use template to fast config(when no config file)
- Keep bin file and config when system upgrade (database and querylog can be choose)
- when boot wait for network access (3min timeout)
- backup workdir when shutdown
#### known issues:
- db database not support filesystem which not support mmap such as jffs2 and data-stk-oo,please modify work dir,if jffs2 is found,will auto ln (soft link)the dbs to /tmp ,will lost dns database after reboot
- ~~AdGuardhome not support ipset,when we use ipset ,it can\`t be the repacement of dnsmasq but the upstream of dnsmasq ,if you want it,vote for it.<br>
https://github.com/AdguardTeam/AdGuardHome/issues/1191<br>~~
- find so many localhost query from 127.0.0.1,the ddns plugin is the reason,if you don\`t use ddns, please remove or comment \etc\hotplug.d\iface\95-ddns
#### usage
- download release,install it with opkg
- or when make op,clone the code to the package path and set it as y or m
## 主要变化
#### pic
example in zh-cn:<br>
![Screenshot_2019-12-23 newifi-d1 - 基础设置 - LuCI](https://user-images.githubusercontent.com/22387141/71361626-81d60900-25ce-11ea-91d5-ac4e35d5c41e.png)
![图片](https://user-images.githubusercontent.com/22387141/71361650-90242500-25ce-11ea-9727-9306a3da1357.png)
![Screenshot_2019-12-23 newifi-d1 - 日志 - LuCI(1)](https://user-images.githubusercontent.com/22387141/71361700-b944b580-25ce-11ea-8562-f68c28952b2b.png)
![Screenshot_2019-12-23 newifi-d1 - 手动设置 - LuCI](https://user-images.githubusercontent.com/22387141/71361704-bb0e7900-25ce-11ea-8042-6dd396607030.png)
### v2.4.0 — LuCI JavaScript 迁移
从 **v2.4.0** 开始,LuCI 页面由传统 Lua CBI / HTM 架构迁移至 JavaScript。
- LuCI 页面由 Lua CBI / HTM 迁移到 JavaScript
- 后端接口迁移到 `rpcd + ucode`
- 使用 LuCI RPC ACL 控制前后端权限
- 删除旧的 `luasrc/controller`、`luasrc/model/cbi` 和 HTM 页面
- 保留原有 AdGuardHome 配置方式与主要功能
- 优化 YAML 配置编辑与配置检查
### nftables DNS 重定向
将原版的 DNS 重定向实现从 `iptables` 迁移到 `nftables`,核心使用方式保持不变。
### WAN 设备排除
从 **v2.2.0** 开始,可直接在 LuCI 界面的 **WAN 接口** 选项中选择需要排除的网卡,无需手动编辑 `adguardhome.nft.tpl`。
该选项仅在 **「重定向」模式** 下生效,用于避免将来自公网接口的 DNS 请求重定向到本机,从而防止 AdGuardHome 被暴露为公共 DNS 解析器。
请仅选择直接面向公网的接口,例如:
- `eth0`
- `pppoe-wan`
## 界面
<img width="677" height="695" src="https://github.com/user-attachments/assets/4b436eec-8e05-4bf6-82d9-a972164e9659" />
<img width="536" height="714" src="https://github.com/user-attachments/assets/575c2d6c-e289-425d-91b9-faf01b5a7262" />
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
set -e
REPO="w9315273/luci-app-adguardhome"
echo "Getting latest version..."
TAG=$(uclient-fetch -qO- "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null \
| sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
[ -n "$TAG" ] || { echo "ERROR: Failed to get latest version"; exit 1; }
case "$TAG" in
*-*) ;;
*) echo "ERROR: Unexpected tag format: $TAG"; exit 1 ;;
esac
VERSION=${TAG#v}
VERSION=${VERSION%-*}-r${VERSION##*-}
URL="https://github.com/${REPO}/releases/download/${TAG}/luci-app-adguardhome-${VERSION}.apk"
echo "Version: $VERSION"
echo "Downloading..."
trap 'rm -f /tmp/agh.apk' EXIT
uclient-fetch -qO /tmp/agh.apk "$URL"
echo "Installing..."
apk add --allow-untrusted /tmp/agh.apk
echo "Refreshing LuCI..."
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null
/etc/init.d/rpcd reload >/dev/null 2>&1
echo "Done."
@@ -1,147 +0,0 @@
module("luci.controller.AdGuardHome",package.seeall)
local fs=require"nixio.fs"
local http=require"luci.http"
local uci=require"luci.model.uci".cursor()
function index()
entry({"admin", "services", "AdGuardHome"},alias("admin", "services", "AdGuardHome", "base"),_("AdGuard Home"), 10).dependent = true
entry({"admin","services","AdGuardHome","base"},cbi("AdGuardHome/base"),_("Base Setting"),1).leaf = true
entry({"admin","services","AdGuardHome","log"},form("AdGuardHome/log"),_("Log"),2).leaf = true
entry({"admin","services","AdGuardHome","manual"},cbi("AdGuardHome/manual"),_("Manual Config"),3).leaf = true
entry({"admin","services","AdGuardHome","status"},call("act_status")).leaf=true
entry({"admin", "services", "AdGuardHome", "check"}, call("check_update"))
entry({"admin", "services", "AdGuardHome", "doupdate"}, call("do_update"))
entry({"admin", "services", "AdGuardHome", "getlog"}, call("get_log"))
entry({"admin", "services", "AdGuardHome", "dodellog"}, call("do_dellog"))
entry({"admin", "services", "AdGuardHome", "reloadconfig"}, call("reload_config"))
entry({"admin", "services", "AdGuardHome", "gettemplateconfig"}, call("get_template_config"))
end
function get_template_config()
local b
local d=""
local rcauto=uci:get("dhcp","@dnsmasq[0]","resolvfile")
if (rcauto == nil) then
for fle in fs.dir("/var/etc") do
if fle ~="." and fle ~=".."then
tf="/var/etc/"..fle
if string.match(tf,"/var/etc/dnsmasq.conf.") then
if tf and fs.access(tf) then
for le in io.lines(tf) do
sf=string.match (le,"^resolv%-file=(%S+)")
if (sf ~=nil) then
rcauto=sf
end
end
end
end
end
end
end
if rcauto and fs.access(rcauto) then
for cnt in io.lines(rcauto) do
b=string.match (cnt,"^[^#]*nameserver%s+([^%s]+)$")
if (b~=nil) then
d=d.." - "..b.."\n"
end
end
end
local f=io.open("/usr/share/AdGuardHome/AdGuardHome_template.yaml", "r+")
local tbl = {}
local a=""
while (1) do
a=f:read("*l")
if (a=="#bootstrap_dns") then
a=d
elseif (a=="#upstream_dns") then
a=d
elseif (a==nil) then
break
end
table.insert(tbl, a)
end
f:close()
http.prepare_content("text/plain; charset=utf-8")
http.write(table.concat(tbl, "\n"))
end
function reload_config()
fs.remove("/tmp/AdGuardHometmpconfig.yaml")
http.prepare_content("application/json")
http.write('')
end
function act_status()
local e={}
local binpath=uci:get("AdGuardHome","AdGuardHome","binpath")
e.running=luci.sys.call("pgrep "..binpath.." >/dev/null")==0
e.redirect=(fs.readfile("/var/run/AdGredir")=="1")
http.prepare_content("application/json")
http.write_json(e)
end
function do_update()
fs.writefile("/var/run/lucilogpos","0")
http.prepare_content("application/json")
http.write('')
local arg
if luci.http.formvalue("force") == "1" then
arg="force"
else
arg=""
end
if fs.access("/var/run/update_core") then
if arg=="force" then
luci.sys.exec("kill $(pgrep /usr/share/AdGuardHome/update_core.sh) ; sh /usr/share/AdGuardHome/update_core.sh "..arg.." >/tmp/AdGuardHome_update.log 2>&1 &")
end
else
luci.sys.exec("sh /usr/share/AdGuardHome/update_core.sh "..arg.." >/tmp/AdGuardHome_update.log 2>&1 &")
end
end
function get_log()
local logfile=uci:get("AdGuardHome","AdGuardHome","logfile")
if (logfile==nil) then
http.write("no log available\n")
return
elseif (logfile=="syslog") then
if not fs.access("/var/run/AdGuardHomesyslog") then
luci.sys.exec("(/usr/share/AdGuardHome/getsyslog.sh &); sleep 1;")
end
logfile="/tmp/AdGuardHometmp.log"
fs.writefile("/var/run/AdGuardHomesyslog","1")
elseif not fs.access(logfile) then
http.write("")
return
end
http.prepare_content("text/plain; charset=utf-8")
local fdp
if fs.access("/var/run/lucilogreload") then
fdp=0
fs.remove("/var/run/lucilogreload")
else
fdp=tonumber(fs.readfile("/var/run/lucilogpos")) or 0
end
local f=io.open(logfile, "r+")
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
fs.writefile("/var/run/lucilogpos",tostring(fdp))
f:close()
http.write(a)
end
function do_dellog()
local logfile=uci:get("AdGuardHome","AdGuardHome","logfile")
fs.writefile(logfile,"")
http.prepare_content("application/json")
http.write('')
end
function check_update()
http.prepare_content("text/plain; charset=utf-8")
local fdp=tonumber(fs.readfile("/var/run/lucilogpos")) or 0
local f=io.open("/tmp/AdGuardHome_update.log", "r+")
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
fs.writefile("/var/run/lucilogpos",tostring(fdp))
f:close()
if fs.access("/var/run/update_core") then
http.write(a)
else
http.write(a.."\0")
end
end
@@ -1,360 +0,0 @@
require("luci.sys")
require("luci.util")
require("io")
local m,s,o,o1
local fs=require"nixio.fs"
local uci=require"luci.model.uci".cursor()
local configpath=uci:get("AdGuardHome","AdGuardHome","configpath") or "/etc/AdGuardHome.yaml"
local binpath=uci:get("AdGuardHome","AdGuardHome","binpath") or "/usr/bin/AdGuardHome"
httpport=uci:get("AdGuardHome","AdGuardHome","httpport") or "3000"
m = Map("AdGuardHome", "AdGuard Home")
m.description = translate("Free and open source, powerful network-wide ads & trackers blocking DNS server.")
m:section(SimpleSection).template = "AdGuardHome/AdGuardHome_status"
s = m:section(TypedSection, "AdGuardHome")
s.anonymous=true
s.addremove=false
---- enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 0
o.optional = false
---- httpport
o =s:option(Value,"httpport",translate("Browser management port"))
o.placeholder=3000
o.default=3000
o.datatype="port"
o.optional = false
o.description = translate("<input type=\"button\" style=\"width:210px;border-color:Teal; text-align:center;font-weight:bold;color:Green;\" value=\"AdGuardHome Web:"..httpport.."\" onclick=\"window.open('http://'+window.location.hostname+':"..httpport.."/')\"/>")
---- update warning not safe
local binmtime=uci:get("AdGuardHome","AdGuardHome","binmtime") or "0"
local e=""
if not fs.access(configpath) then
e=e.." "..translate("no config")
end
if not fs.access(binpath) then
e=e.." "..translate("no core")
else
local version=uci:get("AdGuardHome","AdGuardHome","version")
local testtime=fs.stat(binpath,"mtime")
if testtime~=tonumber(binmtime) or version==nil then
local tmp=luci.sys.exec(binpath.." --version 2>/dev/null | grep -m 1 -E '[0-9]+[.][Bbeta0-9\.\-]+' -o")
version=string.sub(tmp, 1, -2)
if version=="" then version="core error" end
uci:set("AdGuardHome","AdGuardHome","version",version)
uci:set("AdGuardHome","AdGuardHome","binmtime",testtime)
uci:commit("AdGuardHome")
end
e=version..e
end
o=s:option(Button,"restart",translate("Update"))
o.inputtitle=translate("Update core version")
o.template = "AdGuardHome/AdGuardHome_check"
o.showfastconfig=(not fs.access(configpath))
o.description=string.format(translate("core version:").."<strong><font id=\"updateversion\" color=\"green\">%s </font></strong>",e)
---- port warning not safe
local port=luci.sys.exec("awk '/^dns:/ {found_dns=1} found_dns && /^ port:/ {print $2; exit}' "..configpath.." 2>nul")
if (port=="") then port="?" end
---- Redirect
o = s:option(ListValue, "redirect", port..translate("Redirect"), translate("AdGuardHome redirect mode"))
o.placeholder = "none"
o:value("none", translate("none"))
o:value("dnsmasq-upstream", translate("Run as dnsmasq upstream server"))
o:value("redirect", translate("Redirect 53 port to AdGuardHome"))
o:value("exchange", translate("Use port 53 replace dnsmasq"))
o.default = "none"
o.optional = true
---- bin path
o = s:option(Value, "binpath", translate("Bin Path"), translate("AdGuardHome Bin path if no bin will auto download"))
o.default = "/usr/bin/AdGuardHome"
o.datatype = "string"
o.optional = false
o.rmempty=false
o.validate=function(self, value)
if value=="" then return nil end
if fs.stat(value,"type")=="dir" then
fs.rmdir(value)
end
if fs.stat(value,"type")=="dir" then
if (m.message) then
m.message =m.message.."\nerror!bin path is a dir"
else
m.message ="error!bin path is a dir"
end
return nil
end
return value
end
--- arch
o = s:option(ListValue, "arch", translate("choose Arch for download"))
o:value("",translate("Auto"))
o:value("386",translate("i386"))
o:value("amd64",translate("x86_64"))
o:value("armv5",translate("armv5"))
o:value("armv6",translate("armv6"))
o:value("armv7",translate("armv7"))
o:value("arm64",translate("aarch64"))
o:value("mips_softfloat",translate("mips"))
o:value("mips64_softfloat",translate("mips64"))
o:value("mipsle_softfloat",translate("mipsel"))
o:value("mips64le_softfloat",translate("mips64el"))
o:value("ppc64le",translate("powerpc64"))
o.description=translate("Need to save to config first before downloading.")
o.default=""
o.rmempty=true
--- upx
o = s:option(ListValue, "upxflag", translate("use upx to compress bin after download"))
o:value("", translate("none"))
o:value("-1", translate("compress faster"))
o:value("-9", translate("compress better"))
o:value("--best", translate("compress best(can be slow for big files)"))
o:value("--brute", translate("try all available compression methods & filters [slow]"))
o:value("--ultra-brute", translate("try even more compression variants [very slow]"))
o.default = ""
o.description=translate("bin use less space,but may have compatibility issues")
o.rmempty = true
---- config path
o = s:option(Value, "configpath", translate("Config Path"), translate("AdGuardHome config path"))
o.default = "/etc/AdGuardHome.yaml"
o.datatype = "string"
o.optional = false
o.rmempty=false
o.validate=function(self, value)
if value==nil then return nil end
if fs.stat(value,"type")=="dir" then
fs.rmdir(value)
end
if fs.stat(value,"type")=="dir" then
if m.message then
m.message =m.message.."\nerror!config path is a dir"
else
m.message ="error!config path is a dir"
end
return nil
end
return value
end
---- work dir
o = s:option(Value, "workdir", translate("Work dir"), translate("AdGuardHome work dir include rules,audit log and database"))
o.default = "/etc/AdGuardHome"
o.datatype = "string"
o.optional = false
o.rmempty=false
o.validate=function(self, value)
if value=="" then return nil end
if fs.stat(value,"type")=="reg" then
if m.message then
m.message =m.message.."\nerror!work dir is a file"
else
m.message ="error!work dir is a file"
end
return nil
end
if string.sub(value, -1)=="/" then
return string.sub(value, 1, -2)
else
return value
end
end
---- log file
o = s:option(Value, "logfile", translate("Runtime log file"), translate("AdGuardHome runtime Log file if 'syslog': write to system log;if empty no log"))
o.datatype = "string"
o.rmempty = true
o.validate=function(self, value)
if fs.stat(value,"type")=="dir" then
fs.rmdir(value)
end
if fs.stat(value,"type")=="dir" then
if m.message then
m.message =m.message.."\nerror!log file is a dir"
else
m.message ="error!log file is a dir"
end
return nil
end
return value
end
---- debug
o = s:option(Flag, "verbose", translate("Verbose log"))
o.default = 0
o.optional = true
---- gfwlist
local a
if fs.access(configpath) then
a=luci.sys.call("grep -m 1 -q programadd "..configpath)
else
a=1
end
if (a==0) then
a="Added"
else
a="Not added"
end
o=s:option(Button,"gfwdel",translate("Del gfwlist"),translate(a))
o.optional = true
o.inputtitle=translate("Del")
o.write=function()
luci.sys.exec("sh /usr/share/AdGuardHome/gfw2adg.sh del 2>&1")
luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome"))
end
o=s:option(Button,"gfwadd",translate("Add gfwlist"),translate(a))
o.optional = true
o.inputtitle=translate("Add")
o.write=function()
luci.sys.exec("sh /usr/share/AdGuardHome/gfw2adg.sh 2>&1")
luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome"))
end
if fs.access(configpath) then
a=luci.sys.call("grep -m 1 -q ipset.txt "..configpath)
else
a=1
end
if (a==0) then
a="Added"
else
a="Not added"
end
o=s:option(Button,"gfwipsetdel",translate("Del gfwlist")..translate("(ipset only)"),translate(a))
o.optional = true
o.inputtitle=translate("Del")
o.write=function()
luci.sys.exec("sh /usr/share/AdGuardHome/gfwipset2adg.sh del 2>&1")
luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome"))
end
o=s:option(Button,"gfwipsetadd",translate("Add gfwlist")..translate("(ipset only)"),translate(a).." "..translate("will set to name gfwlist"))
o.optional = true
o.inputtitle=translate("Add")
o.write=function()
luci.sys.exec("sh /usr/share/AdGuardHome/gfwipset2adg.sh 2>&1")
luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome"))
end
o = s:option(Value, "gfwupstream", translate("Gfwlist upstream dns server"), translate("Gfwlist domain upstream dns service")..translate(a))
o.default = "tcp://208.67.220.220:5353"
o.datatype = "string"
o.optional = true
---- chpass
o = s:option(Value, "hashpass", translate("Change browser management password"), translate("Press load culculate model and culculate finally save/apply"))
o.default = ""
o.datatype = "string"
o.template = "AdGuardHome/AdGuardHome_chpass"
o.optional = true
---- upgrade protect
o = s:option(MultiValue, "upprotect", translate("Keep files when system upgrade"))
o:value("$binpath",translate("core bin"))
o:value("$configpath",translate("config file"))
o:value("$logfile",translate("log file"))
o:value("$workdir/data/sessions.db",translate("sessions.db"))
o:value("$workdir/data/stats.db",translate("stats.db"))
o:value("$workdir/data/querylog.json",translate("querylog.json"))
o:value("$workdir/data/filters",translate("filters"))
o.widget = "checkbox"
o.default = nil
o.optional=true
---- wait net on boot
o = s:option(Flag, "waitonboot", translate("On boot when network ok restart"))
o.default = 1
o.optional = true
---- backup workdir on shutdown
local workdir=uci:get("AdGuardHome","AdGuardHome","workdir") or "/etc/AdGuardHome"
o = s:option(MultiValue, "backupfile", translate("Backup workdir files when shutdown"))
o1 = s:option(Value, "backupwdpath", translate("Backup workdir path"))
local name
o:value("filters","filters")
o:value("stats.db","stats.db")
o:value("querylog.json","querylog.json")
o:value("sessions.db","sessions.db")
o1:depends ("backupfile", "filters")
o1:depends ("backupfile", "stats.db")
o1:depends ("backupfile", "querylog.json")
o1:depends ("backupfile", "sessions.db")
for name in fs.glob(workdir.."/data/*")
do
name=fs.basename (name)
if name~="filters" and name~="stats.db" and name~="querylog.json" and name~="sessions.db" then
o:value(name,name)
o1:depends ("backupfile", name)
end
end
o.widget = "checkbox"
o.default = nil
o.optional=false
o.description=translate("Will be restore when workdir/data is empty")
----backup workdir path
o1.default = "/etc/AdGuardHome"
o1.datatype = "string"
o1.optional = false
o1.validate=function(self, value)
if fs.stat(value,"type")=="reg" then
if m.message then
m.message =m.message.."\nerror!backup dir is a file"
else
m.message ="error!backup dir is a file"
end
return nil
end
if string.sub(value,-1)=="/" then
return string.sub(value, 1, -2)
else
return value
end
end
----Crontab
o = s:option(MultiValue, "crontab", translate("Crontab task"),translate("Please change time and args in crontab"))
o:value("autoupdate",translate("Auto update core"))
o:value("cutquerylog",translate("Auto tail querylog"))
o:value("cutruntimelog",translate("Auto tail runtime log"))
o:value("autohost",translate("Auto update ipv6 hosts and restart adh"))
o:value("autogfw",translate("Auto update gfwlist and restart adh"))
o:value("autogfwipset",translate("Auto update ipset list and restart adh"))
o.widget = "checkbox"
o.default = nil
o.optional=true
----downloadtarge
o = s:option(ListValue, "tagname", translate("Choose Release Version for download"))
o:value("release",translate("Release(Default)"))
o:value("beta",translate("Beta"))
o.description=translate("If this option is modified, please confirm the download links")
o.default="release"
o.rmempty=true
----downloadpath
o = s:option(TextValue, "downloadlinks",translate("Download links for update"))
o.optional = false
o.rows = 4
o.wrap = "soft"
o.cfgvalue = function(self, section)
return fs.readfile("/usr/share/AdGuardHome/links.txt")
end
o.write = function(self, section, value)
fs.writefile("/usr/share/AdGuardHome/links.txt", value:gsub("\r\n", "\n"))
end
fs.writefile("/var/run/lucilogpos","0")
function m.on_commit(map)
if (fs.access("/var/run/AdGserverdis")) then
io.popen("/etc/init.d/AdGuardHome reload &")
return
end
local ucitracktest=uci:get("AdGuardHome","AdGuardHome","ucitracktest")
if ucitracktest=="1" then
return
elseif ucitracktest=="0" then
io.popen("/etc/init.d/AdGuardHome reload &")
else
if (fs.access("/var/run/AdGlucitest")) then
uci:set("AdGuardHome","AdGuardHome","ucitracktest","0")
io.popen("/etc/init.d/AdGuardHome reload &")
else
fs.writefile("/var/run/AdGlucitest","")
if (ucitracktest=="2") then
uci:set("AdGuardHome","AdGuardHome","ucitracktest","1")
else
uci:set("AdGuardHome","AdGuardHome","ucitracktest","2")
end
end
uci:commit("AdGuardHome")
end
end
return m
Loaded 100 of 918 files, more files were not shown because too many files have changed in this diff. Show more