mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-10-26 21:12:51 +08:00
update 2026-09-24 10:46:53
This commit is contained in:
1 parent
907a158127
commit
b31a5c3878
4914 files changed
-1716179
No files matched your search
@@ -1,149 +0,0 @@
|
||||
#
|
||||
# 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))
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,56 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2015-2016 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=LingTiGameAcc
|
||||
PKG_VERSION:=2023
|
||||
PKG_RELEASE:=1.7.1
|
||||
|
||||
PKG_LICENSE:=GPLv3
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=eSir Playground
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
CATEGORY:=eSir Playground
|
||||
SUBMENU:=2. GameAcc
|
||||
TITLE:=LingTi Game Accelerator
|
||||
URL:=https://github.com/esirplayground/LingTiGameAcc
|
||||
DEPENDS:=+kmod-tun
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
LingTi Game Acc is a Game Accelerator which is paid service.
|
||||
endef
|
||||
|
||||
DIR_ARCH:=$(ARCH)
|
||||
|
||||
define Build/Prepare
|
||||
mkdir $(PKG_BUILD_DIR)/$(PKG_NAME)
|
||||
cp -r ./files/$(DIR_ARCH)/* $(PKG_BUILD_DIR)/$(PKG_NAME)/lingti
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME)/lingti $(1)/usr/bin
|
||||
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./root/etc/init.d/lingti $(1)/etc/init.d
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/postinst
|
||||
#!/bin/sh
|
||||
chmod +x $(1)/usr/bin/lingti
|
||||
chmod +x $(1)/etc/init.d/lingti
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -1,3 +0,0 @@
|
||||
# LingTiGameAcc
|
||||
|
||||
This Tools can be functioning `independently` without luci app.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,28 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
|
||||
[ -d /var/bin ] || mkdir -p /var/bin
|
||||
[ -f /var/bin/lingti ] || cp /usr/bin/lingti /var/bin/lingti
|
||||
|
||||
start() {
|
||||
state=`pgrep -f "/var/bin/lingti"`
|
||||
if [ ! -z "$state" ]; then
|
||||
restart
|
||||
else
|
||||
/var/bin/lingti &
|
||||
fi
|
||||
echo "LingTiGameAcc Started..."
|
||||
}
|
||||
|
||||
stop() {
|
||||
kill -9 `pgrep -f "/var/bin/lingti"` 2>/dev/null
|
||||
echo "LingTiGameAcc Stopped ..."
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
echo "Restarted."
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="https://github-readme-stats.vercel.app/api?username=kenzok8&show_icons=true&theme=default&hide_border=true&cache_seconds=86400" alt="kenzok8 github stats" height="165">
|
||||
<img src="https://github-readme-stats.vercel.app/api/top-langs/?username=kenzok8&layout=compact&theme=default&hide_border=true&cache_seconds=86400" alt="kenzok8 top langs" height="165">
|
||||
</div>
|
||||
<div align="center">
|
||||
<h1 align="center">wall仓库</h1>
|
||||
<img src="https://img.shields.io/github/issues/kenzok8/wall?color=green">
|
||||
<img src="https://img.shields.io/github/stars/kenzok8/wall?color=yellow">
|
||||
<img src="https://img.shields.io/github/forks/kenzok8/wall?color=orange">
|
||||
<img src="https://img.shields.io/github/license/kenzok8/wall?color=ff69b4">
|
||||
<img src="https://img.shields.io/github/languages/code-size/kenzok8/wall?color=blueviolet">
|
||||
</div>
|
||||
|
||||
|
||||
#### wall
|
||||
|
||||
* 集合了常用的几个插件更新,
|
||||
|
||||
* adguardhome
|
||||
* alist
|
||||
* filebrowser
|
||||
* gost
|
||||
* lucky
|
||||
* mosdns
|
||||
* smartdns
|
||||
* UnblockNeteaseMusic
|
||||
|
||||
* 感谢以上github仓库所有者!
|
||||
@@ -1,83 +0,0 @@
|
||||
# SPDX-Identifier-License: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=UnblockNeteaseMusic-Go
|
||||
PKG_VERSION:=0.2.13
|
||||
PKG_RELEASE:=$(AUTORELEASE)
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/cnsilvan/UnblockNeteaseMusic/tar.gz/$(PKG_VERSION)?
|
||||
PKG_HASH:=92201b7f04ab1015c806c672b98a29b97d0f137d9b60e6d35d279c2064ed86a4
|
||||
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
CONFIG_UNBLOCKNETEASEMUSIC_GO_COMPRESS_GOPROXY \
|
||||
CONFIG_UNBLOCKNETEASEMUSIC_GO_COMPRESS_UPX
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(firstword $(subst -, ,$(PKG_NAME)))-$(PKG_VERSION)
|
||||
PKG_BUILD_DEPENDS:=golang/host upx/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
|
||||
GO_PKG:=github.com/cnsilvan/UnblockNeteaseMusic
|
||||
GO_PKG_BUILD_PKG:=$$(GO_PKG)
|
||||
GO_PKG_LDFLAGS:=-s -w
|
||||
COMPILE_TIME:= $(shell TZ=UTC-8 date '+%Y-%m-%d %H:%M:%S')
|
||||
GO_PKG_LDFLAGS+= \
|
||||
-X '$(GO_PKG)/version.Version=$(PKG_VERSION)' \
|
||||
-X '$(GO_PKG)/version.BuildTime=$(COMPILE_TIME)' \
|
||||
-X '$(GO_PKG)/version.ExGoVersionInfo=$(GO_ARM) $(GO_MIPS)$(GO_MIPS64)'
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/UnblockNeteaseMusic-Go/config
|
||||
config UNBLOCKNETEASEMUSIC_GO_COMPRESS_GOPROXY
|
||||
bool "Compiling with GOPROXY proxy"
|
||||
default n
|
||||
|
||||
config UNBLOCKNETEASEMUSIC_GO_COMPRESS_UPX
|
||||
bool "Compress executable files with UPX"
|
||||
depends on !mips64
|
||||
default n
|
||||
endef
|
||||
|
||||
ifeq ($(CONFIG_UNBLOCKNETEASEMUSIC_GO_COMPRESS_GOPROXY),y)
|
||||
export GO111MODULE=on
|
||||
export GOPROXY=https://goproxy.io
|
||||
endif
|
||||
|
||||
define Package/UnblockNeteaseMusic-Go
|
||||
SECTION:=multimedia
|
||||
CATEGORY:=Multimedia
|
||||
TITLE:=Revive Netease Cloud Music (Golang)
|
||||
URL:=https://github.com/cnsilvan/UnblockNeteaseMusic
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(call GoPackage/Build/Compile)
|
||||
ifeq ($(CONFIG_UNBLOCKNETEASEMUSIC_GO_COMPRESS_UPX),y)
|
||||
$(STAGING_DIR_HOST)/bin/upx --lzma --best $(GO_PKG_BUILD_BIN_DIR)/UnblockNeteaseMusic
|
||||
endif
|
||||
endef
|
||||
|
||||
define Package/UnblockNeteaseMusic-Go/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/UnblockNeteaseMusic $(1)/usr/bin/UnblockNeteaseMusic
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/UnblockNeteaseMusicGo
|
||||
$(CP) ./files/* $(1)/usr/share/UnblockNeteaseMusicGo/
|
||||
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,UnblockNeteaseMusic-Go))
|
||||
$(eval $(call BuildPackage,UnblockNeteaseMusic-Go))
|
||||
@@ -1,14 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICIjCCAaigAwIBAgIUTc9HQDej5hLCQ74u436a5yE4MDcwCgYIKoZIzj0EAwMw
|
||||
SDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVzaWMgUm9v
|
||||
dCBDQTETMBEGA1UECgwKMTcxNTE3MzMyOTAeFw0yMTA0MzAwNzIzMDJaFw0yNjA0
|
||||
MjkwNzIzMDJaMEgxCzAJBgNVBAYTAkNOMSQwIgYDVQQDDBtVbmJsb2NrTmV0ZWFz
|
||||
ZU11c2ljIFJvb3QgQ0ExEzARBgNVBAoMCjE3MTUxNzMzMjkwdjAQBgcqhkjOPQIB
|
||||
BgUrgQQAIgNiAASIyI7lYgGlq49qWtY1O2/XNDeowYf7W/Z+l7C14bphxAJ9jSDo
|
||||
tLwbFPWy5VPENc0rB0/yeHA2z7LU67POL2gGgp+17y7scLkkBk3Q7wRMETrtP44Z
|
||||
ITBstZ0wzVyyQEKjUzBRMB0GA1UdDgQWBBQ2F7+t8cPHJaWuCD8RHTSdLugKYzAf
|
||||
BgNVHSMEGDAWgBQ2F7+t8cPHJaWuCD8RHTSdLugKYzAPBgNVHRMBAf8EBTADAQH/
|
||||
MAoGCCqGSM49BAMDA2gAMGUCMQDqaRX2e01e0U+f0As/KUKDhmG5ElkK5CjYK9jk
|
||||
kXLNGFXJLGta6CDvjtMLBPc20qkCMBvDs+JnJKVBEJNZVsRBBs+v2YxNU/u2aYJa
|
||||
dMwXuFveSDWOS7mBeRztX/geEggiSw==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,15 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICRDCCAcqgAwIBAgIUeVqRrT2mHG5Mc8JD+ErphiAmlgkwCgYIKoZIzj0EAwMw
|
||||
SDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVzaWMgUm9v
|
||||
dCBDQTETMBEGA1UECgwKMTcxNTE3MzMyOTAeFw0yMTA0MzAwNzIzMDJaFw0yMjA0
|
||||
MzAwNzIzMDJaMHsxCzAJBgNVBAYTAkNOMREwDwYDVQQHDAhIYW5nemhvdTEsMCoG
|
||||
A1UECgwjTmV0RWFzZSAoSGFuZ3pob3UpIE5ldHdvcmsgQ28uLCBMdGQxETAPBgNV
|
||||
BAsMCElUIERlcHQuMRgwFgYDVQQDDA8qLm11c2ljLjE2My5jb20wdjAQBgcqhkjO
|
||||
PQIBBgUrgQQAIgNiAAQTPyU9RQ1pAFMLmozi+c4pEC1rrxAlPGwO9Em+qV+a5qLW
|
||||
gQjjsJeabMqJ/UQ7hDtdKVxWuXiAjMiDcXwL63I71MZKPTAEKXdCmNQwb4kXvRUn
|
||||
oOR4r7BMxEpGlf0CULWjQjBAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMCkGA1UdEQQi
|
||||
MCCCDW11c2ljLjE2My5jb22CDyoubXVzaWMuMTYzLmNvbTAKBggqhkjOPQQDAwNo
|
||||
ADBlAjEAs5bdgnNP/DiK919RiWscC0kyuY0ugG1C8m8F2Yod4MI3oTyrkVcag21o
|
||||
NSzm802uAjBoPuKEbjjFP4ics0BQdICiVd6WCVAsE69FnlmqRteAJqxvdKGpVLi+
|
||||
Qi3arfomrrc=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,9 +0,0 @@
|
||||
-----BEGIN EC PARAMETERS-----
|
||||
BgUrgQQAIg==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDBfW3twxGaQmMzP9p0/UU5EvHFVCbBw4piVFJ+pm/uFY6CKZkC5LGMa
|
||||
Uc9vn/KiewGgBwYFK4EEACKhZANiAAQTPyU9RQ1pAFMLmozi+c4pEC1rrxAlPGwO
|
||||
9Em+qV+a5qLWgQjjsJeabMqJ/UQ7hDtdKVxWuXiAjMiDcXwL63I71MZKPTAEKXdC
|
||||
mNQwb4kXvRUnoOR4r7BMxEpGlf0CULU=
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -1,144 +0,0 @@
|
||||
From f4f5d11b578a1ab2c3d089bbe5453052b43892bb Mon Sep 17 00:00:00 2001
|
||||
From: tofuliang <tofuliang@gmail.com>
|
||||
Date: Mon, 24 Jan 2022 18:53:11 +0800
|
||||
Subject: [PATCH] fix block ad,add web traffic logs
|
||||
|
||||
---
|
||||
app.go | 1 +
|
||||
config/config.go | 1 +
|
||||
processor/processor.go | 55 +++++++++++++++++++++++++++++++++---------
|
||||
3 files changed, 45 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/app.go b/app.go
|
||||
index 73a6070..1018d75 100644
|
||||
--- a/app.go
|
||||
+++ b/app.go
|
||||
@@ -45,6 +45,7 @@ func main() {
|
||||
log.Println("EnableLocalVip=", *config.EnableLocalVip)
|
||||
log.Println("UnlockSoundEffects=", *config.UnlockSoundEffects)
|
||||
log.Println("QQCookieFile=", *config.QQCookieFile)
|
||||
+ log.Println("LogWebTraffic=", *config.LogWebTraffic)
|
||||
if host.InitHosts() == nil {
|
||||
//go func() {
|
||||
// // // terminal: $ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap
|
||||
diff --git a/config/config.go b/config/config.go
|
||||
index 6c07873..a653cdf 100644
|
||||
--- a/config/config.go
|
||||
+++ b/config/config.go
|
||||
@@ -31,6 +31,7 @@ var (
|
||||
EnableLocalVip = flag.Bool("lv", false, "enable local vip")
|
||||
UnlockSoundEffects = flag.Bool("sef", false, "unlock SoundEffects")
|
||||
QQCookieFile = flag.String("qc", "./qq.cookie", "specify cookies file ,such as : \"qq.cookie\"")
|
||||
+ LogWebTraffic = flag.Bool("wl", false, "log request url and response")
|
||||
)
|
||||
|
||||
func ValidParams() bool {
|
||||
diff --git a/processor/processor.go b/processor/processor.go
|
||||
index 8d09dbf..d07b9d3 100644
|
||||
--- a/processor/processor.go
|
||||
+++ b/processor/processor.go
|
||||
@@ -6,14 +6,6 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/cache"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/common"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/config"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/network"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/processor/crypto"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/provider"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/utils"
|
||||
- "golang.org/x/text/width"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -22,6 +14,15 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
+
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/cache"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/common"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/config"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/network"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/processor/crypto"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/provider"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/utils"
|
||||
+ "golang.org/x/text/width"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -188,6 +189,9 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
|
||||
if ok {
|
||||
code = codeN.String()
|
||||
}
|
||||
+
|
||||
+ logResponse(netease)
|
||||
+
|
||||
if strings.EqualFold(netease.Path, "/api/osx/version") {
|
||||
modified = disableUpdate(netease)
|
||||
} else if strings.Contains(netease.Path, "/usertool/sound/") {
|
||||
@@ -197,9 +201,24 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
|
||||
for key, resp := range netease.JsonBody {
|
||||
if strings.Contains(key, "/usertool/sound/") {
|
||||
modified = unblockSoundEffects(resp.(map[string]interface{}))
|
||||
- } else if *config.BlockAds && strings.Contains(netease.Path, "api/ad/") {
|
||||
+ } else if *config.BlockAds && strings.Contains(key, "api/ad/") {
|
||||
+ log.Println("block Ad has been triggered(" + key + ").")
|
||||
resp = &common.MapType{}
|
||||
modified = true
|
||||
+ } else if *config.BlockAds && strings.EqualFold(key, "/api/v2/banner/get") {
|
||||
+ newInfo := make(common.SliceType, 0)
|
||||
+ info := netease.JsonBody[key]
|
||||
+ for _, data := range info.(common.MapType)["banners"].(common.SliceType) {
|
||||
+ if banner, ok := data.(common.MapType); ok {
|
||||
+ if banner["adid"] == nil {
|
||||
+ newInfo = append(newInfo, banner)
|
||||
+ } else {
|
||||
+ log.Println("block banner Ad has been triggered.")
|
||||
+ modified = true
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ info.(common.MapType)["banners"] = newInfo
|
||||
}
|
||||
}
|
||||
} else if !netease.Web && (code == "401" || code == "512") && strings.Contains(netease.Path, "manipulate") {
|
||||
@@ -220,7 +239,9 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
|
||||
// log.Println("NeedRepackage")
|
||||
modifiedJson, _ := json.Marshal(netease.JsonBody)
|
||||
// log.Println(netease)
|
||||
- // log.Println(string(modifiedJson))
|
||||
+ if *config.LogWebTraffic {
|
||||
+ log.Println("modified =>\n" + string(modifiedJson))
|
||||
+ }
|
||||
if netease.Encrypted {
|
||||
modifiedJson = crypto.AesEncryptECB(modifiedJson, []byte(aeskey))
|
||||
}
|
||||
@@ -258,14 +279,24 @@ func disableUpdate(netease *Netease) bool {
|
||||
if len(value.(common.SliceType)) > 0 {
|
||||
modified = true
|
||||
jsonBody["updateFiles"] = make(common.SliceType, 0)
|
||||
+ log.Println("disable update has been triggered.")
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
- // modifiedJson, _ := json.Marshal(jsonBody)
|
||||
- // log.Println(string(modifiedJson))
|
||||
return modified
|
||||
}
|
||||
+
|
||||
+func logResponse(netease *Netease) {
|
||||
+ if *config.LogWebTraffic {
|
||||
+ reqUrl := netease.Path
|
||||
+ jsonBody := netease.JsonBody
|
||||
+ modifiedJson, _ := json.Marshal(jsonBody)
|
||||
+ sep := "===================================\n"
|
||||
+ log.Println(sep + reqUrl + " => \n" + string(modifiedJson) + "\n")
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
func localVIP(netease *Netease) bool {
|
||||
if !*config.EnableLocalVip {
|
||||
return false
|
||||
@@ -1,29 +0,0 @@
|
||||
From 6c009953d357d1cc03478cf65fc05701fb1966d6 Mon Sep 17 00:00:00 2001
|
||||
From: ameansone <ameansone@outlook.com>
|
||||
Date: Sun, 5 Dec 2021 19:18:20 +0800
|
||||
Subject: [PATCH] fix(processor): avoid unnecessary decryption
|
||||
|
||||
---
|
||||
processor/processor.go | 8 ++++++--
|
||||
1 file changed, 6 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/processor/processor.go b/processor/processor.go
|
||||
index 8d09dbf..011571b 100644
|
||||
--- a/processor/processor.go
|
||||
+++ b/processor/processor.go
|
||||
@@ -177,9 +177,13 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
|
||||
if netease.Forward {
|
||||
aeskey = linuxApiKey
|
||||
}
|
||||
- decryptECBBytes, encrypted := crypto.AesDecryptECB(decryptECBBytes, []byte(aeskey))
|
||||
- netease.Encrypted = encrypted
|
||||
result := utils.ParseJson(decryptECBBytes)
|
||||
+ netease.Encrypted = false;
|
||||
+ if result == nil {
|
||||
+ decryptECBBytes, encrypted := crypto.AesDecryptECB(decryptECBBytes, []byte(aeskey))
|
||||
+ netease.Encrypted = encrypted
|
||||
+ result = utils.ParseJson(decryptECBBytes)
|
||||
+ }
|
||||
netease.JsonBody = result
|
||||
|
||||
modified := false
|
||||
@@ -1,47 +0,0 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=UnblockNeteaseMusic
|
||||
PKG_VERSION:=0.28.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/UnblockNeteaseMusic/server/tar.gz/v${PKG_VERSION}?
|
||||
PKG_HASH:=0a1e843ffaa7b8ce65cc221daec07b63754473f0430019acb228f5b254602318
|
||||
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/UnblockNeteaseMusic
|
||||
SECTION:=multimedia
|
||||
CATEGORY:=Multimedia
|
||||
TITLE:=Revive Netease Cloud Music (NodeJS)
|
||||
URL:=https://github.com/nondanee/UnblockNeteaseMusic
|
||||
DEPENDS:=+node
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Revive Netease Cloud Music (NodeJS)
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
tar -xzvf $(DL_DIR)/$(PKG_SOURCE) -C $(PKG_BUILD_DIR)
|
||||
mkdir -p $(PKG_BUILD_DIR)/$(PKG_NAME)
|
||||
echo -e $(PKG_VERSION) > $(PKG_BUILD_DIR)/$(PKG_NAME)/core_ver
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/share/$(PKG_NAME)
|
||||
$(CP) $(PKG_BUILD_DIR)/$(PKG_NAME)/* $(1)/usr/share/$(PKG_NAME)
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -1,91 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=adguardhome
|
||||
PKG_VERSION:=0.107.79
|
||||
PKG_RELEASE:=1
|
||||
|
||||
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)/AdGuardHome-$(PKG_VERSION)
|
||||
|
||||
FRONTEND_DEST:=$(PKG_NAME)-frontend-$(PKG_VERSION).tar.gz
|
||||
FRONTEND_URL:=https://github.com/AdguardTeam/AdGuardHome/releases/download/v$(PKG_VERSION)/
|
||||
FRONTEND_HASH:=30184c157d4aefe5371b575a43c0ab5a7314aef039a820cc421ea67bab994ce8
|
||||
|
||||
PKG_LICENSE:=GPL-3.0-only
|
||||
PKG_LICENSE_FILES:=LICENSE.txt
|
||||
PKG_CPE_ID:=cpe:/a:adguard:adguardhome
|
||||
PKG_MAINTAINER:=Dobroslaw Kijowski <dobo90@gmail.com>, George Sapkin <george@sapk.in>
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/AdguardTeam/AdGuardHome
|
||||
GO_PKG_BUILD_PKG:=$(GO_PKG)
|
||||
|
||||
GO_PKG_LDFLAGS_X:= \
|
||||
$(GO_PKG)/internal/version.channel=release \
|
||||
$(GO_PKG)/internal/version.version=v$(PKG_VERSION) \
|
||||
$(GO_PKG)/internal/version.committime=$(SOURCE_DATE_EPOCH) \
|
||||
$(GO_PKG)/internal/version.goarm=$(GO_ARM) \
|
||||
$(GO_PKG)/internal/version.gomips=$(GO_MIPS)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-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:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
USERID:=adguardhome=853:adguardhome=853
|
||||
endef
|
||||
|
||||
define Package/adguardhome/conffiles
|
||||
/etc/adguardhome/adguardhome.yaml
|
||||
/etc/config/adguardhome
|
||||
endef
|
||||
|
||||
define Package/adguardhome/description
|
||||
Free and open source, powerful network-wide ads and trackers blocking DNS server.
|
||||
endef
|
||||
|
||||
define Download/adguardhome-frontend
|
||||
URL:=$(FRONTEND_URL)
|
||||
URL_FILE:=AdGuardHome_frontend.tar.gz
|
||||
FILE:=$(FRONTEND_DEST)
|
||||
HASH:=$(FRONTEND_HASH)
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(call Build/Prepare/Default)
|
||||
|
||||
gzip -dc $(DL_DIR)/$(FRONTEND_DEST) | $(HOST_TAR) -C $(PKG_BUILD_DIR)/ $(TAR_OPTIONS)
|
||||
endef
|
||||
|
||||
define Package/adguardhome/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
$(INSTALL_DIR) $(1)/etc/capabilities
|
||||
$(INSTALL_CONF) ./files/adguardhome.json $(1)/etc/capabilities/adguardhome.json
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/adguardhome.conf $(1)/etc/config/adguardhome
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/adguardhome.init $(1)/etc/init.d/adguardhome
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/sysctl.d
|
||||
$(INSTALL_CONF) ./files/adguardhome.sysctl $(1)/etc/sysctl.d/50-adguardhome.conf
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_BIN) ./files/adguardhome.defaults $(1)/etc/uci-defaults/adguardhome
|
||||
endef
|
||||
|
||||
$(eval $(call Download,adguardhome-frontend))
|
||||
$(eval $(call GoBinPackage,adguardhome))
|
||||
$(eval $(call BuildPackage,adguardhome))
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
config adguardhome 'config'
|
||||
option enabled '0'
|
||||
# All paths must be readable by the configured user
|
||||
option config_file '/etc/adguardhome/adguardhome.yaml'
|
||||
# Where to store persistent data by AdGuard Home
|
||||
option work_dir '/var/lib/adguardhome'
|
||||
option user 'adguardhome'
|
||||
option group 'adguardhome'
|
||||
option verbose '0'
|
||||
|
||||
# Files and directories that AdGuard Home has read-only access to
|
||||
# list jail_mount '/etc/ssl/adguardhome.crt'
|
||||
# list jail_mount '/etc/ssl/adguardhome.key'
|
||||
|
||||
# Files and directories that AdGuard Home has read-write access to
|
||||
# list jail_mount_rw '/path/to/dir'
|
||||
|
||||
# Advanced options. Modify at your own risk.
|
||||
|
||||
# More info: https://go.dev/doc/gc-guide#GOGC
|
||||
option gc '0'
|
||||
|
||||
# Max number of OS threads to use
|
||||
# 0 to match the number of CPUs (default)
|
||||
# >0 to explicitly specify concurrency
|
||||
option maxprocs '0'
|
||||
|
||||
# Soft memory limit in MB, 0 to disable
|
||||
option memlimit '0'
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
# Migrate old config format only
|
||||
OLD_CONFIG_FILE=$(uci -q get adguardhome.config.config)
|
||||
OLD_CONFIG_FILE=${OLD_CONFIG_FILE:-/etc/adguardhome.yaml}
|
||||
OLD_CONFIG_NEW_OPT_FILE=$(uci -q get adguardhome.config.config_file)
|
||||
NEW_CONFIG_DIR=/etc/adguardhome
|
||||
NEW_CONFIG_FILE="$NEW_CONFIG_DIR/adguardhome.yaml"
|
||||
|
||||
start_service() {
|
||||
if ! /etc/init.d/adguardhome running; then
|
||||
/etc/init.d/adguardhome start
|
||||
fi
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
if /etc/init.d/adguardhome running; then
|
||||
/etc/init.d/adguardhome stop
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -f "$OLD_CONFIG_NEW_OPT_FILE" ] && [ "$OLD_CONFIG_NEW_OPT_FILE" != "$NEW_CONFIG_FILE" ]; then
|
||||
echo "Old AdGuard Home config found in '$OLD_CONFIG_NEW_OPT_FILE'"
|
||||
|
||||
USER=$(uci -q get adguardhome.config.user)
|
||||
USER=${USER:-adguardhome}
|
||||
|
||||
echo "AdGuard Home config is stored in a non-default path."
|
||||
echo "Ensure configured service user '$USER' can access it."
|
||||
|
||||
elif [ -f "$OLD_CONFIG_FILE" ] && [ "$OLD_CONFIG_FILE" != "$NEW_CONFIG_FILE" ]; then
|
||||
echo "Old AdGuard Home config found in '$OLD_CONFIG_FILE'"
|
||||
OLD_CONFIG_DIR=$(dirname "$OLD_CONFIG_FILE")
|
||||
|
||||
USER=$(uci -q get adguardhome.config.user)
|
||||
USER=${USER:-adguardhome}
|
||||
GROUP=$(uci -q get adguardhome.config.group)
|
||||
GROUP=${GROUP:-adguardhome}
|
||||
|
||||
CUR_CONFIG_FILE="$OLD_CONFIG_FILE"
|
||||
if [ "$OLD_CONFIG_DIR" = "/etc" ]; then
|
||||
echo "AdGuard Home config must be stored in its own directory. Migrating..."
|
||||
stop_service
|
||||
|
||||
echo "Using $USER:$GROUP for file ownership."
|
||||
|
||||
[ -d "$NEW_CONFIG_DIR" ] || mkdir -m 0700 -p "$NEW_CONFIG_DIR"
|
||||
mv "$OLD_CONFIG_FILE" "$NEW_CONFIG_FILE"
|
||||
chown -R "$USER":"$GROUP" "$NEW_CONFIG_DIR"
|
||||
CUR_CONFIG_FILE="$NEW_CONFIG_FILE"
|
||||
|
||||
echo "Config migrated to '$NEW_CONFIG_FILE'"
|
||||
|
||||
else
|
||||
echo "AdGuard Home config is stored in a non-default path."
|
||||
echo "Ensure configured service user '$USER' can access it."
|
||||
fi
|
||||
|
||||
uci set adguardhome.config.config_file="$CUR_CONFIG_FILE"
|
||||
uci -q delete adguardhome.config.config
|
||||
|
||||
# Use awk to split match on :, remove double quotes and trim leading and
|
||||
# trailing spaces
|
||||
cert_path=$(grep certificate_path: "$CUR_CONFIG_FILE" \
|
||||
| awk -F':' '{gsub(/"/, "", $2); gsub(/^ +| +$/, "", $2); print $2}')
|
||||
if [ -n "$cert_path" ]; then
|
||||
echo "Found custom 'certificate_path' pointing to '$cert_path'." \
|
||||
+ "Ensure configured service user '$USER' can access it."
|
||||
|
||||
stop_service
|
||||
|
||||
if ! uci -q show adguardhome.config.jail_mount | grep -q "$cert_path"; then
|
||||
uci add_list adguardhome.config.jail_mount="$cert_path"
|
||||
fi
|
||||
fi
|
||||
|
||||
private_key_path=$(grep private_key_path: "$CUR_CONFIG_FILE" \
|
||||
| awk -F':' '{gsub(/"/, "", $2); gsub(/^ +| +$/, "", $2); print $2}')
|
||||
if [ -n "$private_key_path" ]; then
|
||||
echo "Found custom 'private_key_path' pointing to '$private_key_path'." \
|
||||
+ "Ensure configured service user '$USER' can access it."
|
||||
|
||||
stop_service
|
||||
|
||||
if ! uci -q show adguardhome.config.jail_mount | grep -q "$private_key_path"; then
|
||||
uci add_list adguardhome.config.jail_mount="$private_key_path"
|
||||
fi
|
||||
fi
|
||||
|
||||
uci commit adguardhome
|
||||
start_service
|
||||
|
||||
elif [ -z "$OLD_CONFIG_NEW_OPT_FILE" ] && [ "$OLD_CONFIG_FILE" != "$NEW_CONFIG_FILE" ]; then
|
||||
echo "Old AdGuard Home config not found in '$OLD_CONFIG_FILE'"
|
||||
stop_service
|
||||
|
||||
# Service script will create the new config directory
|
||||
uci set adguardhome.config.config_file="$NEW_CONFIG_FILE"
|
||||
uci -q delete adguardhome.config.config
|
||||
echo "Config path changed to '$NEW_CONFIG_FILE'"
|
||||
|
||||
uci commit adguardhome
|
||||
start_service
|
||||
|
||||
else
|
||||
echo "AdGuard Home config is in its default path '$NEW_CONFIG_FILE'"
|
||||
fi
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# shellcheck disable=SC3043 # ash supports local
|
||||
|
||||
# matches dnsmasq
|
||||
START=19
|
||||
# stops before networking stops
|
||||
STOP=89
|
||||
|
||||
PROG=/usr/bin/AdGuardHome
|
||||
USE_PROCD=1
|
||||
|
||||
boot() {
|
||||
ADGUARDHOME_BOOT=1
|
||||
start "$@"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
if [ -n "$ADGUARDHOME_BOOT" ]; then
|
||||
# Do not start yet, wait for triggers
|
||||
return 0
|
||||
fi
|
||||
|
||||
config_load 'adguardhome'
|
||||
|
||||
local config_name='config'
|
||||
local config_file config group user verbose work_dir workdir
|
||||
local enabled gc maxprocs memlimit
|
||||
|
||||
uci_validate_section 'adguardhome' 'adguardhome' "$config_name" \
|
||||
'enabled:bool:0' \
|
||||
'gc:uinteger:0' \
|
||||
'group:string:adguardhome' \
|
||||
'config:string' \
|
||||
'config_file:string:/etc/adguardhome/adguardhome.yaml' \
|
||||
'jail_mount:list(string)' \
|
||||
'jail_mount_rw:list(string)' \
|
||||
'maxprocs:uinteger:0' \
|
||||
'memlimit:uinteger:0' \
|
||||
'user:string:adguardhome' \
|
||||
'verbose:bool:0' \
|
||||
'workdir:string' \
|
||||
'work_dir:string:/var/lib/adguardhome'
|
||||
|
||||
[ "$enabled" -eq "1" ] || return 1
|
||||
|
||||
# Compatibility with older configs
|
||||
[ -n "$config" ] && config_file="$config"
|
||||
[ -n "$workdir" ] && work_dir="$workdir"
|
||||
|
||||
local config_dir
|
||||
config_dir=$(dirname "$config_file")
|
||||
if [ "$config_dir" = '/etc' ]; then
|
||||
echo 'AdGuard Home config must be stored in its own directory, and not in /etc' >&2
|
||||
return 1
|
||||
fi
|
||||
mkdir -m 0700 -p "$config_dir"
|
||||
chown -R "$user":"$group" "$config_dir"
|
||||
|
||||
mkdir -m 0700 -p "$work_dir"
|
||||
chown -R "$user":"$group" "$work_dir"
|
||||
|
||||
procd_open_instance adguardhome
|
||||
|
||||
procd_set_param command "$PROG"
|
||||
|
||||
[ "$gc" -le 0 ] || procd_append_param env GOGC="$gc"
|
||||
[ "$maxprocs" -le 0 ] || procd_append_param env GOMAXPROCS="$maxprocs"
|
||||
[ "$memlimit" -le 0 ] || procd_append_param env GOMEMLIMIT="$memlimit"
|
||||
|
||||
procd_append_param command --config "$config_file"
|
||||
procd_append_param command --logfile syslog
|
||||
procd_append_param command --no-check-update
|
||||
[ "$verbose" = 1 ] && procd_append_param command --verbose
|
||||
procd_append_param command --work-dir "$work_dir"
|
||||
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param user "$user"
|
||||
procd_set_param group "$group"
|
||||
procd_set_param capabilities '/etc/capabilities/adguardhome.json'
|
||||
procd_set_param no_new_privs 1
|
||||
procd_set_param respawn
|
||||
|
||||
# log is needed for logging to syslog instead of stdout
|
||||
# procfs is needed to readlink /proc/self/exe
|
||||
procd_add_jail adguardhome log procfs
|
||||
|
||||
# config directory must be writable to write new config files
|
||||
procd_add_jail_mount_rw "$config_dir"
|
||||
procd_add_jail_mount_rw "$work_dir"
|
||||
|
||||
procd_add_jail_mount '/etc/hosts'
|
||||
procd_add_jail_mount '/etc/ssl/certs'
|
||||
config_list_foreach "$config_name" jail_mount procd_add_jail_mount
|
||||
config_list_foreach "$config_name" jail_mount_rw procd_add_jail_mount_rw
|
||||
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger adguardhome
|
||||
|
||||
if [ -n "$ADGUARDHOME_BOOT" ]; then
|
||||
# Wait for interfaces to be up before starting AdGuard Home for real.
|
||||
# Prevents issues like https://github.com/openwrt/packages/issues/21868.
|
||||
procd_add_raw_trigger 'interface.*.up' 5000 /etc/init.d/adguardhome restart
|
||||
fi
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"bounding": [
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_NET_RAW"
|
||||
],
|
||||
"effective": [
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_NET_RAW"
|
||||
],
|
||||
"ambient": [
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_NET_RAW"
|
||||
],
|
||||
"permitted": [
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_NET_RAW"
|
||||
],
|
||||
"inheritable": [
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_NET_RAW"
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# quic-go expects larger UDP send/receive buffers
|
||||
net.core.rmem_max = 7500000
|
||||
net.core.wmem_max = 7500000
|
||||
@@ -1,96 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2015-2016 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=alist
|
||||
PKG_VERSION:=3.64.0
|
||||
PKG_WEB_VERSION:=3.64.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/AlistGo/alist/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=599065ec4b26375e4012cf212415f4195c9b92864bdcde4105356261920563f4
|
||||
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
|
||||
|
||||
define Download/$(PKG_NAME)-web
|
||||
FILE:=$(PKG_NAME)-web-$(PKG_WEB_VERSION).tar.gz
|
||||
URL_FILE:=dist.tar.gz
|
||||
URL:=https://github.com/AlistGo/alist-web/releases/download/$(PKG_WEB_VERSION)/
|
||||
HASH:=b80550662de42a2f8a72d35bca8ed66ec9ad0c24a9a02ceec6a01bf5038cf6f4
|
||||
endef
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/alist-org/alist/v3
|
||||
GO_PKG_LDFLAGS:= \
|
||||
-X '$(GO_PKG)/internal/conf.BuiltAt=$(shell date '+%Y-%m-%d %H:%M:%S %z')' \
|
||||
-X '$(GO_PKG)/internal/conf.GoVersion=$(shell $(STAGING_DIR_HOSTPKG)/bin/go version | sed 's/go version //')' \
|
||||
-X '$(GO_PKG)/internal/conf.GitAuthor=Xhofe <i@nn.ci>' \
|
||||
-X '$(GO_PKG)/internal/conf.GitCommit=tarball/$(shell echo $(PKG_HASH) | cut -c 1-7)' \
|
||||
-X '$(GO_PKG)/internal/conf.Version=v$(PKG_VERSION) (OpenWrt $(ARCH_PACKAGES))' \
|
||||
-X '$(GO_PKG)/internal/conf.WebVersion=$(PKG_WEB_VERSION)'
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=A file list program that supports multiple storage
|
||||
URL:=https://alist.nn.ci/
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/alist
|
||||
/etc/config/alist
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
A file list program that supports multiple storage, powered by Gin and Solidjs.
|
||||
endef
|
||||
|
||||
ifeq ($(ARCH),arm)
|
||||
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
||||
ifeq ($(ARM_CPU_FEATURES),)
|
||||
TARGET_CFLAGS:=
|
||||
TARGET_LDFLAGS:=
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(CONFIG_USE_MUSL),)
|
||||
TARGET_CFLAGS += -D_LARGEFILE64_SOURCE
|
||||
endif
|
||||
|
||||
define Build/Prepare
|
||||
$(call Build/Prepare/Default)
|
||||
$(TAR) --strip-components=1 -C $(PKG_BUILD_DIR)/public/dist -xzf $(DL_DIR)/$(PKG_NAME)-web-$(PKG_WEB_VERSION).tar.gz
|
||||
$(SED) 's_https://jsd.nn.ci/gh/alist-org/logo@main/logo.png_/assets/logo.png_g' $(PKG_BUILD_DIR)/public/dist/index.html
|
||||
ifneq ($(CONFIG_ARCH_64BIT),y)
|
||||
$(RM) -rf $(PKG_BUILD_DIR)/drivers/{lark,lark.go}
|
||||
endif
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/alist $(1)/usr/bin
|
||||
$(INSTALL_DIR) $(1)/etc/config $(1)/etc/init.d $(1)/etc/alist
|
||||
$(INSTALL_CONF) $(CURDIR)/files/alist.config $(1)/etc/config/alist
|
||||
$(INSTALL_BIN) $(CURDIR)/files/alist.init $(1)/etc/init.d/alist
|
||||
$(INSTALL_DATA) $(CURDIR)/files/data.db $(1)/etc/alist/data.db
|
||||
endef
|
||||
|
||||
$(eval $(call Download,$(PKG_NAME)-web))
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
config alist
|
||||
option enabled '0'
|
||||
option port '5244'
|
||||
option delayed_start '0'
|
||||
option allow_wan '0'
|
||||
option force '1'
|
||||
option token_expires_in '48'
|
||||
option max_connections '0'
|
||||
option tls_insecure_skip_verify '1'
|
||||
option data_dir '/etc/alist'
|
||||
option temp_dir '/tmp/alist'
|
||||
option log '1'
|
||||
option log_max_size '10'
|
||||
option log_max_backups '5'
|
||||
option log_max_age '28'
|
||||
option log_compress '0'
|
||||
option database_type 'sqlite3'
|
||||
option ssl '0'
|
||||
option download_workers '5'
|
||||
option download_max_retry '1'
|
||||
option transfer_workers '5'
|
||||
option transfer_max_retry '2'
|
||||
option upload_workers '5'
|
||||
option upload_max_retry '0'
|
||||
option copy_workers '5'
|
||||
option copy_max_retry '2'
|
||||
option cors_allow_origins '*'
|
||||
option cors_allow_methods '*'
|
||||
option cors_allow_headers '*'
|
||||
option s3 '0'
|
||||
@@ -1,246 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
START=99
|
||||
USE_PROCD=1
|
||||
PROG=/usr/bin/alist
|
||||
LOG_FILE=/var/log/alist.log
|
||||
|
||||
get_config() {
|
||||
config_get_bool enabled $1 enabled 1
|
||||
config_get port $1 port 5244
|
||||
config_get allow_wan $1 allow_wan 0
|
||||
config_get delayed_start $1 delayed_start 0
|
||||
|
||||
config_get force $1 force 1
|
||||
config_get site_url $1 site_url ""
|
||||
config_get cdn $1 cdn ""
|
||||
config_get jwt_secret $1 jwt_secret ""
|
||||
config_get data_dir $1 data_dir "/etc/alist"
|
||||
config_get temp_dir $1 temp_dir "/tmp/alist"
|
||||
config_get token_expires_in $1 token_expires_in 48
|
||||
config_get max_connections $1 max_connections 0
|
||||
config_get tls_insecure_skip_verify $1 tls_insecure_skip_verify 1
|
||||
|
||||
# log
|
||||
config_get log $1 log 1
|
||||
config_get log_max_size $1 log_max_size 10
|
||||
config_get log_max_backups $1 log_max_backups 5
|
||||
config_get log_max_age $1 log_max_age 28
|
||||
config_get log_compress $1 log_compress 0
|
||||
|
||||
# scheme
|
||||
config_get ssl $1 ssl 0
|
||||
config_get force_https $1 force_https 0
|
||||
config_get ssl_cert $1 ssl_cert ""
|
||||
config_get ssl_key $1 ssl_key ""
|
||||
|
||||
# database
|
||||
config_get database_type $1 database_type "sqlite3"
|
||||
config_get mysql_host $1 mysql_host ""
|
||||
config_get mysql_port $1 mysql_port "3306"
|
||||
config_get mysql_username $1 mysql_username ""
|
||||
config_get mysql_password $1 mysql_password ""
|
||||
config_get mysql_database $1 mysql_database ""
|
||||
config_get mysql_table_prefix $1 mysql_table_prefix "x_"
|
||||
config_get mysql_ssl_mode $1 mysql_ssl_mode ""
|
||||
config_get mysql_dsn $1 mysql_dsn ""
|
||||
|
||||
# tasks
|
||||
config_get download_workers $1 download_workers 5
|
||||
config_get download_max_retry $1 download_max_retry 1
|
||||
config_get transfer_workers $1 transfer_workers 5
|
||||
config_get transfer_max_retry $1 transfer_max_retry 2
|
||||
config_get upload_workers $1 upload_workers 5
|
||||
config_get upload_max_retry $1 upload_max_retry 0
|
||||
config_get copy_workers $1 copy_workers 5
|
||||
config_get copy_max_retry $1 copy_max_retry 2
|
||||
|
||||
# cors
|
||||
config_get cors_allow_origins $1 cors_allow_origins '*'
|
||||
config_get cors_allow_methods $1 cors_allow_methods '*'
|
||||
config_get cors_allow_headers $1 cors_allow_headers '*'
|
||||
|
||||
# s3
|
||||
config_get s3 $1 s3 0
|
||||
config_get s3_port $1 s3_port 5246
|
||||
config_get s3_ssl $1 s3_ssl 0
|
||||
|
||||
config_load network
|
||||
config_get lan_addr lan ipaddr "0.0.0.0"
|
||||
if echo "${lan_addr}" | grep -Fq ' '; then
|
||||
lan_addr="0.0.0.0"
|
||||
else
|
||||
lan_addr=${lan_addr%%/*}
|
||||
fi
|
||||
|
||||
# init jwt_secret
|
||||
[ -z "$jwt_secret" ] && jwt_secret=$(tr -cd "a-zA-Z0-9" < "/dev/urandom" | head -c16)
|
||||
uci -q set alist.@alist[0].jwt_secret="$jwt_secret"
|
||||
uci commit alist
|
||||
}
|
||||
|
||||
set_firewall() {
|
||||
if [ "$external_access" = "allow" ]; then
|
||||
uci -q delete firewall.alist
|
||||
uci set firewall.alist=rule
|
||||
uci set firewall.alist.name="alist"
|
||||
uci set firewall.alist.target="ACCEPT"
|
||||
uci set firewall.alist.src="wan"
|
||||
uci set firewall.alist.proto="tcp"
|
||||
uci set firewall.alist.dest_port="$port"
|
||||
uci set firewall.alist.enabled="1"
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall reload >/dev/null 2>&1
|
||||
elif [ "$external_access" = "deny" ]; then
|
||||
uci -q delete firewall.alist
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall reload >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load alist
|
||||
config_foreach get_config alist
|
||||
[ $enabled -ne 1 ] && return 1
|
||||
mkdir -p "$temp_dir" "$data_dir"
|
||||
[ "$ssl" -eq 1 ] && https_port=$port http_port="-1" || https_port="-1" http_port=$port
|
||||
if [ -e /proc/uptime ]; then
|
||||
[ $(awk -F. '{print $1}' /proc/uptime) -lt "120" ] && delayed_start=$delayed_start || delayed_start=0
|
||||
else
|
||||
delayed_start=$delayed_start
|
||||
fi
|
||||
if [ "$allow_wan" -eq "1" ]; then
|
||||
listen_addr="0.0.0.0"
|
||||
external_access="allow"
|
||||
else
|
||||
listen_addr=$lan_addr
|
||||
external_access="deny"
|
||||
fi
|
||||
|
||||
set_firewall
|
||||
> $LOG_FILE
|
||||
|
||||
# init config
|
||||
json_init
|
||||
json_add_boolean "force" "$force"
|
||||
json_add_string "site_url" "$site_url"
|
||||
json_add_string "cdn" "$cdn"
|
||||
json_add_string "jwt_secret" "$jwt_secret"
|
||||
json_add_int "token_expires_in" "$token_expires_in"
|
||||
|
||||
# database
|
||||
json_add_object 'database'
|
||||
json_add_string "type" "$database_type"
|
||||
json_add_string "host" "$mysql_host"
|
||||
json_add_int "port" "$mysql_port"
|
||||
json_add_string "user" "$mysql_username"
|
||||
json_add_string "password" "$mysql_password"
|
||||
json_add_string "name" "$mysql_database"
|
||||
json_add_string "db_file" "$data_dir/data.db"
|
||||
json_add_string "table_prefix" "$mysql_table_prefix"
|
||||
json_add_string "ssl_mode" "$mysql_ssl_mode"
|
||||
json_add_string "dsn" "$mysql_dsn"
|
||||
json_close_object
|
||||
|
||||
# meilisearch
|
||||
json_add_object "meilisearch"
|
||||
json_add_string "host" "http://localhost:7700"
|
||||
json_add_string "api_key" ""
|
||||
json_add_string "index_prefix" ""
|
||||
json_close_object
|
||||
|
||||
# scheme
|
||||
json_add_object "scheme"
|
||||
json_add_string "address" "$listen_addr"
|
||||
json_add_int "http_port" "$http_port"
|
||||
json_add_int "https_port" "$https_port"
|
||||
json_add_boolean "force_https" "$force_https"
|
||||
json_add_string "cert_file" "$ssl_cert"
|
||||
json_add_string "key_file" "$ssl_key"
|
||||
json_add_string "unix_file" ""
|
||||
json_add_string "unix_file_perm" ""
|
||||
json_close_object
|
||||
|
||||
json_add_string "temp_dir" "$temp_dir"
|
||||
json_add_string "bleve_dir" "$data_dir/bleve"
|
||||
json_add_string "dist_dir" ""
|
||||
|
||||
# log
|
||||
json_add_object "log"
|
||||
json_add_boolean "enable" "$log"
|
||||
json_add_string "name" "$LOG_FILE"
|
||||
json_add_int "max_size" "$log_max_size"
|
||||
json_add_int "max_backups" "$log_max_backups"
|
||||
json_add_int "max_age" "$log_max_age"
|
||||
json_add_boolean "compress" "$log_compress"
|
||||
json_close_object
|
||||
|
||||
json_add_int "delayed_start" "$delayed_start"
|
||||
json_add_int "max_connections" "$max_connections"
|
||||
json_add_boolean "tls_insecure_skip_verify" "$tls_insecure_skip_verify"
|
||||
|
||||
# tasks
|
||||
json_add_object "tasks"
|
||||
json_add_object "download"
|
||||
json_add_int "workers" "$download_workers"
|
||||
json_add_int "max_retry" "$download_max_retry"
|
||||
json_close_object
|
||||
json_add_object "transfer"
|
||||
json_add_int "workers" "$transfer_workers"
|
||||
json_add_int "max_retry" "$transfer_max_retry"
|
||||
json_close_object
|
||||
json_add_object "upload"
|
||||
json_add_int "workers" "$upload_workers"
|
||||
json_add_int "max_retry" "$upload_max_retry"
|
||||
json_close_object
|
||||
json_add_object "copy"
|
||||
json_add_int "workers" "$copy_workers"
|
||||
json_add_int "max_retry" "$copy_max_retry"
|
||||
json_close_object
|
||||
json_close_object
|
||||
|
||||
# cors
|
||||
json_add_object "cors"
|
||||
json_add_array "allow_origins"
|
||||
json_add_string "" "$cors_allow_origins"
|
||||
json_close_array
|
||||
json_add_array "allow_methods"
|
||||
json_add_string "" "$cors_allow_methods"
|
||||
json_close_array
|
||||
json_add_array "allow_headers"
|
||||
json_add_string "" "$cors_allow_headers"
|
||||
json_close_array
|
||||
json_close_object
|
||||
|
||||
# s3
|
||||
json_add_object "s3"
|
||||
json_add_boolean "enable" "$s3"
|
||||
json_add_int "port" "$s3_port"
|
||||
json_add_boolean "ssl" "$s3_ssl"
|
||||
json_close_object
|
||||
|
||||
json_dump > "$data_dir/config.json"
|
||||
|
||||
procd_open_instance alist
|
||||
procd_set_param command $PROG
|
||||
procd_append_param command server
|
||||
procd_append_param command --data "$data_dir"
|
||||
procd_set_param file "$data_dir/config.json"
|
||||
procd_set_param stdout 0
|
||||
procd_set_param stderr 0
|
||||
procd_set_param respawn
|
||||
procd_set_param limits core="unlimited"
|
||||
procd_set_param limits nofile="200000 200000"
|
||||
procd_close_instance alist
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "alist"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
external_access="deny"
|
||||
set_firewall
|
||||
}
|
||||
Binary file not shown.
-10
@@ -1,10 +0,0 @@
|
||||
<svg width="1252" height="1252" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<g>
|
||||
<g id="#70c6beff">
|
||||
<path id="svg_2" d="m634.37,138.38c11.88,-1.36 24.25,1.3 34.18,8.09c14.96,9.66 25.55,24.41 34.49,39.51c40.59,68.03 81.45,135.91 122.02,203.96c54.02,90.99 108.06,181.97 161.94,273.06c37.28,63 74.65,125.96 112.18,188.82c24.72,41.99 50.21,83.54 73.84,126.16c10.18,17.84 15.77,38.44 14.93,59.03c-0.59,15.92 -3.48,32.28 -11.84,46.08c-11.73,19.46 -31.39,33.2 -52.71,40.36c-11.37,4.09 -23.3,6.87 -35.43,6.89c-132.32,-0.05 -264.64,0.04 -396.95,0.03c-11.38,-0.29 -22.95,-1.6 -33.63,-5.72c-7.81,-3.33 -15.5,-7.43 -21.61,-13.42c-10.43,-10.32 -17.19,-24.96 -15.38,-39.83c0.94,-10.39 3.48,-20.64 7.76,-30.16c4.15,-9.77 9.99,-18.67 15.06,-27.97c22.13,-39.47 45.31,-78.35 69.42,-116.65c7.72,-12.05 14.44,-25.07 25.12,-34.87c11.35,-10.39 25.6,-18.54 41.21,-19.6c12.55,-0.52 24.89,3.82 35.35,10.55c11.8,6.92 21.09,18.44 24.2,31.88c4.49,17.01 -0.34,34.88 -7.55,50.42c-8.09,17.65 -19.62,33.67 -25.81,52.18c-1.13,4.21 -2.66,9.52 0.48,13.23c3.19,3 7.62,4.18 11.77,5.22c12,2.67 24.38,1.98 36.59,2.06c45,-0.01 90,0 135,0c8.91,-0.15 17.83,0.3 26.74,-0.22c6.43,-0.74 13.44,-1.79 18.44,-6.28c3.3,-2.92 3.71,-7.85 2.46,-11.85c-2.74,-8.86 -7.46,-16.93 -12.12,-24.89c-119.99,-204.91 -239.31,-410.22 -360.56,-614.4c-3.96,-6.56 -7.36,-13.68 -13.03,-18.98c-2.8,-2.69 -6.95,-4.22 -10.77,-3.11c-3.25,1.17 -5.45,4.03 -7.61,6.57c-5.34,6.81 -10.12,14.06 -14.51,21.52c-20.89,33.95 -40.88,68.44 -61.35,102.64c-117.9,198.43 -235.82,396.85 -353.71,595.29c-7.31,13.46 -15.09,26.67 -23.57,39.43c-7.45,10.96 -16.49,21.23 -28.14,27.83c-13.73,7.94 -30.69,11.09 -46.08,6.54c-11.23,-3.47 -22.09,-9.12 -30.13,-17.84c-10.18,-10.08 -14.69,-24.83 -14.17,-38.94c0.52,-14.86 5.49,-29.34 12.98,-42.1c71.58,-121.59 143.62,-242.92 215.93,-364.09c37.2,-62.8 74.23,-125.69 111.64,-188.36c37.84,-63.5 75.77,-126.94 113.44,-190.54c21.02,-35.82 42.19,-71.56 64.28,-106.74c6.79,-11.15 15.58,-21.15 26.16,-28.85c8.68,-5.92 18.42,-11 29.05,-11.94z" fill="#70c6be"/>
|
||||
</g>
|
||||
<g id="#1ba0d8ff">
|
||||
<path id="svg_3" d="m628.35,608.38c17.83,-2.87 36.72,1.39 51.5,11.78c11.22,8.66 19.01,21.64 21.26,35.65c1.53,10.68 0.49,21.75 -3.44,31.84c-3.02,8.73 -7.35,16.94 -12.17,24.81c-68.76,115.58 -137.5,231.17 -206.27,346.75c-8.8,14.47 -16.82,29.47 -26.96,43.07c-7.37,9.11 -16.58,16.85 -27.21,21.89c-22.47,11.97 -51.79,4.67 -68.88,-13.33c-8.66,-8.69 -13.74,-20.63 -14.4,-32.84c-0.98,-12.64 1.81,-25.42 7.53,-36.69c5.03,-10.96 10.98,-21.45 17.19,-31.77c30.22,-50.84 60.17,-101.84 90.3,-152.73c41.24,-69.98 83.16,-139.55 124.66,-209.37c4.41,-7.94 9.91,-15.26 16.09,-21.9c8.33,-8.46 18.9,-15.3 30.8,-17.16z" fill="#1ba0d8"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,54 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (C) 2017-2020 Yousong Zhou <yszhou4tech@gmail.com>
|
||||
# Copyright (C) 2021-2023 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=aliyundrive-webdav
|
||||
PKG_VERSION:=2.3.3
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/messense/aliyundrive-webdav.git
|
||||
PKG_SOURCE_VERSION:=6e8eba62b4e50acf89681e3a67a3a693186dcd05
|
||||
PKG_MIRROR_HASH:=b6b1099c90818c5eb322bdae105d3df8d7a954d6e9ee7798c3c2412eed528640
|
||||
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_MAINTAINER:=messense <messense@icloud.com>
|
||||
|
||||
PKG_BUILD_DEPENDS:=rust/host
|
||||
|
||||
RUST_PKG_FEATURES:=rustls-tls atomic64 native-tls native-tls-vendored
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/rust/rust-package.mk
|
||||
|
||||
define Package/aliyundrive-webdav
|
||||
SECTION:=multimedia
|
||||
CATEGORY:=Multimedia
|
||||
TITLE:=WebDAV server for AliyunDrive
|
||||
URL:=https://github.com/messense/aliyundrive-webdav
|
||||
DEPENDS:=$$(RUST_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/aliyundrive-webdav/description
|
||||
WebDAV server for AliyunDrive.
|
||||
endef
|
||||
|
||||
define Package/aliyundrive-webdav/conffiles
|
||||
/etc/config/aliyundrive-webdav
|
||||
endef
|
||||
|
||||
define Package/aliyundrive-webdav/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET_ARCH)/release/aliyundrive-webdav $(1)/usr/bin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) $(CURDIR)/files/aliyundrive-webdav.init $(1)/etc/init.d/aliyundrive-webdav
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) $(CURDIR)/files/aliyundrive-webdav.config $(1)/etc/config/aliyundrive-webdav
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,aliyundrive-webdav))
|
||||
@@ -1,21 +0,0 @@
|
||||
config server
|
||||
option enable '0'
|
||||
option debug '0'
|
||||
option refresh_token ''
|
||||
option host '0.0.0.0'
|
||||
option port '8080'
|
||||
option drive_type ''
|
||||
option auth_user ''
|
||||
option auth_password ''
|
||||
option read_buffer_size '10485760'
|
||||
option upload_buffer_size '16777216'
|
||||
option cache_size '1000'
|
||||
option cache_ttl '600'
|
||||
option root '/'
|
||||
option no_trash '0'
|
||||
option read_only '0'
|
||||
option tls_cert ''
|
||||
option tls_key ''
|
||||
option skip_upload_same_size '0'
|
||||
option prefer_http_download '0'
|
||||
option redirect '1'
|
||||
@@ -1,97 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
USE_PROCD=1
|
||||
|
||||
START=99
|
||||
STOP=15
|
||||
|
||||
NAME=aliyundrive-webdav
|
||||
|
||||
uci_get_by_type() {
|
||||
local ret=$(uci get $NAME.@$1[0].$2 2>/dev/null)
|
||||
echo ${ret:=$3}
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local enable=$(uci_get_by_type server enable)
|
||||
case "$enable" in
|
||||
1|on|true|yes|enabled)
|
||||
local refresh_token=$(uci_get_by_type server refresh_token)
|
||||
local auth_user=$(uci_get_by_type server auth_user)
|
||||
local auth_password=$(uci_get_by_type server auth_password)
|
||||
local read_buf_size=$(uci_get_by_type server read_buffer_size 10485760)
|
||||
local upload_buf_size=$(uci_get_by_type server upload_buffer_size 16777216)
|
||||
local cache_size=$(uci_get_by_type server cache_size 1000)
|
||||
local cache_ttl=$(uci_get_by_type server cache_ttl 600)
|
||||
local host=$(uci_get_by_type server host 127.0.0.1)
|
||||
local port=$(uci_get_by_type server port 8080)
|
||||
local root=$(uci_get_by_type server root /)
|
||||
local tls_cert=$(uci_get_by_type server tls_cert)
|
||||
local tls_key=$(uci_get_by_type server tls_key)
|
||||
local drive_type=$(uci_get_by_type server drive_type)
|
||||
|
||||
local extra_options="--auto-index"
|
||||
|
||||
case "$(uci_get_by_type server no_trash 0)" in
|
||||
1|on|true|yes|enabled)
|
||||
extra_options="$extra_options --no-trash"
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
case "$(uci_get_by_type server read_only 0)" in
|
||||
1|on|true|yes|enabled)
|
||||
extra_options="$extra_options --read-only"
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
case "$(uci_get_by_type server skip_upload_same_size 0)" in
|
||||
1|on|true|yes|enabled)
|
||||
extra_options="$extra_options --skip-upload-same-size"
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
case "$(uci_get_by_type server prefer_http_download 0)" in
|
||||
1|on|true|yes|enabled)
|
||||
extra_options="$extra_options --prefer-http-download"
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
case "$(uci_get_by_type server redirect 0)" in
|
||||
1|on|true|yes|enabled)
|
||||
extra_options="$extra_options --redirect"
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
if [[ ! -z "$tls_cert" && ! -z "$tls_key" ]]; then
|
||||
extra_options="$extra_options --tls-cert $tls_cert --tls-key $tls_key"
|
||||
fi
|
||||
|
||||
if [[ ! -z "$drive_type" ]]; then
|
||||
extra_options="$extra_options --drive-type $drive_type"
|
||||
fi
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command /bin/sh -c "/usr/bin/$NAME $extra_options --host $host --port $port --root $root --read-buffer-size $read_buf_size --upload-buffer-size $upload_buf_size --cache-size $cache_size --cache-ttl $cache_ttl --workdir /var/run/$NAME >>/var/log/$NAME.log 2>&1"
|
||||
procd_set_param pidfile /var/run/$NAME.pid
|
||||
procd_set_param env REFRESH_TOKEN="$refresh_token"
|
||||
[[ ! -z "$auth_user" ]] && procd_append_param env WEBDAV_AUTH_USER="$auth_user"
|
||||
[[ ! -z "$auth_password" ]] && procd_append_param env WEBDAV_AUTH_PASSWORD="$auth_password"
|
||||
case $(uci_get_by_type server debug) in
|
||||
1|on|true|yes|enabled)
|
||||
procd_append_param env RUST_LOG="aliyundrive_webdav=debug" ;;
|
||||
*) ;;
|
||||
esac
|
||||
procd_close_instance ;;
|
||||
*)
|
||||
stop_service ;;
|
||||
esac
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "aliyundrive-webdav"
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
menu "Aria2 Configuration"
|
||||
depends on PACKAGE_aria2
|
||||
|
||||
choice
|
||||
prompt "SSL Library"
|
||||
default ARIA2_OPENSSL
|
||||
|
||||
config ARIA2_OPENSSL
|
||||
bool "OpenSSL"
|
||||
|
||||
config ARIA2_GNUTLS
|
||||
bool "GnuTLS"
|
||||
|
||||
config ARIA2_NOSSL
|
||||
bool "No SSL Support"
|
||||
|
||||
endchoice
|
||||
|
||||
choice
|
||||
prompt "Crypto Library"
|
||||
depends on !ARIA2_OPENSSL
|
||||
default ARIA2_NOCRYPTO
|
||||
|
||||
config ARIA2_NETTLE
|
||||
bool "Nettle"
|
||||
|
||||
config ARIA2_LIBGCRYPT
|
||||
bool "Libgcrypt"
|
||||
|
||||
config ARIA2_NOCRYPTO
|
||||
bool "No Crypto Library"
|
||||
|
||||
endchoice
|
||||
|
||||
choice
|
||||
prompt "XML Library"
|
||||
default ARIA2_NOXML
|
||||
|
||||
config ARIA2_LIBXML2
|
||||
bool "Libxml2"
|
||||
|
||||
config ARIA2_EXPAT
|
||||
bool "Expat"
|
||||
|
||||
config ARIA2_NOXML
|
||||
bool "No XML Library"
|
||||
|
||||
endchoice
|
||||
|
||||
config ARIA2_GMP
|
||||
bool "GNU Multiple Precision Arithmetic Library"
|
||||
depends on ARIA2_NETTLE
|
||||
default n
|
||||
|
||||
config ARIA2_BITTORRENT
|
||||
bool "Enable Bittorrent Support"
|
||||
depends on ARIA2_OPENSSL || ARIA2_LIBGCRYPT || \
|
||||
(ARIA2_NETTLE && ARIA2_GMP)
|
||||
default y
|
||||
|
||||
config ARIA2_METALINK
|
||||
bool "Enable Metalink Support"
|
||||
depends on !ARIA2_NOXML
|
||||
default n
|
||||
|
||||
config ARIA2_SFTP
|
||||
bool "Enable SFTP Support"
|
||||
default n
|
||||
|
||||
config ARIA2_ASYNC_DNS
|
||||
bool "Enable Async DNS Support"
|
||||
default n
|
||||
|
||||
config ARIA2_COOKIE
|
||||
bool "Enable Firefox3/Chromium Cookie Support"
|
||||
default n
|
||||
|
||||
config ARIA2_WEBSOCKET
|
||||
bool "Enable JSON-RPC over WebSocket Support"
|
||||
depends on ARIA2_OPENSSL || ARIA2_LIBGCRYPT || ARIA2_NETTLE
|
||||
default y
|
||||
|
||||
endmenu
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2012-2023 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=aria2
|
||||
PKG_VERSION:=1.37.0
|
||||
PKG_RELEASE:=9
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_URL:=https://github.com/aria2/aria2/releases/download/release-$(PKG_VERSION)/
|
||||
PKG_HASH:=skip
|
||||
PKG_INSTALL:=1
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=gc-sections lto
|
||||
|
||||
PKG_MAINTAINER:=Imre Kaloz <kaloz@openwrt.org>, \
|
||||
Hsing-Wang Liao <kuoruan@gmail.com>
|
||||
PKG_LICENSE:=GPLv2
|
||||
PKG_LICENSE_FILES:=COPYING
|
||||
PKG_CPE_ID:=cpe:/a:aria2_project:aria2
|
||||
|
||||
PKG_CONFIG_DEPENDS := \
|
||||
CONFIG_ARIA2_NOSSL \
|
||||
CONFIG_ARIA2_OPENSSL \
|
||||
CONFIG_ARIA2_GNUTLS \
|
||||
CONFIG_ARIA2_NOCRYPTO \
|
||||
CONFIG_ARIA2_NETTLE \
|
||||
CONFIG_ARIA2_LIBGCRYPT \
|
||||
CONFIG_ARIA2_LIBXML2 \
|
||||
CONFIG_ARIA2_EXPAT \
|
||||
CONFIG_ARIA2_GMP \
|
||||
CONFIG_ARIA2_BITTORRENT \
|
||||
CONFIG_ARIA2_METALINK \
|
||||
CONFIG_ARIA2_SFTP \
|
||||
CONFIG_ARIA2_ASYNC_DNS \
|
||||
CONFIG_ARIA2_COOKIE \
|
||||
CONFIG_ARIA2_WEBSOCKET
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/nls.mk
|
||||
|
||||
define Package/aria2-openssl
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=File Transfer
|
||||
TITLE:=lightweight download utility (OpenSSL dependencies)
|
||||
DEPENDS:=+libopenssl +libopenssl-legacy
|
||||
HIDDEN:=1
|
||||
endef
|
||||
|
||||
define Package/aria2/config
|
||||
source "$(SOURCE)/Config.in"
|
||||
endef
|
||||
|
||||
define Package/aria2
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=File Transfer
|
||||
TITLE:=lightweight download utility
|
||||
URL:=https://aria2.github.io/
|
||||
DEPENDS:=+zlib +libstdcpp +ARIA2_OPENSSL:aria2-openssl +ARIA2_GNUTLS:libgnutls \
|
||||
+ARIA2_NETTLE:libnettle +ARIA2_LIBGCRYPT:libgcrypt +ARIA2_GMP:libgmp \
|
||||
+ARIA2_LIBXML2:libxml2 +ARIA2_EXPAT:libexpat +ARIA2_SFTP:libssh2 \
|
||||
+ARIA2_ASYNC_DNS:libcares +ARIA2_COOKIE:libsqlite3
|
||||
USERID:=aria2=6800:aria2=6800
|
||||
endef
|
||||
|
||||
define Package/aria2/description
|
||||
aria2 is a lightweight multi-protocol & multi-source command-line download
|
||||
utility
|
||||
endef
|
||||
|
||||
CONFIGURE_ARGS += \
|
||||
--disable-nls \
|
||||
$(if $(CONFIG_ARIA2_NOSSL),--disable,--enable)-ssl \
|
||||
$(if $(CONFIG_ARIA2_BITTORRENT),--enable,--disable)-bittorrent \
|
||||
$(if $(CONFIG_ARIA2_METALINK),--enable,--disable)-metalink \
|
||||
$(if $(CONFIG_ARIA2_WEBSOCKET),--enable,--disable)-websocket \
|
||||
$(if $(CONFIG_ARIA2_OPENSSL),--with,--without)-openssl \
|
||||
$(if $(CONFIG_ARIA2_GNUTLS),--with,--without)-gnutls \
|
||||
$(if $(CONFIG_ARIA2_NETTLE),--with,--without)-libnettle \
|
||||
$(if $(CONFIG_ARIA2_LIBGCRYPT),--with,--without)-libgcrypt \
|
||||
$(if $(CONFIG_ARIA2_GMP),--with,--without)-libgmp \
|
||||
$(if $(CONFIG_ARIA2_LIBXML2),--with,--without)-libxml2 \
|
||||
$(if $(CONFIG_ARIA2_EXPAT),--with,--without)-libexpat \
|
||||
$(if $(CONFIG_ARIA2_SFTP),--with,--without)-libssh2 \
|
||||
$(if $(CONFIG_ARIA2_ASYNC_DNS),--with,--without)-libcares \
|
||||
$(if $(CONFIG_ARIA2_COOKIE),--with,--without)-sqlite3 \
|
||||
--without-libuv \
|
||||
--with-libz
|
||||
|
||||
define Package/aria2/conffiles
|
||||
/etc/config/aria2
|
||||
endef
|
||||
|
||||
define Download/aria2.conf
|
||||
URL:=https://github.com/P3TERX/aria2.conf/archive
|
||||
URL_FILE:=master.zip
|
||||
FILE:=aria2.conf
|
||||
HASH:=skip
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(call Build/Prepare/Default)
|
||||
unzip $(DL_DIR)/aria2.conf -d $(PKG_BUILD_DIR)/
|
||||
sed -i '/rpc-secret/d' $(PKG_BUILD_DIR)/aria2.conf-master/*
|
||||
[ -x $(PKG_BUILD_DIR)/aria2.conf-master/tracker.sh ] && { cd $(PKG_BUILD_DIR)/aria2.conf-master && sh ./tracker.sh >/dev/null 2>&1; }
|
||||
endef
|
||||
|
||||
define Package/aria2/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/aria2c $(1)/usr/bin
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/aria2.init $(1)/etc/init.d/aria2
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/aria2.conf $(1)/etc/config/aria2
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/aria2
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/aria2.conf-master/* $(1)/usr/share/aria2/
|
||||
endef
|
||||
|
||||
define Package/aria2-openssl/install
|
||||
true
|
||||
endef
|
||||
|
||||
$(eval $(call Download,aria2.conf))
|
||||
$(eval $(call BuildPackage,aria2))
|
||||
$(eval $(call BuildPackage,aria2-openssl))
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
# You can use most aria2 command-line options, replace '-' with '_'.
|
||||
# eg. 'rpc-secret' ==> 'rpc_secret'
|
||||
#
|
||||
# We do not support all options at this time. But you can add any option
|
||||
# with 'list extra_settings'.
|
||||
#
|
||||
# You can also add new config sections to define multi instance.
|
||||
#
|
||||
config aria2 'main'
|
||||
option enabled '0'
|
||||
option user 'aria2'
|
||||
option dir '/mnt/sda1/aria2'
|
||||
option config_dir '/var/etc/aria2'
|
||||
option bt_enable_lpd 'true'
|
||||
option enable_dht 'true'
|
||||
option follow_torrent 'true'
|
||||
option file_allocation 'none'
|
||||
option save_session_interval '30'
|
||||
option split '128'
|
||||
option min_split_size '100K'
|
||||
option max_connection_per_server '128'
|
||||
list extra_settings 'dht-file-path=/usr/share/aria2/dht.dat'
|
||||
@@ -1,364 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2016-2017 Hsing-wang Liao <kuoruan@gmail.com>
|
||||
# Licensed to the public under the Apache License 2.0.
|
||||
|
||||
START=99
|
||||
USE_PROCD=1
|
||||
|
||||
NAME=aria2
|
||||
PROG=/usr/bin/aria2c
|
||||
|
||||
_info() {
|
||||
logger -p daemon.info -t "$NAME" "$*"
|
||||
}
|
||||
|
||||
_err() {
|
||||
logger -p daemon.err -t "$NAME" "$*"
|
||||
}
|
||||
|
||||
_make_dir() {
|
||||
local d
|
||||
for d in "$@"; do
|
||||
if [ ! -d "$d" ]; then
|
||||
mkdir -p "$d" 2>/dev/null || return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_create_file() {
|
||||
touch "$@" 2>/dev/null
|
||||
}
|
||||
|
||||
_change_owner() {
|
||||
local u="$1"; shift
|
||||
|
||||
local d
|
||||
for d in "$@"; do
|
||||
if [ -f "$d" ]; then
|
||||
chown "$u" "$d" 2>/dev/null || return 1
|
||||
elif [ -d "$d" ]; then
|
||||
chown -R "$u" "$d" 2>/dev/null || return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_change_file_mode() {
|
||||
local mod="$1"; shift
|
||||
chmod "$mod" "$@" 2>/dev/null
|
||||
}
|
||||
|
||||
_reset_dir_mode() {
|
||||
local d
|
||||
for d in "$@"; do
|
||||
if [ -d "$d" ]; then
|
||||
find "$d" -type d -exec chmod 755 {} \; 2>/dev/null
|
||||
find "$d" -type f -exec chmod 644 {} \; 2>/dev/null
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
append_options() {
|
||||
local o; local v
|
||||
for o in "$@"; do
|
||||
v="$(eval echo "\$$o")"
|
||||
[ -n "$v" ] && \
|
||||
echo "${o//_/-}=$v" >>"$config_file_tmp"
|
||||
done
|
||||
}
|
||||
|
||||
append_setting() {
|
||||
local s="$1"
|
||||
[ -n "$s" ] && \
|
||||
echo "$s" >>"$config_file_tmp"
|
||||
}
|
||||
|
||||
append_header() {
|
||||
local h="$1"
|
||||
[ -n "$h" ] && \
|
||||
echo "header=\"$h\"" >>"$config_file_tmp"
|
||||
}
|
||||
|
||||
aria2_validate() {
|
||||
uci_load_validate "$NAME" aria2 "$1" "$2" \
|
||||
'enabled:bool:0' \
|
||||
'enable_logging:bool' \
|
||||
'enable_proxy:bool' \
|
||||
'config_dir:string:/var/etc/aria2' \
|
||||
'user:string' \
|
||||
'all_proxy:string' \
|
||||
'all_proxy_passwd:string' \
|
||||
'all_proxy_user:string' \
|
||||
'auto_save_interval:range(0,600)' \
|
||||
'bt_enable_lpd:or("true","false")' \
|
||||
'bt_detach_seed_only:or("true","false")' \
|
||||
'bt_load_saved_metadata:or("true","false")' \
|
||||
'bt_prioritize_piece:string' \
|
||||
'bt_max_open_files:uinteger' \
|
||||
'bt_max_peers:uinteger' \
|
||||
'bt_remove_unselected_file:or("true","false")' \
|
||||
'bt_request_peer_speed_limit:string' \
|
||||
'bt_save_metadata:or("true","false")' \
|
||||
'bt_seed_unverified:or("true","false")' \
|
||||
'bt_stop_timeout:uinteger' \
|
||||
'bt_tracker:list(string)' \
|
||||
'ca_certificate:file' \
|
||||
'certificate:file' \
|
||||
'check_certificate:or("true","false"):true' \
|
||||
'check_integrity:or("true","false")' \
|
||||
'connect_timeout:uinteger' \
|
||||
'dht_listen_port:string' \
|
||||
'dir:string' \
|
||||
'disable_ipv6:or("true","false")' \
|
||||
'disk_cache:string' \
|
||||
'enable_dht:or("true","false"):true' \
|
||||
'enable_dht6:or("true","false")' \
|
||||
'enable_peer_exchange:or("true","false")' \
|
||||
'event_poll:or("epoll","kqueue","port","poll","select")' \
|
||||
'file_allocation:or("none","prealloc","trunc","falloc")' \
|
||||
'follow_torrent:or("true","false","mem")' \
|
||||
'force_save:or("true","false")' \
|
||||
'http_accept_gzip:or("true","false")' \
|
||||
'http_no_cache:or("true","false")' \
|
||||
'listen_port:string' \
|
||||
'log:string' \
|
||||
'log_level:or("debug","info","notice","warn","error")' \
|
||||
'lowest_speed_limit:string' \
|
||||
'max_concurrent_downloads:uinteger' \
|
||||
'max_connection_per_server:uinteger' \
|
||||
'max_download_limit:string' \
|
||||
'max_overall_download_limit:string' \
|
||||
'max_overall_upload_limit:string' \
|
||||
'max_tries:uinteger' \
|
||||
'max_upload_limit:string' \
|
||||
'min_split_size:string' \
|
||||
'pause:or("true","false")' \
|
||||
'pause_metadata:or("true","false")' \
|
||||
'peer_id_prefix:string' \
|
||||
'private_key:file' \
|
||||
'retry_wait:uinteger' \
|
||||
'rpc_auth_method:or("none","user_pass","token")' \
|
||||
'rpc_certificate:file' \
|
||||
'rpc_listen_port:range(1024,65535)' \
|
||||
'rpc_passwd:string' \
|
||||
'rpc_private_key:file' \
|
||||
'rpc_secret:string' \
|
||||
'rpc_secure:or("true","false")' \
|
||||
'rpc_user:string' \
|
||||
'save_session_interval:uinteger' \
|
||||
'seed_ratio:ufloat' \
|
||||
'seed_time:ufloat' \
|
||||
'split:uinteger' \
|
||||
'timeout:uinteger' \
|
||||
'user_agent:string'
|
||||
}
|
||||
|
||||
aria2_start() {
|
||||
local section="$1"
|
||||
[ "$2" = "0" ] || { _err "Validation failed."; return 1; }
|
||||
|
||||
[ "$enabled" = "1" ] || { _info "Instance \"$section\" disabled."; return 1; }
|
||||
[ -n "$dir" ] || { _err "Please set download dir."; return 1; }
|
||||
[ -d "$dir" ] || _make_dir "$dir" || {
|
||||
_err "Can't create download dir: $dir"
|
||||
return 1
|
||||
}
|
||||
|
||||
config_file="$config_dir/$NAME.conf.$section"
|
||||
config_file_tmp="$config_dir/$NAME.conf.tmp"
|
||||
session_file="$config_dir/$NAME.session.$section"
|
||||
|
||||
_make_dir "$config_dir" || {
|
||||
_err "Can't create config dir: $config_dir"
|
||||
return 1
|
||||
}
|
||||
|
||||
_create_file "$session_file" "$config_file" "$config_file_tmp" || {
|
||||
_err "Can't create files: $session_file, $config_file, $config_file_tmp"
|
||||
return 1
|
||||
}
|
||||
|
||||
# create tmp file
|
||||
cat >"$config_file_tmp" <<-EOF
|
||||
# Auto generated file, changes to this file will be lost.
|
||||
EOF
|
||||
|
||||
append_setting "dir=$dir"
|
||||
append_setting "enable-rpc=true"
|
||||
append_setting "rpc-allow-origin-all=true"
|
||||
append_setting "rpc-listen-all=true"
|
||||
append_setting "quiet=true"
|
||||
append_setting "continue=true"
|
||||
append_setting "input-file=$session_file"
|
||||
append_setting "save-session=$session_file"
|
||||
|
||||
if [ -z "$enable_logging" ]; then
|
||||
append_options "log" "log_level"
|
||||
elif [ "$enable_logging" = "1" ]; then
|
||||
log=${log:-"/var/log/aria2.log"}
|
||||
|
||||
local log_dir
|
||||
log_dir="$(dirname "$log")"
|
||||
|
||||
_make_dir "$log_dir" || {
|
||||
_err "Can't create log dir: $log_dir"
|
||||
return 1
|
||||
}
|
||||
|
||||
# create or clear log file
|
||||
echo >"$log"
|
||||
|
||||
append_setting "log=$log"
|
||||
append_options "log_level"
|
||||
fi
|
||||
|
||||
if [ -z "$enable_proxy" ] || [ "$enable_proxy" = "1" ]; then
|
||||
append_options "all_proxy" "all_proxy_user" "all_proxy_passwd"
|
||||
fi
|
||||
|
||||
unset_auth_method() {
|
||||
uci -q batch <<-EOF
|
||||
set $NAME.$section.rpc_auth_method=""
|
||||
commit $NAME
|
||||
EOF
|
||||
}
|
||||
|
||||
if [ -z "$rpc_auth_method" ]; then
|
||||
if [ -n "$rpc_secret" ]; then
|
||||
append_setting "rpc-secret=$rpc_secret"
|
||||
elif [ -n "$rpc_user" ]; then
|
||||
append_setting "rpc-user=$rpc_user"
|
||||
append_setting "rpc-passwd=$rpc_passwd"
|
||||
else
|
||||
_info "It is recommended to set RPC secret."
|
||||
fi
|
||||
elif [ "$rpc_auth_method" = "token" ]; then
|
||||
if [ -n "$rpc_secret" ]; then
|
||||
append_setting "rpc-secret=$rpc_secret"
|
||||
else
|
||||
unset_auth_method
|
||||
fi
|
||||
elif [ "$rpc_auth_method" = "user_pass" ]; then
|
||||
if [ -n "$rpc_user" ]; then
|
||||
append_setting "rpc-user=$rpc_user"
|
||||
append_setting "rpc-passwd=$rpc_passwd"
|
||||
else
|
||||
_info "Please set RPC user."
|
||||
unset_auth_method
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ."$rpc_secure" = ."true" ] && [ -n "$rpc_certificate" ]; then
|
||||
append_setting "rpc-secure=true"
|
||||
append_options "rpc_certificate" "rpc_private_key"
|
||||
fi
|
||||
|
||||
if [ ."$check_certificate" = ."true" ]; then
|
||||
append_setting "check-certificate=true"
|
||||
append_options "ca_certificate"
|
||||
elif [ ."$check_certificate" = ."false" ]; then
|
||||
append_setting "check-certificate=false"
|
||||
fi
|
||||
|
||||
if [ ."$enable_dht" = ."true" ]; then
|
||||
dht_file="$config_dir/dht.dat.$section"
|
||||
_create_file "$dht_file" || {
|
||||
_err "Can't create DHT file: $dht_file"
|
||||
return 1
|
||||
}
|
||||
|
||||
append_setting "enable-dht=true"
|
||||
append_setting "dht-file-path=$dht_file"
|
||||
fi
|
||||
|
||||
if [ ."$enable_dht6" = ."true" ] && [ ."$disable_ipv6" != ."true" ]; then
|
||||
dht6_file="$config_dir/dht6.dat.$section"
|
||||
_create_file "$dht6_file" || {
|
||||
_err "Can't create DHT6 file: $dht6_file"
|
||||
return 1
|
||||
}
|
||||
|
||||
append_setting "enable-dht6=true"
|
||||
append_setting "dht-file-path6=$dht6_file"
|
||||
fi
|
||||
|
||||
if [ -n "$bt_tracker" ]; then
|
||||
local bt_tracker_list; local t
|
||||
for t in $bt_tracker; do
|
||||
if [ -z "$bt_tracker_list" ]; then
|
||||
bt_tracker_list="$t"
|
||||
else
|
||||
bt_tracker_list="$bt_tracker_list,$t"
|
||||
fi
|
||||
done
|
||||
|
||||
append_setting "bt-tracker=$bt_tracker_list"
|
||||
fi
|
||||
|
||||
append_options "auto_save_interval" "bt_enable_lpd" "bt_max_open_files" "bt_max_peers" \
|
||||
"bt_remove_unselected_file" "bt_request_peer_speed_limit" "bt_prioritize_piece" \
|
||||
"bt_stop_timeout" "bt_detach_seed_only" "bt_save_metadata" "bt_load_saved_metadata" \
|
||||
"bt_seed_unverified" "certificate" "check_integrity" "connect_timeout" "dht_listen_port" \
|
||||
"disable_ipv6" "disk_cache" "enable_peer_exchange" "event_poll" "file_allocation" \
|
||||
"follow_torrent" "force_save" "http_accept_gzip" "http_no_cache" "listen_port" \
|
||||
"lowest_speed_limit" "max_concurrent_downloads" "max_connection_per_server" \
|
||||
"max_download_limit" "max_overall_download_limit" "max_overall_upload_limit" "max_tries" \
|
||||
"max_upload_limit" "min_split_size" "pause" "pause_metadata" "peer_id_prefix" "private_key" \
|
||||
"retry_wait" "rpc_listen_port" "save_session_interval" "seed_ratio" "seed_time" "split" "timeout" \
|
||||
"user_agent"
|
||||
|
||||
config_list_foreach "$section" "header" append_header
|
||||
config_list_foreach "$section" "extra_settings" append_setting
|
||||
|
||||
cd /usr/share/aria2 && sh ./tracker.sh >/dev/null 2>&1 &
|
||||
cat /usr/share/aria2/aria2.conf > "$config_file"
|
||||
echo '' >> "$config_file"
|
||||
sed '/^$/d' "$config_file_tmp" >> "$config_file"
|
||||
rm -f "$config_file_tmp"
|
||||
|
||||
_reset_dir_mode "$config_dir"
|
||||
_change_file_mode 600 "$config_file"
|
||||
|
||||
if [ -n "$user" ]; then
|
||||
if ( user_exists "$user" && _change_owner "$user" "$config_dir" "$log" ); then
|
||||
_info "Aria2 will run with user '$user'."
|
||||
if [ "$user" != "root" ]; then
|
||||
_info "Please make sure user '$user' has write access to download dir: $dir"
|
||||
fi
|
||||
else
|
||||
_info "Setting run user to '$user' failed, default user will be used."
|
||||
user=
|
||||
fi
|
||||
fi
|
||||
|
||||
procd_open_instance "$NAME.$section"
|
||||
procd_set_param command "$PROG"
|
||||
procd_append_param command --conf-path="$config_file"
|
||||
|
||||
procd_set_param respawn
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
|
||||
procd_set_param file "$config_file"
|
||||
[ -n "$user" ] && \
|
||||
procd_set_param user "$user"
|
||||
|
||||
procd_add_jail "$NAME.$section" log
|
||||
procd_add_jail_mount "/usr/lib/ossl-modules"
|
||||
procd_add_jail_mount "$ca_certificate" "$certificate" "$rpc_certificate" "$rpc_private_key"
|
||||
procd_add_jail_mount_rw "$dir" "$config_dir" "$log"
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "$NAME"
|
||||
procd_add_validation aria2_validate
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load "$NAME"
|
||||
config_foreach aria2_validate "aria2" aria2_start
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
--- /src/OptionHandlerFactory.cc 2018-05-15 20:33:25.000000000 +0800
|
||||
+++ /src/OptionHandlerFactory.cc 2018-07-05 10:40:25.000000000 +0800
|
||||
@@ -440,7 +440,7 @@
|
||||
{
|
||||
OptionHandler* op(new NumberOptionHandler(PREF_MAX_CONNECTION_PER_SERVER,
|
||||
TEXT_MAX_CONNECTION_PER_SERVER,
|
||||
- "1", 1, 16, 'x'));
|
||||
+ "64", 1, -1, 'x'));
|
||||
op->addTag(TAG_BASIC);
|
||||
op->addTag(TAG_FTP);
|
||||
op->addTag(TAG_HTTP);
|
||||
@@ -501,7 +501,7 @@
|
||||
}
|
||||
{
|
||||
OptionHandler* op(new UnitNumberOptionHandler(
|
||||
- PREF_MIN_SPLIT_SIZE, TEXT_MIN_SPLIT_SIZE, "20M", 1_m, 1_g, 'k'));
|
||||
+ PREF_MIN_SPLIT_SIZE, TEXT_MIN_SPLIT_SIZE, "1M", 1_k, 1_g, 'k'));
|
||||
op->addTag(TAG_BASIC);
|
||||
op->addTag(TAG_FTP);
|
||||
op->addTag(TAG_HTTP);
|
||||
@@ -905,7 +905,7 @@ std::vector<OptionHandler*> OptionHandlerFactory::createOptionHandlers()
|
||||
}
|
||||
{
|
||||
OptionHandler* op(new UnitNumberOptionHandler(
|
||||
- PREF_PIECE_LENGTH, TEXT_PIECE_LENGTH, "1M", 1_m, 1_g));
|
||||
+ PREF_PIECE_LENGTH, TEXT_PIECE_LENGTH, "1M", 1_k, 1_g));
|
||||
op->addTag(TAG_ADVANCED);
|
||||
op->addTag(TAG_FTP);
|
||||
op->addTag(TAG_HTTP);
|
||||
@@ -1,61 +0,0 @@
|
||||
From 66524bee738e98742c908d93c993d0a78a2d9891 Mon Sep 17 00:00:00 2001
|
||||
From: myfreeer <myfreeer@users.noreply.github.com>
|
||||
Date: Sat, 18 Nov 2017 11:55:04 +0800
|
||||
Subject: [PATCH 3/4] download: retry on slow speed and conection close
|
||||
|
||||
This would provide better speed on bad network conditions
|
||||
---
|
||||
src/DownloadCommand.cc | 2 +-
|
||||
src/SocketBuffer.cc | 3 ++-
|
||||
src/SocketCore.cc | 2 +-
|
||||
3 files changed, 4 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/DownloadCommand.cc b/src/DownloadCommand.cc
|
||||
index 2db41e4..f49eb80 100644
|
||||
--- a/src/DownloadCommand.cc
|
||||
+++ b/src/DownloadCommand.cc
|
||||
@@ -306,7 +306,7 @@ void DownloadCommand::checkLowestDownloadSpeed() const
|
||||
startupIdleTime_) {
|
||||
int nowSpeed = peerStat_->calculateDownloadSpeed();
|
||||
if (nowSpeed <= lowestDownloadSpeedLimit_) {
|
||||
- throw DL_ABORT_EX2(fmt(EX_TOO_SLOW_DOWNLOAD_SPEED, nowSpeed,
|
||||
+ throw DL_RETRY_EX2(fmt(EX_TOO_SLOW_DOWNLOAD_SPEED, nowSpeed,
|
||||
lowestDownloadSpeedLimit_,
|
||||
getRequest()->getHost().c_str()),
|
||||
error_code::TOO_SLOW_DOWNLOAD_SPEED);
|
||||
diff --git a/src/SocketBuffer.cc b/src/SocketBuffer.cc
|
||||
index 62862ff..1906173 100644
|
||||
--- a/src/SocketBuffer.cc
|
||||
+++ b/src/SocketBuffer.cc
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
#include "SocketCore.h"
|
||||
#include "DlAbortEx.h"
|
||||
+#include "DlRetryEx.h"
|
||||
#include "message.h"
|
||||
#include "fmt.h"
|
||||
#include "LogFactory.h"
|
||||
@@ -158,7 +159,7 @@ ssize_t SocketBuffer::send()
|
||||
}
|
||||
ssize_t slen = socket_->writeVector(iov, num);
|
||||
if (slen == 0 && !socket_->wantRead() && !socket_->wantWrite()) {
|
||||
- throw DL_ABORT_EX(fmt(EX_SOCKET_SEND, "Connection closed."));
|
||||
+ throw DL_RETRY_EX(fmt(EX_SOCKET_SEND, "Connection closed."));
|
||||
}
|
||||
// A2_LOG_NOTICE(fmt("num=%zu, amount=%d, bufq.size()=%zu, SEND=%d",
|
||||
// num, amount, bufq_.size(), slen));
|
||||
diff --git a/src/SocketCore.cc b/src/SocketCore.cc
|
||||
index 77dc30c..537375a 100644
|
||||
--- a/src/SocketCore.cc
|
||||
+++ b/src/SocketCore.cc
|
||||
@@ -1009,7 +1009,7 @@ bool SocketCore::tlsHandshake(TLSContext* tlsctx, const std::string& hostname)
|
||||
|
||||
if (rv == TLS_ERR_ERROR) {
|
||||
// Damn those error.
|
||||
- throw DL_ABORT_EX(fmt("SSL/TLS handshake failure: %s",
|
||||
+ throw DL_RETRY_EX(fmt("SSL/TLS handshake failure: %s",
|
||||
handshakeError.empty()
|
||||
? tlsSession_->getLastErrorString().c_str()
|
||||
: handshakeError.c_str()));
|
||||
--
|
||||
2.17.1
|
||||
@@ -1,239 +0,0 @@
|
||||
From 8adbc01dc5975a64c55fe594d8c758c71e8183b3 Mon Sep 17 00:00:00 2001
|
||||
From: myfreeer <myfreeer@users.noreply.github.com>
|
||||
Date: Sun, 22 Jul 2018 19:59:02 +0800
|
||||
Subject: [PATCH] option: add option to retry on http 400, 403, 406, or unknown
|
||||
|
||||
--retry-on-400[=true|false] Configure whether retry or not when
|
||||
HTTP server returns 400 Bad Request.
|
||||
Only effective if retry-wait > 0.
|
||||
|
||||
Possible Values: true, false
|
||||
Default: false
|
||||
Tags: #advanced, #http
|
||||
|
||||
--retry-on-403[=true|false] Configure whether retry or not when
|
||||
HTTP server returns 403 Forbidden.
|
||||
Only effective if retry-wait > 0.
|
||||
|
||||
Possible Values: true, false
|
||||
Default: false
|
||||
Tags: #advanced, #http
|
||||
|
||||
--retry-on-406[=true|false] Configure whether retry or not when
|
||||
HTTP server returns 406 Not Acceptable.
|
||||
Only effective if retry-wait > 0.
|
||||
|
||||
Possible Values: true, false
|
||||
Default: false
|
||||
Tags: #advanced, #http
|
||||
|
||||
--retry-on-unknown[=true|false] Configure whether retry or not when
|
||||
HTTP server returns unknown status code.
|
||||
Only effective if retry-wait > 0.
|
||||
|
||||
Possible Values: true, false
|
||||
Default: false
|
||||
Tags: #advanced, #http
|
||||
---
|
||||
src/HttpSkipResponseCommand.cc | 42 +++++++++++++++++++++++++++++-----
|
||||
src/OptionHandlerFactory.cc | 40 ++++++++++++++++++++++++++++++++
|
||||
src/prefs.cc | 8 +++++++
|
||||
src/prefs.h | 8 +++++++
|
||||
src/usage_text.h | 16 +++++++++++++
|
||||
5 files changed, 108 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/src/HttpSkipResponseCommand.cc b/src/HttpSkipResponseCommand.cc
|
||||
index a722d77..de4ad6c 100644
|
||||
--- a/src/HttpSkipResponseCommand.cc
|
||||
+++ b/src/HttpSkipResponseCommand.cc
|
||||
@@ -204,7 +204,7 @@ bool HttpSkipResponseCommand::processResponse()
|
||||
auto statusCode = httpResponse_->getStatusCode();
|
||||
if (statusCode >= 400) {
|
||||
switch (statusCode) {
|
||||
- case 401:
|
||||
+ case 401: // Unauthorized
|
||||
if (getOption()->getAsBool(PREF_HTTP_AUTH_CHALLENGE) &&
|
||||
!httpResponse_->getHttpRequest()->authenticationUsed() &&
|
||||
getDownloadEngine()->getAuthConfigFactory()->activateBasicCred(
|
||||
@@ -213,15 +213,41 @@ bool HttpSkipResponseCommand::processResponse()
|
||||
return prepareForRetry(0);
|
||||
}
|
||||
throw DL_ABORT_EX2(EX_AUTH_FAILED, error_code::HTTP_AUTH_FAILED);
|
||||
- case 404:
|
||||
+ case 404: // Not Found
|
||||
if (getOption()->getAsInt(PREF_MAX_FILE_NOT_FOUND) == 0) {
|
||||
throw DL_ABORT_EX2(MSG_RESOURCE_NOT_FOUND,
|
||||
error_code::RESOURCE_NOT_FOUND);
|
||||
}
|
||||
throw DL_RETRY_EX2(MSG_RESOURCE_NOT_FOUND,
|
||||
error_code::RESOURCE_NOT_FOUND);
|
||||
- case 502:
|
||||
- case 503:
|
||||
+ case 400: // Bad Request
|
||||
+ if (getOption()->getAsBool(PREF_RETRY_ON_400)
|
||||
+ && getOption()->getAsInt(PREF_RETRY_WAIT) > 0) {
|
||||
+ throw DL_RETRY_EX2(fmt(EX_BAD_STATUS, statusCode),
|
||||
+ error_code::HTTP_PROTOCOL_ERROR);
|
||||
+ }
|
||||
+ break;
|
||||
+ case 403: // Forbidden
|
||||
+ if (getOption()->getAsBool(PREF_RETRY_ON_403)
|
||||
+ && getOption()->getAsInt(PREF_RETRY_WAIT) > 0) {
|
||||
+ throw DL_RETRY_EX2(fmt(EX_BAD_STATUS, statusCode),
|
||||
+ error_code::HTTP_PROTOCOL_ERROR);
|
||||
+ }
|
||||
+ break;
|
||||
+ case 406: // Not Acceptable
|
||||
+ if (getOption()->getAsBool(PREF_RETRY_ON_406)
|
||||
+ && getOption()->getAsInt(PREF_RETRY_WAIT) > 0) {
|
||||
+ throw DL_RETRY_EX2(fmt(EX_BAD_STATUS, statusCode),
|
||||
+ error_code::HTTP_PROTOCOL_ERROR);
|
||||
+ }
|
||||
+ break;
|
||||
+ case 408: // Request Timeout
|
||||
+ case 429: // Too Many Requests
|
||||
+ case 502: // Bad Gateway
|
||||
+ case 503: // Service Unavailable
|
||||
+ case 507: // Insufficient Storage
|
||||
+ case 520: // https://github.com/aria2/aria2/issues/1229
|
||||
+ case 521: // https://github.com/aria2/aria2/issues/1229
|
||||
// Only retry if pretry-wait > 0. Hammering 'busy' server is not
|
||||
// a good idea.
|
||||
if (getOption()->getAsInt(PREF_RETRY_WAIT) > 0) {
|
||||
@@ -230,12 +256,16 @@ bool HttpSkipResponseCommand::processResponse()
|
||||
}
|
||||
throw DL_ABORT_EX2(fmt(EX_BAD_STATUS, statusCode),
|
||||
error_code::HTTP_SERVICE_UNAVAILABLE);
|
||||
- case 504:
|
||||
+ case 504: // Gateway Timeout
|
||||
// This is Gateway Timeout, so try again
|
||||
throw DL_RETRY_EX2(fmt(EX_BAD_STATUS, statusCode),
|
||||
error_code::HTTP_SERVICE_UNAVAILABLE);
|
||||
};
|
||||
-
|
||||
+ if (getOption()->getAsBool(PREF_RETRY_ON_UNKNOWN)
|
||||
+ && getOption()->getAsInt(PREF_RETRY_WAIT) > 0) {
|
||||
+ throw DL_RETRY_EX2(fmt(EX_BAD_STATUS, statusCode),
|
||||
+ error_code::HTTP_PROTOCOL_ERROR);
|
||||
+ }
|
||||
throw DL_ABORT_EX2(fmt(EX_BAD_STATUS, statusCode),
|
||||
error_code::HTTP_PROTOCOL_ERROR);
|
||||
}
|
||||
diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc
|
||||
index 5768f7b..decb03e 100644
|
||||
--- a/src/OptionHandlerFactory.cc
|
||||
+++ b/src/OptionHandlerFactory.cc
|
||||
@@ -934,6 +934,46 @@ std::vector<OptionHandler*> OptionHandlerFactory::createOptionHandlers()
|
||||
op->setChangeOptionForReserved(true);
|
||||
handlers.push_back(op);
|
||||
}
|
||||
+ {
|
||||
+ OptionHandler* op(new BooleanOptionHandler(
|
||||
+ PREF_RETRY_ON_400, TEXT_RETRY_ON_400, A2_V_FALSE, OptionHandler::OPT_ARG));
|
||||
+ op->addTag(TAG_ADVANCED);
|
||||
+ op->addTag(TAG_HTTP);
|
||||
+ op->setInitialOption(true);
|
||||
+ op->setChangeGlobalOption(true);
|
||||
+ op->setChangeOptionForReserved(true);
|
||||
+ handlers.push_back(op);
|
||||
+ }
|
||||
+ {
|
||||
+ OptionHandler* op(new BooleanOptionHandler(
|
||||
+ PREF_RETRY_ON_403, TEXT_RETRY_ON_403, A2_V_FALSE, OptionHandler::OPT_ARG));
|
||||
+ op->addTag(TAG_ADVANCED);
|
||||
+ op->addTag(TAG_HTTP);
|
||||
+ op->setInitialOption(true);
|
||||
+ op->setChangeGlobalOption(true);
|
||||
+ op->setChangeOptionForReserved(true);
|
||||
+ handlers.push_back(op);
|
||||
+ }
|
||||
+ {
|
||||
+ OptionHandler* op(new BooleanOptionHandler(
|
||||
+ PREF_RETRY_ON_406, TEXT_RETRY_ON_406, A2_V_FALSE, OptionHandler::OPT_ARG));
|
||||
+ op->addTag(TAG_ADVANCED);
|
||||
+ op->addTag(TAG_HTTP);
|
||||
+ op->setInitialOption(true);
|
||||
+ op->setChangeGlobalOption(true);
|
||||
+ op->setChangeOptionForReserved(true);
|
||||
+ handlers.push_back(op);
|
||||
+ }
|
||||
+ {
|
||||
+ OptionHandler* op(new BooleanOptionHandler(
|
||||
+ PREF_RETRY_ON_UNKNOWN, TEXT_RETRY_ON_UNKNOWN, A2_V_FALSE, OptionHandler::OPT_ARG));
|
||||
+ op->addTag(TAG_ADVANCED);
|
||||
+ op->addTag(TAG_HTTP);
|
||||
+ op->setInitialOption(true);
|
||||
+ op->setChangeGlobalOption(true);
|
||||
+ op->setChangeOptionForReserved(true);
|
||||
+ handlers.push_back(op);
|
||||
+ }
|
||||
{
|
||||
OptionHandler* op(new BooleanOptionHandler(
|
||||
PREF_REUSE_URI, TEXT_REUSE_URI, A2_V_TRUE, OptionHandler::OPT_ARG));
|
||||
diff --git a/src/prefs.cc b/src/prefs.cc
|
||||
index 937e927..33eff91 100644
|
||||
--- a/src/prefs.cc
|
||||
+++ b/src/prefs.cc
|
||||
@@ -327,6 +327,14 @@ PrefPtr PREF_ENABLE_ASYNC_DNS6 = makePref("enable-async-dns6");
|
||||
PrefPtr PREF_MAX_DOWNLOAD_RESULT = makePref("max-download-result");
|
||||
// value: 1*digit
|
||||
PrefPtr PREF_RETRY_WAIT = makePref("retry-wait");
|
||||
+// value: true | false
|
||||
+PrefPtr PREF_RETRY_ON_400 = makePref("retry-on-400");
|
||||
+// value: true | false
|
||||
+PrefPtr PREF_RETRY_ON_403 = makePref("retry-on-403");
|
||||
+// value: true | false
|
||||
+PrefPtr PREF_RETRY_ON_406 = makePref("retry-on-406");
|
||||
+// value: true | false
|
||||
+PrefPtr PREF_RETRY_ON_UNKNOWN = makePref("retry-on-unknown");
|
||||
// value: string
|
||||
PrefPtr PREF_ASYNC_DNS_SERVER = makePref("async-dns-server");
|
||||
// value: true | false
|
||||
diff --git a/src/prefs.h b/src/prefs.h
|
||||
index e1f8397..019e774 100644
|
||||
--- a/src/prefs.h
|
||||
+++ b/src/prefs.h
|
||||
@@ -280,6 +280,14 @@ extern PrefPtr PREF_ENABLE_ASYNC_DNS6;
|
||||
extern PrefPtr PREF_MAX_DOWNLOAD_RESULT;
|
||||
// value: 1*digit
|
||||
extern PrefPtr PREF_RETRY_WAIT;
|
||||
+// value: true | false
|
||||
+extern PrefPtr PREF_RETRY_ON_400;
|
||||
+// value: true | false
|
||||
+extern PrefPtr PREF_RETRY_ON_403;
|
||||
+// value: true | false
|
||||
+extern PrefPtr PREF_RETRY_ON_406;
|
||||
+// value: true | false
|
||||
+extern PrefPtr PREF_RETRY_ON_UNKNOWN;
|
||||
// value: string
|
||||
extern PrefPtr PREF_ASYNC_DNS_SERVER;
|
||||
// value: true | false
|
||||
diff --git a/src/usage_text.h b/src/usage_text.h
|
||||
index d73b50d..75d34a0 100644
|
||||
--- a/src/usage_text.h
|
||||
+++ b/src/usage_text.h
|
||||
@@ -64,6 +64,22 @@
|
||||
_(" --retry-wait=SEC Set the seconds to wait between retries. \n" \
|
||||
" With SEC > 0, aria2 will retry download when the\n" \
|
||||
" HTTP server returns 503 response.")
|
||||
+#define TEXT_RETRY_ON_400 \
|
||||
+ _(" --retry-on-400[=true|false] Configure whether retry or not when\n" \
|
||||
+ " HTTP server returns 400 Bad Request.\n" \
|
||||
+ " Only effective if retry-wait > 0.")
|
||||
+#define TEXT_RETRY_ON_403 \
|
||||
+ _(" --retry-on-403[=true|false] Configure whether retry or not when\n" \
|
||||
+ " HTTP server returns 403 Forbidden.\n" \
|
||||
+ " Only effective if retry-wait > 0.")
|
||||
+#define TEXT_RETRY_ON_406 \
|
||||
+ _(" --retry-on-406[=true|false] Configure whether retry or not when\n" \
|
||||
+ " HTTP server returns 406 Not Acceptable.\n" \
|
||||
+ " Only effective if retry-wait > 0.")
|
||||
+#define TEXT_RETRY_ON_UNKNOWN \
|
||||
+ _(" --retry-on-unknown[=true|false] Configure whether retry or not when\n" \
|
||||
+ " HTTP server returns unknown status code.\n" \
|
||||
+ " Only effective if retry-wait > 0.")
|
||||
#define TEXT_TIMEOUT \
|
||||
_(" -t, --timeout=SEC Set timeout in seconds.")
|
||||
#define TEXT_MAX_TRIES \
|
||||
--
|
||||
2.18.0
|
||||
@@ -1,35 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2010-2011 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=autoshare-ksmbd
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=11
|
||||
PKG_ARCH:=all
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/autoshare-ksmbd
|
||||
TITLE:=Samba autoconfig hotplug script.
|
||||
MAINTAINER:=Lean
|
||||
DEPENDS:=+wsdd2
|
||||
endef
|
||||
|
||||
define Package/autoshare-ksmbd/description
|
||||
A hotplug script to config Samba share automatically.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/autoshare-ksmbd/install
|
||||
$(INSTALL_DIR) $(1)/etc/hotplug.d/block
|
||||
$(INSTALL_BIN) ./files/20-smb-ksmbd $(1)/etc/hotplug.d/block/20-smb-ksmbd
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,autoshare-ksmbd))
|
||||
@@ -1,120 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/functions.sh
|
||||
. /lib/functions/service.sh
|
||||
|
||||
global=0
|
||||
config_file="/etc/config/ksmbd"
|
||||
|
||||
[ ! -f /etc/config/ksmbd ] && {
|
||||
echo "
|
||||
config globals
|
||||
option workgroup 'WORKGROUP'
|
||||
option charset 'UTF-8'
|
||||
option description 'Ksmbd on OpenWRT'
|
||||
" > $config_file
|
||||
}
|
||||
|
||||
wait_for_init() {
|
||||
for i in `seq 30`
|
||||
do
|
||||
[ -e /tmp/procd.done ] || {
|
||||
sleep 1; continue;
|
||||
}
|
||||
return
|
||||
done
|
||||
}
|
||||
|
||||
smb_handle() {
|
||||
local path
|
||||
config_get path "$1" path
|
||||
if [ -n "$path" ]; then
|
||||
case "$path" in
|
||||
"$2" | "$2"/*) global=1 ;;
|
||||
esac
|
||||
case "$2" in
|
||||
"$path" | "$path"/*) global=1 ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
chk_en() {
|
||||
config_get_bool autoshare "$1" autoshare 1
|
||||
[ "$autoshare" -eq 0 ] && exit
|
||||
}
|
||||
|
||||
config_load ksmbd
|
||||
config_foreach chk_en ksmbd
|
||||
|
||||
device=`basename "$DEVPATH"`
|
||||
|
||||
case "$ACTION" in
|
||||
add)
|
||||
|
||||
case "$device" in
|
||||
sd*);;
|
||||
md*);;
|
||||
hd*);;
|
||||
mmcblk*);;
|
||||
nvme*);;
|
||||
*) return;;
|
||||
esac
|
||||
|
||||
path="/dev/$device"
|
||||
|
||||
wait_for_init
|
||||
|
||||
grep -v '/boot\|/opt' /proc/mounts | while read -r j
|
||||
do
|
||||
str=${j%% *}
|
||||
if [ "$str" = "$path" ];then
|
||||
strr=${j#* }
|
||||
target=${strr%% *}
|
||||
global=0
|
||||
|
||||
config_load ksmbd
|
||||
config_foreach smb_handle share "$target"
|
||||
|
||||
name=${target#*/mnt/}
|
||||
name=$(echo "$name" | sed -e "s/^\///")
|
||||
|
||||
if [ "$global" -eq 0 ] ;then
|
||||
echo -e "\n\nconfig share" >> $config_file
|
||||
echo -e "\toption auto '1'" >> $config_file
|
||||
echo -e "\toption name '$name'" >> $config_file
|
||||
echo -e "\toption path '$target'" >> $config_file
|
||||
echo -e "\toption read_only 'no'" >> $config_file
|
||||
echo -e "\toption guest_ok 'yes'" >> $config_file
|
||||
echo -e "\toption create_mask '0666'" >> $config_file
|
||||
echo -e "\toption dir_mask '0777'" >> $config_file
|
||||
echo -e "\toption device '$device'" >> $config_file
|
||||
echo -e "\toption force_root '1'" >> $config_file
|
||||
/etc/init.d/ksmbd reload
|
||||
return
|
||||
fi
|
||||
fi
|
||||
done
|
||||
;;
|
||||
|
||||
remove)
|
||||
i=0
|
||||
while true
|
||||
do
|
||||
if ! uci -q get ksmbd.@share[$i] >/dev/null; then
|
||||
break
|
||||
fi
|
||||
|
||||
dev=$(uci -q get ksmbd.@share[$i].device)
|
||||
if [ "$dev" = "$device" ]; then
|
||||
auto=$(uci -q get ksmbd.@share[$i].auto)
|
||||
if [ "$auto" = "1" ]; then
|
||||
uci delete ksmbd.@share[$i]
|
||||
uci commit ksmbd
|
||||
/etc/init.d/ksmbd reload
|
||||
return
|
||||
fi
|
||||
fi
|
||||
let i+=1
|
||||
done
|
||||
;;
|
||||
esac
|
||||
@@ -1,35 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2010-2011 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=autoshare-samba
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=11
|
||||
PKG_ARCH:=all
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/autoshare-samba
|
||||
TITLE:=Samba autoconfig hotplug script.
|
||||
MAINTAINER:=Lean
|
||||
DEPENDS:=+wsdd2
|
||||
endef
|
||||
|
||||
define Package/autoshare-samba/description
|
||||
A hotplug script to config Samba share automatically.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/autoshare-samba/install
|
||||
$(INSTALL_DIR) $(1)/etc/hotplug.d/block
|
||||
$(INSTALL_BIN) ./files/20-smb $(1)/etc/hotplug.d/block/20-smb
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,autoshare-samba))
|
||||
@@ -1,120 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/functions.sh
|
||||
. /lib/functions/service.sh
|
||||
|
||||
global=0
|
||||
config_file="/etc/config/samba4"
|
||||
|
||||
[ ! -f /etc/config/samba4 ] && {
|
||||
echo "
|
||||
config samba
|
||||
option workgroup 'WORKGROUP'
|
||||
option charset 'UTF-8'
|
||||
option description 'Samba on OpenWRT'
|
||||
" > $config_file
|
||||
}
|
||||
|
||||
wait_for_init() {
|
||||
for i in `seq 30`
|
||||
do
|
||||
[ -e /tmp/procd.done ] || {
|
||||
sleep 1; continue;
|
||||
}
|
||||
return
|
||||
done
|
||||
}
|
||||
|
||||
smb_handle() {
|
||||
local path
|
||||
config_get path "$1" path
|
||||
if [ -n "$path" ]; then
|
||||
case "$path" in
|
||||
"$2" | "$2"/*) global=1 ;;
|
||||
esac
|
||||
case "$2" in
|
||||
"$path" | "$path"/*) global=1 ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
chk_en() {
|
||||
config_get_bool autoshare "$1" autoshare 1
|
||||
[ "$autoshare" -eq 0 ] && exit
|
||||
}
|
||||
|
||||
config_load samba4
|
||||
config_foreach chk_en samba4
|
||||
|
||||
device=`basename "$DEVPATH"`
|
||||
|
||||
case "$ACTION" in
|
||||
add)
|
||||
|
||||
case "$device" in
|
||||
sd*);;
|
||||
md*);;
|
||||
hd*);;
|
||||
mmcblk*);;
|
||||
nvme*);;
|
||||
*) return;;
|
||||
esac
|
||||
|
||||
path="/dev/$device"
|
||||
|
||||
wait_for_init
|
||||
|
||||
grep -v '/boot\|/opt' /proc/mounts | while read -r j
|
||||
do
|
||||
str=${j%% *}
|
||||
if [ "$str" = "$path" ];then
|
||||
strr=${j#* }
|
||||
target=${strr%% *}
|
||||
global=0
|
||||
|
||||
config_load samba4
|
||||
config_foreach smb_handle sambashare "$target"
|
||||
|
||||
name=${target#*/mnt/}
|
||||
name=$(echo "$name" | sed -e "s/^\///")
|
||||
|
||||
if [ "$global" -eq 0 ] ;then
|
||||
echo -e "\n\nconfig sambashare" >> $config_file
|
||||
echo -e "\toption auto '1'" >> $config_file
|
||||
echo -e "\toption name '$name'" >> $config_file
|
||||
echo -e "\toption path '$target'" >> $config_file
|
||||
echo -e "\toption read_only 'no'" >> $config_file
|
||||
echo -e "\toption guest_ok 'yes'" >> $config_file
|
||||
echo -e "\toption create_mask '0666'" >> $config_file
|
||||
echo -e "\toption dir_mask '0777'" >> $config_file
|
||||
echo -e "\toption device '$device'" >> $config_file
|
||||
echo -e "\toption force_root '1'" >> $config_file
|
||||
/etc/init.d/samba4 reload
|
||||
return
|
||||
fi
|
||||
fi
|
||||
done
|
||||
;;
|
||||
|
||||
remove)
|
||||
i=0
|
||||
while true
|
||||
do
|
||||
if ! uci -q get samba4.@sambashare[$i] >/dev/null; then
|
||||
break
|
||||
fi
|
||||
|
||||
dev=$(uci -q get samba4.@sambashare[$i].device)
|
||||
if [ "$dev" = "$device" ]; then
|
||||
auto=$(uci -q get samba4.@sambashare[$i].auto)
|
||||
if [ "$auto" = "1" ]; then
|
||||
uci delete samba4.@sambashare[$i]
|
||||
uci commit samba4
|
||||
/etc/init.d/samba4 reload
|
||||
return
|
||||
fi
|
||||
fi
|
||||
let i+=1
|
||||
done
|
||||
;;
|
||||
esac
|
||||
@@ -1,46 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (C) 2009-2013 OpenWrt.org
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=BaiduPCS-Go
|
||||
PKG_VERSION:=3.9.0
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/qjfoidnh/BaiduPCS-Go/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=de71666e961a3eeb90f8aca7b11934c3f0296d1870e036a7c086ba1decfca8ab
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
|
||||
GO_PKG:=github.com/qjfoidnh/BaiduPCS-Go
|
||||
GO_PKG_LDFLAGS_X:=main.Version=v$(PKG_VERSION)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/BaiduPCS-Go
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Download Manager
|
||||
TITLE:=Terminal utility for Baidu Network Disk
|
||||
URL:=https://github.com/qjfoidnh/BaiduPCS-Go
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
endef
|
||||
|
||||
define Package/BaiduPCS-Go/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/bin/
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/bin/BaiduPCS-Go $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,BaiduPCS-Go))
|
||||
$(eval $(call BuildPackage,BaiduPCS-Go))
|
||||
@@ -1,25 +0,0 @@
|
||||
From: coolsnowwolf <coolsnowwolf@gmail.com>
|
||||
Subject: [PATCH] cachepool: stop using runtime.rawbyteslice
|
||||
|
||||
Recent Go releases reject external linkname references to rawbyteslice.
|
||||
Use the language-provided slice allocator for the zeroed allocation path.
|
||||
|
||||
Signed-off-by: coolsnowwolf <coolsnowwolf@gmail.com>
|
||||
---
|
||||
--- a/pcsutil/cachepool/malloc.go
|
||||
+++ b/pcsutil/cachepool/malloc.go
|
||||
@@ -8,12 +8,9 @@
|
||||
//go:linkname mallocgc runtime.mallocgc
|
||||
func mallocgc(size uintptr, typ uintptr, needzero bool) unsafe.Pointer
|
||||
|
||||
-//go:linkname rawbyteslice runtime.rawbyteslice
|
||||
-func rawbyteslice(size int) (b []byte)
|
||||
-
|
||||
-// RawByteSlice point to runtime.rawbyteslice
|
||||
+// RawByteSlice allocates a zeroed byte slice.
|
||||
func RawByteSlice(size int) (b []byte) {
|
||||
- return rawbyteslice(size)
|
||||
+ return make([]byte, size)
|
||||
}
|
||||
|
||||
// RawMalloc allocates a new slice. The slice is not zeroed.
|
||||
@@ -1,8 +0,0 @@
|
||||
__
|
||||
___ ___ ___ ___ _ ______/ /_
|
||||
/ _ \/ _ \/ -_) _ \ |/|/ / __/ __/
|
||||
\___/ .__/\__/_//_/__,__/_/ \__/
|
||||
/_/
|
||||
--------------------------------
|
||||
%D %V, %C
|
||||
--------------------------------
|
||||
@@ -1,57 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2006-2009 OpenWrt.org
|
||||
# Copyright (C) 2005-2016 Weijie Gao <hackpascal@gmail.com>
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=brlaser
|
||||
PKG_VERSION:=v3
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_URL:=https://github.com/pdewacht/brlaser.git
|
||||
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_FIXUP:=autoreconf
|
||||
|
||||
PKG_INSTALL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/cmake.mk
|
||||
|
||||
define Package/brlaser
|
||||
SECTION:=print
|
||||
CATEGORY:=Printing and Imaging
|
||||
TITLE:=Driver for (some) Brother laster printers
|
||||
DEPENDS:=+libcupsimage +libstdcpp
|
||||
MAINTAINER:=Weijie Gao <hackpascal@gmail.com>
|
||||
URL:=https://github.com/pdewacht/brlaser
|
||||
endef
|
||||
|
||||
define Package/brlaser/description
|
||||
Driver for Brother DCP-7030 / Brother DCP-7055 / Brother DCP-7065DN
|
||||
endef
|
||||
|
||||
define Package/brlaser/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/cups/filter
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/cups/filter/rastertobrlaser $(1)/usr/lib/cups/filter/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/cups/drv
|
||||
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/share/cups/drv/brlaser.drv $(1)/usr/share/cups/drv/
|
||||
|
||||
(cd $(PKG_BUILD_DIR) && ppdc brlaser.drv)
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/ppd/Brother
|
||||
$(CP) $(PKG_BUILD_DIR)/ppd/*.ppd $(1)/usr/share/ppd/Brother
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_BIN) ./files/brlaser-set-cups-filter-permission $(1)/etc/uci-defaults/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,brlaser))
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ -d /usr/lib/cups/filter ] && chmod 0754 /usr/lib/cups/filter/*
|
||||
@@ -1,45 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021-2023 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=brook
|
||||
PKG_VERSION:=20270101
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/txthinking/brook/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=43d8e5476918daa2d35fc63e8b0c94c0c1df8577f1d09103a3ab0f6141f29c0f
|
||||
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/txthinking/brook
|
||||
GO_PKG_BUILD_PKG:=$(GO_PKG)/cli/brook
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/brook
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=A cross-platform proxy software
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
URL:=https://github.com/txthinking/brook
|
||||
endef
|
||||
|
||||
define Package/brook/description
|
||||
Brook is a cross-platform strong encryption and not detectable proxy.
|
||||
Zero-Configuration.
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,brook))
|
||||
$(eval $(call BuildPackage,brook))
|
||||
@@ -1,72 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=chinadns-ng
|
||||
PKG_VERSION:=2025.08.09
|
||||
PKG_RELEASE:=1
|
||||
|
||||
ifeq ($(ARCH),aarch64)
|
||||
ifeq ($(BOARD),rockchip)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@aarch64-linux-musl@generic+v8a@fast+lto
|
||||
PKG_HASH:=3fe0217615dd7060b7287d2b6b31d2a0b364137398bfb335a03bead322eac716
|
||||
else
|
||||
PKG_ARCH:=chinadns-ng+wolfssl_noasm@aarch64-linux-musl@generic+v8a@fast+lto
|
||||
PKG_HASH:=42ddd494200ec6d88b35902927688d316bc23e06e6c08d9e01eb2412196ab845
|
||||
endif
|
||||
else ifeq ($(ARCH),arm)
|
||||
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
||||
ifeq ($(ARM_CPU_FEATURES),)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@arm-linux-musleabi@generic+v6+soft_float@fast+lto
|
||||
PKG_HASH:=0a401d1dc11129481b2baf86f847d55d66bd7e725cba4bf57875fdad27ef0052
|
||||
else
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@arm-linux-musleabihf@generic+v7a@fast+lto
|
||||
PKG_HASH:=dfa1f6ba80fb0925613822f4c4e00df8da68e7b8b772048d26a0d1a9d07d346b
|
||||
endif
|
||||
else ifeq ($(ARCH),mips)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@mips-linux-musl@mips32+soft_float@fast+lto
|
||||
PKG_HASH:=b610821a8f61b0ed3c8c7e82e10d401348a9de17f900988589024a37c4099c8e
|
||||
else ifeq ($(ARCH),mipsel)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@mipsel-linux-musl@mips32+soft_float@fast+lto
|
||||
PKG_HASH:=760544a88724e3b1b9eac79c9400231e81aa8786f8f00a979229e175811ffe6d
|
||||
else ifeq ($(ARCH),mips64)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@mips64-linux-musl@mips64+soft_float@fast+lto
|
||||
PKG_HASH:=2d0fce18a7ef1d74fdc12738767e66998a52c2b30d8790da760933853fe8726e
|
||||
else ifeq ($(ARCH),i386)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@i386-linux-musl@i686@fast+lto
|
||||
PKG_HASH:=85e057dd0a0e8913b30471737436ab8b71834c494ed9f9e53544261b1ffdc8d6
|
||||
else ifeq ($(ARCH),x86_64)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@x86_64-linux-musl@x86_64@fast+lto
|
||||
PKG_HASH:=842ea4e9816efd91d39bc76ead5c4a42e79011757e37c521b4270b675cfcb30c
|
||||
else
|
||||
PKG_HASH:=dummy
|
||||
endif
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_ARCH)
|
||||
PKG_SOURCE_URL:=https://github.com/zfl9/chinadns-ng/releases/download/$(PKG_VERSION)/$(PKG_ARCH)?
|
||||
UNPACK_CMD=$(CP) $(DL_DIR)/$(PKG_SOURCE) $(PKG_BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
PKG_LICENSE:=AGPL-3.0-only
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/chinadns-ng
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=IP Addresses and Names
|
||||
TITLE:=ChinaDNS next generation, refactoring with epoll and ipset.
|
||||
URL:=https://github.com/zfl9/chinadns-ng
|
||||
DEPENDS:=@(aarch64||arm||i386||mips||mipsel||mips64||x86_64) +ipset
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/chinadns-ng/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/chinadns-ng $(1)/usr/bin
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,chinadns-ng))
|
||||
@@ -1,111 +0,0 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=clashoo
|
||||
PKG_SHORT_SHA:=3c947c7
|
||||
PKG_COMMIT_DATE:=2026.09.22
|
||||
|
||||
PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA)
|
||||
PKG_RELEASE:=1
|
||||
|
||||
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:=3c947c76d4f48a938aace935a30a1da1364a1b1b
|
||||
PKG_HASH:=6f557ac33177ca56ca477e22f4bf4196483bb487105ec45c28a4219522c4e652
|
||||
PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA)
|
||||
|
||||
PKG_LICENSE:=GPL3.0+
|
||||
PKG_MAINTAINER:=kenzok8
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
|
||||
|
||||
GO_PKG:=github.com/metacubex/mihomo
|
||||
GO_PKG_LDFLAGS_X:=$(GO_PKG)/constant.Version=$(PKG_BUILD_VERSION) $(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME)
|
||||
GO_PKG_TAGS:=with_gvisor
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/clashoo
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Clashoo (Mihomo)
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle +yq +firewall4 +ip-full +coreutils-nohup +coreutils-timeout +kmod-inet-diag +kmod-nft-socket +kmod-nft-tproxy +kmod-tun +kmod-dummy +unzip
|
||||
PROVIDES:=mihomo clash-meta
|
||||
endef
|
||||
|
||||
define Package/clashoo/description
|
||||
Mihomo proxy core with runtime environment for Clashoo.
|
||||
endef
|
||||
|
||||
define Package/clashoo/conffiles
|
||||
/etc/config/clashoo
|
||||
endef
|
||||
|
||||
define Package/clashoo/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
ln -sf /usr/bin/mihomo $(1)/usr/bin/clash-meta
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_DIR) $(1)/etc/clashoo
|
||||
$(INSTALL_DIR) $(1)/etc/clashoo/provider
|
||||
$(INSTALL_DIR) $(1)/etc/clashoo/proxyprovider
|
||||
$(INSTALL_DIR) $(1)/etc/clashoo/ruleprovider
|
||||
$(INSTALL_DIR) $(1)/usr/share/clashoo
|
||||
$(INSTALL_DIR) $(1)/usr/share/clashbackup
|
||||
$(INSTALL_DIR) $(1)/usr/share/clashoo/config
|
||||
$(INSTALL_DIR) $(1)/usr/share/clashoo/config/sub
|
||||
$(INSTALL_DIR) $(1)/usr/share/clashoo/config/upload
|
||||
$(INSTALL_DIR) $(1)/usr/share/clashoo/config/custom
|
||||
|
||||
$(INSTALL_BIN) $(CURDIR)/files/etc/init.d/clashoo $(1)/etc/init.d/clashoo
|
||||
$(INSTALL_CONF) $(CURDIR)/files/etc/config/clashoo $(1)/etc/config/clashoo
|
||||
$(INSTALL_DATA) $(CURDIR)/files/etc/config/clashoo $(1)/usr/share/clashoo/clashoo.default
|
||||
$(CP) $(CURDIR)/files/usr/share/clashoo/* $(1)/usr/share/clashoo/
|
||||
$(INSTALL_BIN) $(CURDIR)/files/usr/share/clashoo/migrate_lan_acl.sh $(1)/usr/share/clashoo/migrate_lan_acl.sh
|
||||
$(INSTALL_BIN) $(CURDIR)/files/usr/share/clashoo/migrate_fake_ip_filter.sh $(1)/usr/share/clashoo/migrate_fake_ip_filter.sh
|
||||
$(INSTALL_BIN) $(CURDIR)/files/usr/share/clashoo/detect_core_arch.sh $(1)/usr/share/clashoo/detect_core_arch.sh
|
||||
$(INSTALL_BIN) $(CURDIR)/files/usr/share/clashoo/diagnose_bypass.sh $(1)/usr/share/clashoo/diagnose_bypass.sh
|
||||
|
||||
$(RM) $(1)/usr/share/clashoo/dashboard
|
||||
$(RM) $(1)/usr/share/clashoo/yacd
|
||||
$(RM) $(1)/usr/share/clashoo/update.log
|
||||
$(RM) $(1)/usr/share/clashoo/geoip.log
|
||||
$(RM) $(1)/usr/share/clashoo/core_down_complete
|
||||
endef
|
||||
|
||||
define Package/clashoo/postinst
|
||||
#!/bin/sh
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
[ -x /usr/bin/mihomo ] && [ ! -x /usr/bin/clash-meta ] && ln -sf /usr/bin/mihomo /usr/bin/clash-meta
|
||||
rm -f /usr/share/clashoo/check_luci_version.sh /usr/share/clashoo/check_clash_meta_version.sh /usr/share/clashoo/check_mihomo_core_version.sh
|
||||
rm -f /usr/share/clashoo/cuslist.sh /usr/share/clashoo/groups.sh /usr/share/clashoo/load_groups.sh /usr/share/clashoo/panel_diag.sh
|
||||
rm -f /usr/share/clashoo/rmlist.sh /usr/share/clashoo/uplist.sh /usr/share/clashoo/rpc_restart_async.sh /usr/share/clashoo/rpc_update_china_ip_async.sh
|
||||
rm -rf /usr/share/clashoo/create
|
||||
/usr/share/clashoo/migrate_lan_acl.sh >/dev/null 2>&1 || true
|
||||
/usr/share/clashoo/migrate_fake_ip_filter.sh >/dev/null 2>&1 || true
|
||||
/usr/share/clashoo/detect_core_arch.sh >/dev/null 2>&1 || true
|
||||
if [ "$$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ]; then
|
||||
/etc/init.d/clashoo enable 2>/dev/null || true
|
||||
else
|
||||
/etc/init.d/clashoo disable 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(Build/Prepare/Default)
|
||||
$(RM) -r $(PKG_BUILD_DIR)/rules/logic_test
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,clashoo))
|
||||
$(eval $(call BuildPackage,clashoo))
|
||||
@@ -1,156 +0,0 @@
|
||||
|
||||
config clashoo 'config'
|
||||
option redir_port '7891'
|
||||
option http_port '8080'
|
||||
option socks_port '1080'
|
||||
option mixed_port '7890'
|
||||
option enable_ipv6 'true'
|
||||
option enable '0'
|
||||
option dash_port '9090'
|
||||
option dashboard_panel 'zashboard'
|
||||
option level 'info'
|
||||
option core '3'
|
||||
option allow_lan '1'
|
||||
option dnsforwader '1'
|
||||
option subcri 'meta'
|
||||
option cusrule '0'
|
||||
option dnscache '1'
|
||||
option p_mode 'rule'
|
||||
option append_rules '0'
|
||||
option enhanced_mode 'fake-ip'
|
||||
option fake_ip_range '198.18.0.1/16'
|
||||
option fake_ip_range6 'fc00::/18'
|
||||
list fake_ip_filter '+.lan'
|
||||
list fake_ip_filter '+.local'
|
||||
list fake_ip_filter 'localhost.ptlogin2.qq.com'
|
||||
list fake_ip_filter 'rule-set:cn_domain'
|
||||
option fake_ip_filter_migrated '1'
|
||||
option listen_port '1053'
|
||||
option interf '0'
|
||||
option download_core 'amd64-compatible'
|
||||
option dcore '3'
|
||||
option same_tag '1'
|
||||
option geoip_source '3'
|
||||
option geoip_format 'mmdb'
|
||||
option geodata_loader 'standard'
|
||||
option tproxy_port '7982'
|
||||
option access_control '0'
|
||||
option acl_migrated '1'
|
||||
option tcp_mode 'redirect'
|
||||
option tun_mode '0'
|
||||
option core_mirror_prefix 'https://gh-proxy.com/'
|
||||
option core_download_advanced '0'
|
||||
list default_nameserver '223.5.5.5'
|
||||
list default_nameserver '119.29.29.29'
|
||||
option dns_ecs ''
|
||||
option dns_ecs_override '0'
|
||||
option fallback_filter_geoip '0'
|
||||
list fallback_filter_ipcidr '240.0.0.0/4'
|
||||
option dns_respect_rules '1'
|
||||
option dns_loopback_compat '0'
|
||||
option dns_migrated '1'
|
||||
option block_quic '0'
|
||||
option singbox_independent_cache '0'
|
||||
option udp_mode 'tproxy'
|
||||
option ipv4_dns_hijack '1'
|
||||
option ipv6_dns_hijack '1'
|
||||
option ipv4_proxy '1'
|
||||
option ipv6_proxy '1'
|
||||
option fake_ip_ping_hijack '1'
|
||||
option dns_leak_protect '0'
|
||||
option geoip_mmdb_url 'https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb'
|
||||
option geosite_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat'
|
||||
option geoip_dat_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat'
|
||||
option enable_udp '1'
|
||||
option stack 'gvisor'
|
||||
option bypass_china '1'
|
||||
option bypass_china_ipv6 '1'
|
||||
option bypass_port_mode 'common'
|
||||
option sniffer_streaming '1'
|
||||
option selection_cache '1'
|
||||
option fake_ip_cache '1'
|
||||
option enable_dns '1'
|
||||
option disable_quic_gso '1'
|
||||
option smart_policy_priority 'Premium:0.9;SG:1.3'
|
||||
option smart_prefer_asn '1'
|
||||
option smart_uselightgbm '1'
|
||||
option smart_collectdata '1'
|
||||
option smart_collect_size '100'
|
||||
option smart_collect_rate '1'
|
||||
option smart_lgbm_auto_update '1'
|
||||
option smart_lgbm_update_interval '72'
|
||||
option smart_lgbm_url 'https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model.bin'
|
||||
list defaul_nameserver '114.114.114.114'
|
||||
list defaul_nameserver '223.5.5.5'
|
||||
option auto_update '1'
|
||||
option auto_update_time '72'
|
||||
option auto_subscription_update '0'
|
||||
option subscription_update_interval '72'
|
||||
option auto_clear_log '1'
|
||||
option clear_time '72'
|
||||
option core_type 'mihomo'
|
||||
option smart_auto_switch '0'
|
||||
option core_only '0'
|
||||
|
||||
config hosts
|
||||
option enabled '0'
|
||||
option adress '*.clash.dev'
|
||||
option ip '127.0.0.1'
|
||||
|
||||
config hosts
|
||||
option enabled '0'
|
||||
option adress 'alpha.clash.dev'
|
||||
option ip '::1'
|
||||
|
||||
config dnsservers
|
||||
option enabled '1'
|
||||
option ser_type 'nameserver'
|
||||
option ser_address 'https://dns.alidns.com/dns-query'
|
||||
option protocol 'none'
|
||||
|
||||
config dnsservers
|
||||
option enabled '1'
|
||||
option ser_type 'nameserver'
|
||||
option ser_address 'https://doh.pub/dns-query'
|
||||
option protocol 'none'
|
||||
|
||||
config dnsservers
|
||||
option enabled '1'
|
||||
option ser_type 'proxy-server-nameserver'
|
||||
option ser_address '1.1.1.1'
|
||||
option protocol 'tls://'
|
||||
option ser_port '853'
|
||||
|
||||
config dnsservers
|
||||
option enabled '1'
|
||||
option ser_type 'fallback'
|
||||
option ser_address 'https://cloudflare-dns.com/dns-query'
|
||||
option protocol 'none'
|
||||
|
||||
config dnsservers
|
||||
option enabled '1'
|
||||
option ser_type 'fallback'
|
||||
option ser_address 'https://dns.google/dns-query'
|
||||
option protocol 'none'
|
||||
|
||||
config dnsservers
|
||||
option enabled '1'
|
||||
option ser_type 'direct-nameserver'
|
||||
option ser_address '223.5.5.5'
|
||||
option protocol 'udp://'
|
||||
|
||||
config dns_policy
|
||||
option enabled '1'
|
||||
option policy_type 'nameserver-policy'
|
||||
option matcher 'geosite:cn'
|
||||
list nameserver 'https://dns.alidns.com/dns-query'
|
||||
list nameserver 'https://doh.pub/dns-query'
|
||||
|
||||
config authentication
|
||||
|
||||
# default empty custom-rule row so the section shows the input form, not just an add
|
||||
# button; injectors skip rules with an empty ipaaddr, so it generates nothing until filled
|
||||
config addtype
|
||||
option type 'DOMAIN-SUFFIX'
|
||||
option pgroup 'DIRECT'
|
||||
option ipaaddr ''
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,26 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ -n "$(uci -q get clashoo.config.download_core 2>/dev/null)" ] && exit 0
|
||||
|
||||
raw="$(uname -m 2>/dev/null)"
|
||||
[ -n "$raw" ] || raw="$(apk --print-arch 2>/dev/null)"
|
||||
[ -n "$raw" ] || raw="$(opkg status libc 2>/dev/null | awk -F': ' '/^Architecture/{print $2; exit}')"
|
||||
[ -n "$raw" ] || exit 0
|
||||
|
||||
case "$raw" in
|
||||
x86_64|amd64) arch="amd64-compatible" ;;
|
||||
aarch64*|arm64) arch="arm64" ;;
|
||||
armv7*|arm_cortex-a[7-9]*|arm_cortex-a1[0-9]*) arch="armv7" ;;
|
||||
armv6*|arm_cortex-a[56]*) arch="armv6" ;;
|
||||
arm*) arch="armv5" ;;
|
||||
i[3-6]86) arch="386" ;;
|
||||
mips64el*) arch="mips64le" ;;
|
||||
mips64*) arch="mips64" ;;
|
||||
mipsel*) arch="mipsle" ;;
|
||||
mips*) arch="mips" ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
uci -q set clashoo.config.download_core="$arch"
|
||||
uci -q commit clashoo
|
||||
exit 0
|
||||
@@ -1,181 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
umask 077
|
||||
|
||||
CONFIG_YAML="${CONFIG_YAML:-/etc/clashoo/config.yaml}"
|
||||
NFT_V4="${NFT_V4:-/usr/share/clashoo/nftables/geoip_cn.nft}"
|
||||
NFT_V6="${NFT_V6:-/usr/share/clashoo/nftables/geoip6_cn.nft}"
|
||||
MAX_CONN=200
|
||||
|
||||
TMPDIR_D="$(mktemp -d /tmp/.clashoo_diag.XXXXXX 2>/dev/null)" || exit 1
|
||||
trap 'rm -rf "$TMPDIR_D" 2>/dev/null' EXIT INT TERM
|
||||
|
||||
uci_get() {
|
||||
uci -q get "clashoo.config.$1" 2>/dev/null
|
||||
}
|
||||
|
||||
section() {
|
||||
printf '\n===== %s =====\n' "$1"
|
||||
}
|
||||
|
||||
yesno() {
|
||||
[ -n "$1" ] && [ "$1" != "0" ] && [ "$1" != "false" ] && echo "on" || echo "off"
|
||||
}
|
||||
|
||||
bool_on() {
|
||||
case "$1" in
|
||||
1|true|TRUE|yes|on) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
bypass4="$(uci_get bypass_china)"
|
||||
bypass6="$(uci_get bypass_china_ipv6)"
|
||||
[ -n "$bypass6" ] || bypass6="$bypass4"
|
||||
|
||||
section "配置摘要"
|
||||
printf '服务开关 : %s\n' "$(yesno "$(uci_get enable)")"
|
||||
printf '运行模式 : %s\n' "$(uci_get enhanced_mode)"
|
||||
printf 'TCP / UDP : %s / %s\n' "$(uci_get tcp_mode)" "$(uci_get udp_mode)"
|
||||
printf '大陆绕过 v4 : %s\n' "$(yesno "$bypass4")"
|
||||
printf '大陆绕过 v6 : %s%s\n' "$(yesno "$bypass6")" \
|
||||
"$([ -z "$(uci_get bypass_china_ipv6)" ] && echo '(未设置,跟随 v4)')"
|
||||
printf '接管端口 : %s' "$(uci_get bypass_port_mode)"
|
||||
[ "$(uci_get bypass_port_mode)" = "custom" ] && printf ' (%s)' "$(uci_get bypass_port_custom)"
|
||||
printf '\n'
|
||||
printf 'IPv6 代理 : %s\n' "$(yesno "$(uci_get ipv6_proxy)")"
|
||||
printf '阻断 QUIC : %s\n' "$(yesno "$(uci_get block_quic)")"
|
||||
|
||||
if [ "$(uci_get bypass_port_mode)" = "common" ]; then
|
||||
printf '\n注意: 接管端口为 common,只代理 22/53/80/443/8080/8443,\n'
|
||||
printf ' 游戏与 P2P 常用的高位端口不会走代理。\n'
|
||||
fi
|
||||
|
||||
section "fake-ip 过滤"
|
||||
if [ -s "$CONFIG_YAML" ]; then
|
||||
if grep -qE '^[[:space:]]*fake-ip-filter:' "$CONFIG_YAML" 2>/dev/null; then
|
||||
filter_mode="$(uci_get fake_ip_filter_mode)"
|
||||
[ -n "$filter_mode" ] || filter_mode="$(yq -M e '(.dns."fake-ip-filter-mode" // "blacklist") | tostring' "$CONFIG_YAML" 2>/dev/null)"
|
||||
if [ "$filter_mode" = "whitelist" ]; then
|
||||
echo "白名单模式:列表中的域名使用 fake-IP"
|
||||
elif grep -qiE 'rule-set:cn_domain|RULE-SET,[[:space:]]*cn_domain,[[:space:]]*real-ip' "$CONFIG_YAML" 2>/dev/null; then
|
||||
echo "含 rule-set:cn_domain (国内域名返回真实 IP)"
|
||||
else
|
||||
echo "缺少 rule-set:cn_domain"
|
||||
echo " 国内域名会拿到 fake-ip 走代理,与硬编码国内 IP 分成两条路。"
|
||||
fi
|
||||
else
|
||||
echo "配置中无 fake-ip-filter"
|
||||
fi
|
||||
else
|
||||
echo "找不到 $CONFIG_YAML"
|
||||
fi
|
||||
|
||||
section "内核分流规则"
|
||||
if [ -s "$CONFIG_YAML" ]; then
|
||||
echo "-- rules 末尾 5 条 --"
|
||||
grep -E '^[[:space:]]*-[[:space:]]*["'"'"']?(GEOIP|RULE-SET|MATCH|IP-CIDR|DOMAIN)' "$CONFIG_YAML" 2>/dev/null | tail -5
|
||||
fi
|
||||
|
||||
section "IP 库"
|
||||
for f in "$NFT_V4" "$NFT_V6"; do
|
||||
if [ -s "$f" ]; then
|
||||
printf '%-46s %6s 条 %s\n' "$f" \
|
||||
"$(grep -cE '^[[:space:]]*[0-9a-fA-F].*/' "$f" 2>/dev/null)" \
|
||||
"$(date -r "$f" '+%Y-%m-%d %H:%M' 2>/dev/null)"
|
||||
else
|
||||
printf '%-46s 缺失\n' "$f"
|
||||
fi
|
||||
done
|
||||
for f in /etc/clashoo/Country.mmdb /etc/clashoo/geoip.metadb; do
|
||||
[ -f "$f" ] && printf '%-46s %s\n' "$f" "$(ls -lh "$f" 2>/dev/null | awk '{print $5}')"
|
||||
done
|
||||
|
||||
set_count() {
|
||||
nft list set inet fw4 "$1" 2>/dev/null | tr ',' '\n' | grep -cE '[0-9a-fA-F]+[.:].*/'
|
||||
}
|
||||
|
||||
section "防火墙绕过规则"
|
||||
if nft list sets inet fw4 2>/dev/null | grep -q clashoo_china; then
|
||||
for ch in dstnat mangle_prerouting; do
|
||||
nft -a list chain inet fw4 "$ch" 2>/dev/null |
|
||||
grep -E '@clashoo_china6? ' | sed "s|^[[:space:]]*| ${ch}: |"
|
||||
done
|
||||
echo
|
||||
echo "已加载元素数(nft auto-merge 会合并相邻网段,少于文件行数属正常):"
|
||||
bool_on "$bypass4" && printf ' clashoo_china : %s\n' "$(set_count clashoo_china)"
|
||||
bool_on "$bypass6" && printf ' clashoo_china6 : %s\n' "$(set_count clashoo_china6)"
|
||||
else
|
||||
echo "当前 nftables 中没有 clashoo_china 集合(绕过未启用或服务未运行)"
|
||||
fi
|
||||
|
||||
section "绕过命中检查"
|
||||
if ! bool_on "$bypass4" && ! bool_on "$bypass6"; then
|
||||
echo "两个绕过均未启用,所有被接管的流量都交给内核判定,无需检查"
|
||||
else
|
||||
SECRET="$(uci_get dash_pass)"
|
||||
PORT="$(uci_get dash_port)"
|
||||
LAN_IP="$(uci -q get network.lan.ipaddr 2>/dev/null | awk -F/ '{print $1}')"
|
||||
[ -n "$LAN_IP" ] || LAN_IP="127.0.0.1"
|
||||
CONN_JSON="$TMPDIR_D/connections.json"
|
||||
|
||||
if [ -z "$PORT" ]; then
|
||||
echo "未配置控制端口,跳过"
|
||||
elif ! curl -m 5 -s -H "Authorization: Bearer ${SECRET}" \
|
||||
"http://${LAN_IP}:${PORT}/connections" -o "$CONN_JSON" 2>/dev/null; then
|
||||
echo "无法访问内核 API (http://${LAN_IP}:${PORT}),跳过"
|
||||
elif [ ! -s "$CONN_JSON" ]; then
|
||||
echo "内核 API 返回为空,跳过"
|
||||
else
|
||||
jsonfilter -i "$CONN_JSON" -e '@["connections"][*]["metadata"]["destinationIP"]' \
|
||||
2>/dev/null > "${CONN_JSON}.ip"
|
||||
jsonfilter -i "$CONN_JSON" -e '@["connections"][*]["chains"][0]' \
|
||||
2>/dev/null > "${CONN_JSON}.ch"
|
||||
|
||||
total="$(wc -l < "${CONN_JSON}.ip" 2>/dev/null | tr -d ' ')"
|
||||
[ -n "$total" ] || total=0
|
||||
|
||||
if [ "$total" -eq 0 ]; then
|
||||
echo "当前无活动连接"
|
||||
elif [ "$total" != "$(wc -l < "${CONN_JSON}.ch" 2>/dev/null | tr -d ' ')" ]; then
|
||||
echo "内核 API 字段数不一致,跳过"
|
||||
else
|
||||
awk 'NR==FNR { a[FNR] = $0; next } { print a[FNR] "\t" $0 }' \
|
||||
"${CONN_JSON}.ip" "${CONN_JSON}.ch" 2>/dev/null \
|
||||
| awk -F'\t' '$2 != "DIRECT" && $1 != "" && $1 !~ /^(198\.18\.|::|fc00:|fd)/ { print $1 "\t" $2 }' \
|
||||
| sort -u | head -n "$MAX_CONN" > "${CONN_JSON}.chk"
|
||||
|
||||
hit=0
|
||||
: > "${CONN_JSON}.bad"
|
||||
while IFS="$(printf '\t')" read -r dip chain; do
|
||||
[ -n "$dip" ] || continue
|
||||
case "$dip" in
|
||||
*:*) bool_on "$bypass6" || continue
|
||||
set_name=clashoo_china6 ;;
|
||||
*) bool_on "$bypass4" || continue
|
||||
set_name=clashoo_china ;;
|
||||
esac
|
||||
if nft get element inet fw4 "$set_name" "{ $dip }" >/dev/null 2>&1; then
|
||||
hit=$((hit + 1))
|
||||
printf ' %-40s -> %s\n' "$dip" "$chain" >> "${CONN_JSON}.bad"
|
||||
fi
|
||||
done < "${CONN_JSON}.chk"
|
||||
|
||||
printf '活动连接 %s 条,其中走代理的去重目的地 %s 个(上限 %s)\n' \
|
||||
"$total" "$(wc -l < "${CONN_JSON}.chk" 2>/dev/null | tr -d ' ')" "$MAX_CONN"
|
||||
if [ "$hit" -gt 0 ]; then
|
||||
printf '命中绕过白名单却仍走代理: %s 个\n\n' "$hit"
|
||||
head -20 "${CONN_JSON}.bad"
|
||||
echo
|
||||
echo "这些目的地在防火墙白名单内,本应直连,却出现在代理链路上。"
|
||||
echo "可能是连接建立于规则变更之前,或该地址族的绕过规则未生效。"
|
||||
else
|
||||
echo "未发现白名单地址走代理的连接"
|
||||
fi
|
||||
rm -f "${CONN_JSON}.chk" "${CONN_JSON}.bad" 2>/dev/null
|
||||
fi
|
||||
rm -f "${CONN_JSON}.ip" "${CONN_JSON}.ch" 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
@@ -1,385 +0,0 @@
|
||||
#!/usr/bin/ucode
|
||||
|
||||
'use strict';
|
||||
|
||||
import { readfile, writefile } from 'fs';
|
||||
|
||||
// Standalone sing-box config version-migration, used by the init.d core-only
|
||||
// path to upgrade an imported config (momo/homeproxy/etc.) to the format the
|
||||
// current sing-box core accepts, WITHOUT clashoo normalize/takeover.
|
||||
//
|
||||
// IMPORTANT: migrate_singbox() below is kept byte-for-byte in sync with the
|
||||
// copy in luci-app-clashoo/.../luci.clashoo (the UI "migrate" button). If you
|
||||
// change one, change the other.
|
||||
|
||||
function migrate_singbox(cfg) {
|
||||
// --- Fix DNS servers: address-based → type-based, remove detour ---
|
||||
if (cfg.dns && cfg.dns.servers) {
|
||||
cfg.dns.servers = map(cfg.dns.servers || [], function(srv) {
|
||||
let addr = srv.address || '';
|
||||
let new_srv = {};
|
||||
let skip = { address: 1, detour: 1, address_resolver: 1, address_strategy: 1 };
|
||||
for (let k in srv) if (!skip[k]) new_srv[k] = srv[k];
|
||||
|
||||
if (addr === 'fakeip') {
|
||||
new_srv.type = 'fakeip';
|
||||
} else if (addr === 'local' || addr === '') {
|
||||
new_srv.type = new_srv.type || 'local';
|
||||
} else if (match(addr, /^rcode:\/\//) || addr === 'rcode') {
|
||||
new_srv._rcode = true; // mark for removal, handled via dns rule action
|
||||
} else if (match(addr, /^h3:\/\//)) {
|
||||
let host = replace(replace(addr, /^h3:\/\//, ''), /\/.*$/, '');
|
||||
new_srv.type = 'h3'; new_srv.server = host;
|
||||
} else if (match(addr, /^https:\/\//)) {
|
||||
let host = replace(replace(addr, /^https:\/\//, ''), /\/.*$/, '');
|
||||
new_srv.type = 'https'; new_srv.server = host;
|
||||
} else if (match(addr, /^tls:\/\//)) {
|
||||
new_srv.type = 'tls'; new_srv.server = replace(addr, /^tls:\/\//, '');
|
||||
} else if (match(addr, /^tcp:\/\//)) {
|
||||
new_srv.type = 'tcp'; new_srv.server = replace(addr, /^tcp:\/\//, '');
|
||||
} else if (addr && !new_srv.type) {
|
||||
new_srv.type = 'udp'; new_srv.server = addr; // plain IP → UDP
|
||||
} else if (addr) {
|
||||
new_srv.address = addr; // unknown format, keep as-is
|
||||
}
|
||||
return new_srv;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Remove rcode servers (unsupported in 1.12+ new format), convert referencing rules to reject ---
|
||||
let rcode_tags = {};
|
||||
if (cfg.dns && cfg.dns.servers) {
|
||||
let good = [];
|
||||
for (let srv in cfg.dns.servers) {
|
||||
if (srv._rcode) rcode_tags[srv.tag] = true;
|
||||
else push(good, srv);
|
||||
}
|
||||
cfg.dns.servers = good;
|
||||
}
|
||||
if (cfg.dns && cfg.dns.rules && length(keys(rcode_tags)) > 0) {
|
||||
cfg.dns.rules = map(cfg.dns.rules || [], function(rule) {
|
||||
if (rule.server && rcode_tags[rule.server]) {
|
||||
let r = {};
|
||||
for (let k in rule) if (k !== 'server' && k !== 'action') r[k] = rule[k];
|
||||
r.action = 'reject';
|
||||
return r;
|
||||
}
|
||||
return rule;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Remove legacy dns.fakeip.enabled (1.12+ fakeip is configured via type:"fakeip" server) ---
|
||||
if (cfg.dns && cfg.dns.fakeip) {
|
||||
let fp = cfg.dns.fakeip;
|
||||
let new_fp = {};
|
||||
for (let k in fp) if (k !== 'enabled') new_fp[k] = fp[k];
|
||||
if (length(keys(new_fp)) > 0) cfg.dns.fakeip = new_fp;
|
||||
else delete cfg.dns['fakeip'];
|
||||
}
|
||||
|
||||
// --- Fix DNS rules: add action:"route" when server is set ---
|
||||
if (cfg.dns && cfg.dns.rules) {
|
||||
cfg.dns.rules = map(cfg.dns.rules || [], function(rule) {
|
||||
if (rule.server && !rule.action) {
|
||||
let r = {};
|
||||
for (let k in rule) r[k] = rule[k];
|
||||
r.action = 'route';
|
||||
return r;
|
||||
}
|
||||
return rule;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Fix legacy special outbounds (block / dns types) ---
|
||||
let special = {};
|
||||
if (cfg.outbounds) {
|
||||
let kept = [];
|
||||
for (let ob in cfg.outbounds) {
|
||||
if (ob.type === 'block' || ob.type === 'dns') {
|
||||
special[ob.tag] = ob.type;
|
||||
} else {
|
||||
push(kept, ob);
|
||||
}
|
||||
}
|
||||
cfg.outbounds = kept;
|
||||
}
|
||||
|
||||
// Patch route rules that pointed to removed special outbounds
|
||||
if (cfg.route && cfg.route.rules && length(keys(special)) > 0) {
|
||||
cfg.route.rules = map(cfg.route.rules || [], function(rule) {
|
||||
let sp = rule.outbound && special[rule.outbound];
|
||||
if (!sp) return rule;
|
||||
let r = {};
|
||||
for (let k in rule) if (k !== 'outbound') r[k] = rule[k];
|
||||
r.action = (sp === 'dns') ? 'hijack-dns' : 'reject';
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Migrate geoip/geosite database refs to rule_set (removed in sing-box 1.12) ---
|
||||
let needed_rule_sets = {};
|
||||
|
||||
let migrate_geo_rule = function(rule) {
|
||||
let has_geo = rule.geoip || rule.geosite;
|
||||
if (!has_geo) return rule;
|
||||
let r = {};
|
||||
let new_rs = [];
|
||||
for (let k in rule) {
|
||||
if (k === 'geoip') {
|
||||
for (let n in rule[k]) {
|
||||
let tag = 'geoip-' + n;
|
||||
needed_rule_sets[tag] = 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geoip@rule-set/geoip-' + n + '.srs';
|
||||
push(new_rs, tag);
|
||||
}
|
||||
} else if (k === 'geosite') {
|
||||
for (let n in rule[k]) {
|
||||
let tag = 'geosite-' + n;
|
||||
needed_rule_sets[tag] = 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-' + n + '.srs';
|
||||
push(new_rs, tag);
|
||||
}
|
||||
} else {
|
||||
r[k] = rule[k];
|
||||
}
|
||||
}
|
||||
// Merge new rule_set tags with any existing ones
|
||||
let existing_rs = type(r.rule_set) === 'array' ? r.rule_set :
|
||||
(r.rule_set ? [r.rule_set] : []);
|
||||
for (let t in new_rs) push(existing_rs, t);
|
||||
r.rule_set = existing_rs;
|
||||
return r;
|
||||
};
|
||||
|
||||
if (cfg.route && cfg.route.rules)
|
||||
cfg.route.rules = map(cfg.route.rules, migrate_geo_rule);
|
||||
if (cfg.dns && cfg.dns.rules)
|
||||
cfg.dns.rules = map(cfg.dns.rules, migrate_geo_rule);
|
||||
|
||||
// download_detour must be DIRECT: at first start rule-sets aren't loaded, so
|
||||
// routing it via a proxy deadlocks (DNS recursion -> sing-box FATAL).
|
||||
let pick_dl_detour = function() {
|
||||
if (cfg.outbounds) {
|
||||
for (let ob in cfg.outbounds) {
|
||||
if (ob && ob.type === 'direct' && ob.tag) return ob.tag;
|
||||
}
|
||||
}
|
||||
return 'DIRECT';
|
||||
};
|
||||
|
||||
// Add rule_set download entries for each referenced geo tag
|
||||
if (length(keys(needed_rule_sets)) > 0) {
|
||||
if (!cfg.route) cfg.route = {};
|
||||
if (!cfg.route.rule_set) cfg.route.rule_set = [];
|
||||
let existing_tags = {};
|
||||
for (let rs in cfg.route.rule_set) existing_tags[rs.tag] = true;
|
||||
let dl_detour = pick_dl_detour();
|
||||
for (let tag in keys(needed_rule_sets)) {
|
||||
if (!existing_tags[tag]) {
|
||||
push(cfg.route.rule_set, {
|
||||
tag: tag,
|
||||
type: 'remote',
|
||||
format: 'binary',
|
||||
url: needed_rule_sets[tag],
|
||||
download_detour: dl_detour
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Remove deprecated 'outbound' DNS rule items (fatal in sing-box 1.13) ---
|
||||
// Replacement: set route.default_domain_resolver to the plain IP-based resolver
|
||||
let dns_resolver_tag = '';
|
||||
if (cfg.dns && cfg.dns.servers) {
|
||||
for (let srv in cfg.dns.servers) {
|
||||
if ((srv.type === 'udp' || srv.type === 'local') && srv.server &&
|
||||
match(srv.server, /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
|
||||
dns_resolver_tag = srv.tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cfg.dns && cfg.dns.rules) {
|
||||
let filtered = [];
|
||||
for (let rule in cfg.dns.rules) {
|
||||
if (!rule.outbound) push(filtered, rule);
|
||||
}
|
||||
cfg.dns.rules = filtered;
|
||||
}
|
||||
// Set route.default_domain_resolver (replaces outbound:any DNS routing)
|
||||
if (dns_resolver_tag) {
|
||||
if (!cfg.route) cfg.route = {};
|
||||
if (!cfg.route.default_domain_resolver)
|
||||
cfg.route.default_domain_resolver = dns_resolver_tag;
|
||||
}
|
||||
|
||||
// --- Fix DNS servers with domain-name server field: add domain_resolver ---
|
||||
// sing-box 1.12+ requires domain_resolver when server= is a hostname (not IP)
|
||||
if (cfg.dns && cfg.dns.servers && dns_resolver_tag) {
|
||||
let domain_types = { https: 1, h3: 1, tls: 1, tcp: 1 };
|
||||
cfg.dns.servers = map(cfg.dns.servers, function(srv) {
|
||||
if (!domain_types[srv.type]) return srv;
|
||||
if (!srv.server || match(srv.server, /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) return srv;
|
||||
if (srv.domain_resolver) return srv;
|
||||
let r = {};
|
||||
for (let k in srv) r[k] = srv[k];
|
||||
r.domain_resolver = dns_resolver_tag;
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Fix DNS rules referencing non-existent server tags → action: reject ---
|
||||
if (cfg.dns && cfg.dns.servers && cfg.dns.rules) {
|
||||
let valid_servers = {};
|
||||
for (let srv in cfg.dns.servers) valid_servers[srv.tag] = true;
|
||||
cfg.dns.rules = map(cfg.dns.rules, function(rule) {
|
||||
if (!rule.server || valid_servers[rule.server]) return rule;
|
||||
let r = {};
|
||||
for (let k in rule) if (k !== 'server' && k !== 'action') r[k] = rule[k];
|
||||
r.action = 'reject';
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Fix reject method: sing-box 1.14 rejects "dropped", only "default"/"drop" ---
|
||||
if (cfg.dns && cfg.dns.rules) {
|
||||
cfg.dns.rules = map(cfg.dns.rules, function(rule) {
|
||||
if (rule.action !== 'reject' || !rule.method) return rule;
|
||||
let m = rule.method;
|
||||
if (m === 'dropped') m = 'drop';
|
||||
if (m !== 'default' && m !== 'drop') {
|
||||
// unknown value: drop it, sing-box defaults to NXDOMAIN
|
||||
let r = {};
|
||||
for (let k in rule) if (k !== 'method') r[k] = rule[k];
|
||||
return r;
|
||||
}
|
||||
if (m === rule.method) return rule;
|
||||
let r = {};
|
||||
for (let k in rule) r[k] = rule[k];
|
||||
r.method = m;
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Migrate TUN inet4_address/inet6_address -> address array (1.12+), and
|
||||
// strip legacy inbound fields removed in 1.13 (sniff / sniff_override_destination /
|
||||
// sniff_timeout / domain_strategy). If any inbound had sniff on, prepend a
|
||||
// { "action": "sniff" } route rule as the equivalent. ---
|
||||
let had_sniff = false;
|
||||
if (cfg.inbounds) {
|
||||
cfg.inbounds = map(cfg.inbounds, function(ib) {
|
||||
let r = {};
|
||||
let addrs = [];
|
||||
for (let k in ib) {
|
||||
if (k === 'inet4_address') {
|
||||
if (type(ib[k]) === 'array') { for (let a in ib[k]) push(addrs, a); }
|
||||
else push(addrs, ib[k]);
|
||||
} else if (k === 'inet6_address') {
|
||||
if (type(ib[k]) === 'array') { for (let a in ib[k]) push(addrs, a); }
|
||||
else push(addrs, ib[k]);
|
||||
} else if (k === 'sniff') {
|
||||
if (ib[k]) had_sniff = true;
|
||||
} else if (k === 'sniff_override_destination' || k === 'sniff_timeout' ||
|
||||
k === 'domain_strategy') {
|
||||
// drop — moved to route.rules actions in 1.13
|
||||
} else {
|
||||
r[k] = ib[k];
|
||||
}
|
||||
}
|
||||
if (length(addrs) > 0) r.address = addrs;
|
||||
return r;
|
||||
});
|
||||
}
|
||||
if (had_sniff) {
|
||||
if (!cfg.route) cfg.route = {};
|
||||
if (!cfg.route.rules) cfg.route.rules = [];
|
||||
let has_sniff_rule = false;
|
||||
for (let rl in cfg.route.rules)
|
||||
if (rl.action === 'sniff') { has_sniff_rule = true; break; }
|
||||
if (!has_sniff_rule) {
|
||||
let new_rules = [ { action: 'sniff' } ];
|
||||
for (let rl in cfg.route.rules) push(new_rules, rl);
|
||||
cfg.route.rules = new_rules;
|
||||
}
|
||||
}
|
||||
|
||||
// --- sing-box 1.14 requires route.default_domain_resolver; add one if missing,
|
||||
// preferring dns.final, else the first dns.servers entry with a tag. ---
|
||||
if (cfg.route && !cfg.route.default_domain_resolver) {
|
||||
let resolver_tag = '';
|
||||
if (cfg.dns && cfg.dns.final) resolver_tag = cfg.dns.final;
|
||||
else if (cfg.dns && cfg.dns.servers) {
|
||||
for (let srv in cfg.dns.servers)
|
||||
if (srv.tag) { resolver_tag = srv.tag; break; }
|
||||
}
|
||||
if (resolver_tag)
|
||||
cfg.route.default_domain_resolver = { server: resolver_tag };
|
||||
}
|
||||
|
||||
// --- Remove dangling outbound refs from selectors/urltest, and drop airline
|
||||
// pseudo-nodes. Two cases: (1) subconverter may reference a missing tag (e.g.
|
||||
// REJECT); (2) "Traffic:.../Expire:.../quota" pseudo-nodes are real SS/Vmess
|
||||
// outbounds (kept so the UI can read traffic/expiry) but don't forward — in a
|
||||
// selector/urltest they win (0ms) and swallow all foreign traffic. ---
|
||||
let is_pseudo_tag = function(t) {
|
||||
if (!t) return false;
|
||||
return match(t, /^Traffic[::]/) ||
|
||||
match(t, /^Expire[::]/) ||
|
||||
match(t, /剩余流量|剩余[::]/) ||
|
||||
match(t, /距离下次重置/) ||
|
||||
match(t, /到期(时间|日期)?[::]/) ||
|
||||
match(t, /官网[::]|网站[::]|套餐[::]?|客服[::]/) ||
|
||||
match(t, /QQ[群]?[::]/) ||
|
||||
match(t, /Telegram|TG群|官方群/) ||
|
||||
match(t, /续费|订阅地址|流量重置/);
|
||||
};
|
||||
if (cfg.outbounds) {
|
||||
let defined_tags = {};
|
||||
for (let ob in cfg.outbounds) defined_tags[ob.tag] = true;
|
||||
cfg.outbounds = map(cfg.outbounds, function(ob) {
|
||||
if ((ob.type !== 'selector' && ob.type !== 'urltest' && ob.type !== 'loadbalance') ||
|
||||
!ob.outbounds) return ob;
|
||||
let valid = [];
|
||||
for (let t in ob.outbounds) {
|
||||
if (!defined_tags[t]) continue;
|
||||
if (is_pseudo_tag(t)) continue;
|
||||
push(valid, t);
|
||||
}
|
||||
if (length(valid) === length(ob.outbounds)) return ob;
|
||||
let r = {};
|
||||
for (let k in ob) r[k] = ob[k];
|
||||
r.outbounds = valid;
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Force download_detour=DIRECT on every remote rule_set ---
|
||||
// subconverter often emits download_detour="auto", but the proxy isn't ready at
|
||||
// first start -> urltest deadlock ("fetch rule-set: deadline exceeded") -> FATAL.
|
||||
// The srs URLs already use a CN-reachable mirror, so DIRECT is always correct.
|
||||
if (cfg.route && cfg.route.rule_set) {
|
||||
let dl_detour = pick_dl_detour();
|
||||
cfg.route.rule_set = map(cfg.route.rule_set, function(rs) {
|
||||
if (!rs || rs.type !== 'remote') return rs;
|
||||
let r = {};
|
||||
for (let k in rs) r[k] = rs[k];
|
||||
r.download_detour = dl_detour;
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
|
||||
// ---- CLI entry: ucode migrate_singbox.uc <config.json> ----
|
||||
let _path = ARGV[0] || '';
|
||||
if (!_path) { print("missing path\n"); exit(1); }
|
||||
let _raw = readfile(_path);
|
||||
if (!_raw) { print("read failed\n"); exit(1); }
|
||||
let _cfg = json(_raw);
|
||||
if (!_cfg) { print("json parse failed\n"); exit(1); }
|
||||
let _before = sprintf('%J', _cfg);
|
||||
_cfg = migrate_singbox(_cfg);
|
||||
let _after = sprintf('%J', _cfg);
|
||||
if (_before === _after) { print("nochange\n"); exit(0); }
|
||||
if (writefile(_path, _after) === null) { print("write failed\n"); exit(1); }
|
||||
print("migrated\n");
|
||||
@@ -1,939 +0,0 @@
|
||||
#!/usr/bin/ucode
|
||||
|
||||
'use strict';
|
||||
|
||||
import { readfile, writefile, access, popen } from 'fs';
|
||||
|
||||
let path = ARGV[0] || '';
|
||||
let redir_port = +(ARGV[1] || '7891');
|
||||
let tproxy_port = +(ARGV[2] || '7982');
|
||||
let mixed_port = +(ARGV[3] || '7890');
|
||||
let has_tun_device = (ARGV[4] || '1') == '1';
|
||||
if (has_tun_device && system('(ip tuntap add mode tun name cotuntest >/dev/null 2>&1 && ip link del cotuntest >/dev/null 2>&1)') != 0)
|
||||
has_tun_device = false;
|
||||
|
||||
// default 6666 must match fw4.sh CORE_ROUTING_MARK (0x1a0a)
|
||||
let routing_mark = +(ARGV[5] || '6666');
|
||||
let dns_port = +(ARGV[6] || '1053');
|
||||
let dash_port = +(ARGV[7] || '9090');
|
||||
let dash_secret = ARGV[8] != null ? (ARGV[8] + '') : '';
|
||||
|
||||
if (!path) {
|
||||
print("missing path\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let raw = readfile(path);
|
||||
if (!raw) {
|
||||
print("read failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let cfg = json(raw);
|
||||
if (!cfg) {
|
||||
print("json parse failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
function s_len(s) {
|
||||
return length(s || '');
|
||||
}
|
||||
|
||||
function s_sub(s, start, count) {
|
||||
if (count == null)
|
||||
return substr(s || '', start);
|
||||
return substr(s || '', start, count);
|
||||
}
|
||||
|
||||
function is_space(ch) {
|
||||
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
|
||||
}
|
||||
|
||||
function trim_s(s) {
|
||||
s = (s == null) ? '' : (s + '');
|
||||
while (s_len(s) > 0 && is_space(s_sub(s, 0, 1)))
|
||||
s = s_sub(s, 1);
|
||||
while (s_len(s) > 0 && is_space(s_sub(s, s_len(s) - 1, 1)))
|
||||
s = s_sub(s, 0, s_len(s) - 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
let default_interface = '';
|
||||
let _default_if_pipe = popen("ip -4 route show default 2>/dev/null | awk '{print $5; exit}'");
|
||||
if (_default_if_pipe) {
|
||||
default_interface = trim_s(_default_if_pipe.read('all'));
|
||||
_default_if_pipe.close();
|
||||
}
|
||||
|
||||
function starts_with(s, prefix) {
|
||||
return s_sub(s || '', 0, s_len(prefix)) == prefix;
|
||||
}
|
||||
|
||||
function find_char(s, ch) {
|
||||
for (let i = 0; i < s_len(s); i++)
|
||||
if (s_sub(s, i, 1) == ch)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
function find_last_char(s, ch) {
|
||||
for (let i = s_len(s) - 1; i >= 0; i--)
|
||||
if (s_sub(s, i, 1) == ch)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
function split_uci_words(line) {
|
||||
let out = [], cur = '', quote = '', esc = false;
|
||||
for (let i = 0; i < s_len(line); i++) {
|
||||
let ch = s_sub(line, i, 1);
|
||||
if (esc) {
|
||||
cur += ch;
|
||||
esc = false;
|
||||
continue;
|
||||
}
|
||||
if (ch == '\\') {
|
||||
esc = true;
|
||||
continue;
|
||||
}
|
||||
if (quote) {
|
||||
if (ch == quote)
|
||||
quote = '';
|
||||
else
|
||||
cur += ch;
|
||||
continue;
|
||||
}
|
||||
if (ch == '"' || ch == "'") {
|
||||
quote = ch;
|
||||
continue;
|
||||
}
|
||||
if (is_space(ch)) {
|
||||
if (s_len(cur) > 0) {
|
||||
push(out, cur);
|
||||
cur = '';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cur += ch;
|
||||
}
|
||||
if (s_len(cur) > 0)
|
||||
push(out, cur);
|
||||
return out;
|
||||
}
|
||||
|
||||
function load_clashoo_uci() {
|
||||
let uci_path = ARGV[9] || '/etc/config/clashoo';
|
||||
let txt = readfile(uci_path) || '';
|
||||
let sections = [], cur = null;
|
||||
for (let line in split(txt, '\n')) {
|
||||
line = trim_s(line);
|
||||
if (!s_len(line) || starts_with(line, '#'))
|
||||
continue;
|
||||
let words = split_uci_words(line);
|
||||
if (!length(words))
|
||||
continue;
|
||||
if (words[0] == 'config') {
|
||||
cur = { type: words[1] || '', name: words[2] || '', options: {}, lists: {} };
|
||||
push(sections, cur);
|
||||
continue;
|
||||
}
|
||||
if (!cur || length(words) < 3)
|
||||
continue;
|
||||
if (words[0] == 'option')
|
||||
cur.options[words[1]] = words[2];
|
||||
else if (words[0] == 'list') {
|
||||
if (cur.lists[words[1]] == null)
|
||||
cur.lists[words[1]] = [];
|
||||
push(cur.lists[words[1]], words[2]);
|
||||
}
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
let clashoo_uci = load_clashoo_uci();
|
||||
|
||||
function uci_config_section() {
|
||||
for (let s in clashoo_uci)
|
||||
if (s.type == 'clashoo' && (s.name == 'config' || s.name == ''))
|
||||
return s;
|
||||
return {};
|
||||
}
|
||||
|
||||
let uci_cfg = uci_config_section();
|
||||
|
||||
function uci_opt(key, def) {
|
||||
if (uci_cfg.options && uci_cfg.options[key] != null)
|
||||
return uci_cfg.options[key];
|
||||
return def;
|
||||
}
|
||||
|
||||
function uci_list(key) {
|
||||
if (uci_cfg.lists && uci_cfg.lists[key] != null)
|
||||
return uci_cfg.lists[key];
|
||||
if (uci_cfg.options && uci_cfg.options[key] != null)
|
||||
return [ uci_cfg.options[key] ];
|
||||
return [];
|
||||
}
|
||||
|
||||
function uci_sections(type_name) {
|
||||
let out = [];
|
||||
for (let s in clashoo_uci)
|
||||
if (s.type == type_name)
|
||||
push(out, s);
|
||||
return out;
|
||||
}
|
||||
|
||||
function opt_bool(v, def) {
|
||||
if (v == null || v == '')
|
||||
return def;
|
||||
return v === true || v == '1' || v == 'true' || v == 'yes' || v == 'on';
|
||||
}
|
||||
|
||||
function normalize_dns_uri(address, protocol, port) {
|
||||
address = trim_s(address || '');
|
||||
protocol = trim_s(protocol || '');
|
||||
port = trim_s(port || '');
|
||||
if (!s_len(address))
|
||||
return '';
|
||||
if (find_char(address, ':') >= 0 && find_char(address, '/') >= 0)
|
||||
return address;
|
||||
if (protocol == 'none')
|
||||
protocol = '';
|
||||
if (protocol == 'dot')
|
||||
protocol = 'tls://';
|
||||
else if (protocol == 'doh')
|
||||
protocol = 'https://';
|
||||
else if (protocol == 'doq')
|
||||
protocol = 'quic://';
|
||||
if (s_len(protocol) && !starts_with(protocol, 'udp://') && !starts_with(protocol, 'tcp://') &&
|
||||
!starts_with(protocol, 'tls://') && !starts_with(protocol, 'https://') && !starts_with(protocol, 'quic://'))
|
||||
protocol += '://';
|
||||
return protocol + address + (s_len(port) ? ':' + port : '');
|
||||
}
|
||||
|
||||
function direct_outbound_tag() {
|
||||
for (let ob in (cfg.outbounds || []))
|
||||
if (ob && ob.type == 'direct' && s_len(ob.tag || ''))
|
||||
return ob.tag;
|
||||
return 'DIRECT';
|
||||
}
|
||||
|
||||
function dns_server_obj(uri, tag, fallback_type) {
|
||||
uri = normalize_dns_uri(uri || '', '', '');
|
||||
if (!s_len(uri))
|
||||
return null;
|
||||
let scheme = fallback_type || 'udp';
|
||||
let rest = uri;
|
||||
let p = -1;
|
||||
for (let i = 0; i < s_len(uri) - 2; i++) {
|
||||
if (s_sub(uri, i, 3) == '://') {
|
||||
p = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p >= 0) {
|
||||
scheme = s_sub(uri, 0, p);
|
||||
rest = s_sub(uri, p + 3);
|
||||
}
|
||||
if (scheme == 'dot')
|
||||
scheme = 'tls';
|
||||
else if (scheme == 'doh')
|
||||
scheme = 'https';
|
||||
else if (scheme == 'doq')
|
||||
scheme = 'quic';
|
||||
|
||||
let path = '';
|
||||
let slash = find_char(rest, '/');
|
||||
if (slash >= 0) {
|
||||
path = s_sub(rest, slash);
|
||||
rest = s_sub(rest, 0, slash);
|
||||
}
|
||||
|
||||
let server = rest, server_port = null;
|
||||
let colon = find_last_char(rest, ':');
|
||||
if (colon > 0 && find_char(rest, ']') < 0) {
|
||||
server = s_sub(rest, 0, colon);
|
||||
let n = +(s_sub(rest, colon + 1));
|
||||
if (n === n)
|
||||
server_port = n;
|
||||
}
|
||||
|
||||
let obj = { type: scheme, tag: tag, server: server };
|
||||
if (server_port != null)
|
||||
obj.server_port = server_port;
|
||||
if (path && (scheme == 'https' || scheme == 'h3'))
|
||||
obj.path = path;
|
||||
if ((scheme == 'https' || scheme == 'tls' || scheme == 'quic') && tag != 'dns_resolver')
|
||||
obj.domain_resolver = 'dns_resolver';
|
||||
/* sing-box 1.12+ DNS servers detour to the proxy by default, so a server
|
||||
* resolving its own domain via dns_resolver loops -> "deadline exceeded".
|
||||
* Force direct/resolver servers to DIRECT; only dns_proxy may use the proxy. */
|
||||
if (tag == 'dns_resolver' || tag == 'dns_direct' || tag == 'dns_foreign')
|
||||
obj.detour = direct_outbound_tag();
|
||||
return obj;
|
||||
}
|
||||
|
||||
function dns_servers_by_role(role) {
|
||||
let out = [];
|
||||
for (let s in uci_sections('dnsservers')) {
|
||||
if (!opt_bool(s.options.enabled, true))
|
||||
continue;
|
||||
if ((s.options.ser_type || 'nameserver') != role)
|
||||
continue;
|
||||
let uri = normalize_dns_uri(s.options.ser_address || '', s.options.protocol || '', s.options.ser_port || '');
|
||||
if (s_len(uri))
|
||||
push(out, uri);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function first_or(arr, fallback) {
|
||||
return length(arr) ? arr[0] : fallback;
|
||||
}
|
||||
|
||||
function local_rule_set_path(tag) {
|
||||
if (!s_len(tag))
|
||||
return '';
|
||||
let path = '/usr/share/clashoo/ruleset/' + tag + '.srs';
|
||||
if (access(path, 'r'))
|
||||
return path;
|
||||
if (tag == 'geolocation-cn' || tag == 'cn') {
|
||||
path = '/usr/share/clashoo/ruleset/geosite-cn.srs';
|
||||
if (access(path, 'r'))
|
||||
return path;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function keep_remote_rule_set(rs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function normalize_rule_set_url(url) {
|
||||
url = url || '';
|
||||
url = replace(url, /^https:\/\/gh-proxy\.com\//, '');
|
||||
let m = match(url, /^https:\/\/raw\.githubusercontent\.com\/([^\/]+)\/([^\/]+)\/([^\/]+)\/(.+)$/);
|
||||
if (m)
|
||||
return 'https://cdn.jsdelivr.net/gh/' + m[1] + '/' + m[2] + '@' + m[3] + '/' + m[4];
|
||||
m = match(url, /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/raw\/refs\/heads\/([^\/]+)\/(.+)$/);
|
||||
if (m)
|
||||
return 'https://cdn.jsdelivr.net/gh/' + m[1] + '/' + m[2] + '@' + m[3] + '/' + m[4];
|
||||
return url;
|
||||
}
|
||||
|
||||
function has_route_rule_set(tag) {
|
||||
for (let rs in (cfg.route || {}).rule_set || [])
|
||||
if (rs && rs.tag == tag)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function add_remote_rule_set(tag, url) {
|
||||
cfg.route = cfg.route || {};
|
||||
cfg.route.rule_set = cfg.route.rule_set || [];
|
||||
if (has_route_rule_set(tag))
|
||||
return;
|
||||
push(cfg.route.rule_set, {
|
||||
tag: tag,
|
||||
type: 'remote',
|
||||
format: 'binary',
|
||||
url: url
|
||||
});
|
||||
}
|
||||
|
||||
function array_has_value(arr, val) {
|
||||
if (type(arr) != 'array')
|
||||
return false;
|
||||
for (let item in arr)
|
||||
if (item == val)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
let dnsmasq_uid = null;
|
||||
let _dnsmasq_uid_pipe = popen("id -u dnsmasq 2>/dev/null");
|
||||
if (_dnsmasq_uid_pipe) {
|
||||
let value = trim_s(_dnsmasq_uid_pipe.read('all'));
|
||||
_dnsmasq_uid_pipe.close();
|
||||
if (match(value, /^[0-9]+$/))
|
||||
dnsmasq_uid = +value;
|
||||
}
|
||||
|
||||
function ensure_tun_dnsmasq_exclude(ib) {
|
||||
if (dnsmasq_uid == null)
|
||||
return;
|
||||
let excludes = ib.exclude_uid;
|
||||
if (type(excludes) != 'array')
|
||||
excludes = excludes != null ? [ excludes ] : [];
|
||||
if (!array_has_value(excludes, dnsmasq_uid))
|
||||
push(excludes, dnsmasq_uid);
|
||||
ib.exclude_uid = excludes;
|
||||
}
|
||||
|
||||
function ensure_tun_cn_exclude(ib) {
|
||||
let excludes = ib.route_exclude_address_set;
|
||||
if (type(excludes) != 'array')
|
||||
excludes = excludes ? [ excludes ] : [];
|
||||
if (!array_has_value(excludes, 'cn-ip'))
|
||||
push(excludes, 'cn-ip');
|
||||
ib.route_exclude_address_set = excludes;
|
||||
}
|
||||
|
||||
function prune_tun_cn_exclude_if_missing() {
|
||||
if (has_route_rule_set('cn-ip'))
|
||||
return;
|
||||
for (let ib in cfg.inbounds || []) {
|
||||
if (!ib || ib.type != 'tun')
|
||||
continue;
|
||||
let excludes = [];
|
||||
for (let item in ib.route_exclude_address_set || [])
|
||||
if (item != 'cn-ip')
|
||||
push(excludes, item);
|
||||
if (length(excludes))
|
||||
ib.route_exclude_address_set = excludes;
|
||||
else
|
||||
delete ib.route_exclude_address_set;
|
||||
}
|
||||
}
|
||||
|
||||
function matcher_rule(matcher, server_tag) {
|
||||
let r = { server: server_tag };
|
||||
if (starts_with(matcher, 'geosite:'))
|
||||
r.rule_set = s_sub(matcher, 8);
|
||||
else if (starts_with(matcher, 'rule_set:'))
|
||||
r.rule_set = s_sub(matcher, 9);
|
||||
else if (starts_with(matcher, 'domain:'))
|
||||
r.domain = s_sub(matcher, 7);
|
||||
else if (starts_with(matcher, 'domain-suffix:'))
|
||||
r.domain_suffix = s_sub(matcher, 14);
|
||||
else
|
||||
r.domain_suffix = matcher;
|
||||
return r;
|
||||
}
|
||||
|
||||
function apply_dns_from_uci() {
|
||||
cfg.dns = cfg.dns || {};
|
||||
/* strip mihomo-style DNS fields (enable/ipv6/listen/fake-ip-filter/nameserver...)
|
||||
* that leak in from YAML conversion; sing-box rejects them (Fatal). */
|
||||
let _mihomo_dns_fields = ['enable', 'ipv6', 'listen', 'fake-ip-filter', 'fake-ip-range',
|
||||
'enhanced-mode', 'nameserver', 'fallback', 'fallback-filter',
|
||||
'use-hosts', 'default-nameserver', 'proxy-server-nameserver',
|
||||
'direct-nameserver', 'nameserver-policy'];
|
||||
for (let _i = 0; _i < length(_mihomo_dns_fields); _i++)
|
||||
delete cfg.dns[_mihomo_dns_fields[_i]];
|
||||
/* strip mihomo-style experimental fields */
|
||||
let _mihomo_exp = ['sniff-tls-sni', 'sniff', 'sniffer'];
|
||||
for (let _me = 0; _me < length(_mihomo_exp); _me++)
|
||||
if (cfg.experimental && cfg.experimental[_mihomo_exp[_me]] != null)
|
||||
delete cfg.experimental[_mihomo_exp[_me]];
|
||||
/* strip non-sing-box root-level fields */
|
||||
let _mihomo_root = ['clash-for-android', 'cfw-bypass', 'sniffer', 'profile',
|
||||
'geodata-mode', 'geodata-loader', 'geox-url', 'geo-auto-update',
|
||||
'geo-update-interval', 'tun', 'ipv6', 'interface-name',
|
||||
'port', 'socks-port', 'mixed-port', 'redir-port', 'tproxy-port', 'mode', 'allow-lan', 'log-level', 'external-controller', 'secret', 'bind-address', 'routing-mark', 'find-process-mode', 'tcp-concurrent', 'unified-delay',
|
||||
'keep-alive-interval', 'keep-alive-idle', 'disable-keep-alive'];
|
||||
/* strip subconverter note fields and non-standard keys */
|
||||
let _subconv_fields = ['_note', '_sub_url', 'hosts', 'script', 'enable', 'fake-ip-filter', 'fake-ip-range'];
|
||||
for (let _sf = 0; _sf < length(_subconv_fields); _sf++)
|
||||
if (cfg[_subconv_fields[_sf]] != null)
|
||||
delete cfg[_subconv_fields[_sf]];
|
||||
for (let _mr = 0; _mr < length(_mihomo_root); _mr++)
|
||||
if (cfg[_mihomo_root[_mr]] != null)
|
||||
delete cfg[_mihomo_root[_mr]];
|
||||
let bootstrap = uci_list('default_nameserver');
|
||||
if (!length(bootstrap))
|
||||
bootstrap = uci_list('defaul_nameserver');
|
||||
let resolver_uri = first_or(bootstrap, '223.5.5.5');
|
||||
let direct_uri = first_or(dns_servers_by_role('direct-nameserver'), first_or(dns_servers_by_role('proxy-server-nameserver'), 'https://doh.pub/dns-query'));
|
||||
let proxy_uri = first_or(dns_servers_by_role('nameserver'), first_or(dns_servers_by_role('fallback'), 'https://1.1.1.1/dns-query'));
|
||||
|
||||
let servers = [];
|
||||
push(servers, dns_server_obj(resolver_uri, 'dns_resolver', 'udp'));
|
||||
push(servers, dns_server_obj(direct_uri, 'dns_direct', 'udp'));
|
||||
push(servers, dns_server_obj(proxy_uri, 'dns_proxy', 'tls'));
|
||||
|
||||
let enhanced = uci_opt('enhanced_mode', 'fake-ip');
|
||||
if (enhanced == 'fake-ip') {
|
||||
let fake_range = uci_opt('fake_ip_range', '198.18.0.1/16');
|
||||
push(servers, {
|
||||
type: 'fakeip',
|
||||
tag: 'dns_fakeip',
|
||||
inet4_range: fake_range || '198.18.0.1/16',
|
||||
inet6_range: 'fc00::/18'
|
||||
});
|
||||
}
|
||||
|
||||
let rules = [];
|
||||
push(servers, { type: 'udp', tag: 'dns_foreign', server: '1.1.1.1' });
|
||||
if (enhanced == 'fake-ip') {
|
||||
push(rules, {
|
||||
rule_set: 'geolocation-!cn',
|
||||
server: 'dns_fakeip'
|
||||
});
|
||||
}
|
||||
|
||||
let clean_servers = [];
|
||||
for (let s in servers)
|
||||
if (s)
|
||||
push(clean_servers, s);
|
||||
cfg.dns.servers = clean_servers;
|
||||
cfg.dns.rules = rules;
|
||||
|
||||
/* Keep bootstrap direct; proxy unmatched queries in strict mode. */
|
||||
if (opt_bool(uci_opt('dns_leak_protect', '0'), false)) {
|
||||
cfg.dns.final = 'dns_proxy';
|
||||
if (!opt_bool(uci_opt('ipv6_proxy', '0'), false))
|
||||
unshift(cfg.dns.rules, { query_type: ['AAAA'], action: 'reject', method: 'drop' });
|
||||
} else
|
||||
cfg.dns.final = 'dns_direct';
|
||||
|
||||
let ecs = trim_s(uci_opt('dns_ecs', ''));
|
||||
if (s_len(ecs))
|
||||
cfg.dns.client_subnet = ecs;
|
||||
else
|
||||
delete cfg.dns.client_subnet;
|
||||
|
||||
if (opt_bool(uci_opt('singbox_independent_cache', '0'), false))
|
||||
cfg.dns.independent_cache = true;
|
||||
else
|
||||
delete cfg.dns.independent_cache;
|
||||
|
||||
/* sing-box 1.14 requires this (else Fatal); dns_resolver is DIRECT so first-start node lookups use direct DNS */
|
||||
cfg.route.default_domain_resolver = 'dns_resolver';
|
||||
}
|
||||
|
||||
let inbounds = cfg.inbounds || [];
|
||||
let normalized = [];
|
||||
let has_redirect = false;
|
||||
let has_tproxy = false;
|
||||
let has_mixed = false;
|
||||
let has_tun = false;
|
||||
let has_dns_in = false;
|
||||
let wants_tun = has_tun_device && (uci_opt('tcp_mode', '') == 'tun' || uci_opt('udp_mode', '') == 'tun');
|
||||
let tun_stack = uci_opt('stack', 'mixed') || 'mixed';
|
||||
|
||||
if (wants_tun) {
|
||||
add_remote_rule_set('cn-ip',
|
||||
'https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/cn.srs');
|
||||
}
|
||||
|
||||
for (let ib in inbounds) {
|
||||
if (!ib)
|
||||
continue;
|
||||
|
||||
if (ib.type == 'tun' || ib.tag == 'tun-in') {
|
||||
/* Keep tun inbound only when the selected proxy mode actually uses TUN. */
|
||||
if (wants_tun && !has_tun) {
|
||||
ib.type = 'tun';
|
||||
ib.tag = ib.tag || 'tun-in';
|
||||
if (!ib.address)
|
||||
ib.address = [ '172.19.0.1/30', 'fdfe:dcba:9876::1/126' ];
|
||||
ib.auto_route = true;
|
||||
ib.auto_redirect = true;
|
||||
ib.auto_redirect_input_mark = '0x2023';
|
||||
ib.auto_redirect_output_mark = '0x2024';
|
||||
ib.strict_route = true;
|
||||
ib.stack = tun_stack;
|
||||
ensure_tun_dnsmasq_exclude(ib);
|
||||
ensure_tun_cn_exclude(ib);
|
||||
push(normalized, ib);
|
||||
has_tun = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ib.tag == 'redirect-in' || ib.type == 'redirect') {
|
||||
if (has_redirect)
|
||||
continue;
|
||||
ib.type = 'redirect';
|
||||
ib.tag = 'redirect-in';
|
||||
ib.listen = '0.0.0.0';
|
||||
ib.listen_port = redir_port;
|
||||
has_redirect = true;
|
||||
push(normalized, ib);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ib.tag == 'tproxy-in' || ib.type == 'tproxy') {
|
||||
if (has_tproxy)
|
||||
continue;
|
||||
ib.type = 'tproxy';
|
||||
ib.tag = 'tproxy-in';
|
||||
ib.listen = '0.0.0.0';
|
||||
ib.listen_port = tproxy_port;
|
||||
ib.network = 'udp';
|
||||
has_tproxy = true;
|
||||
push(normalized, ib);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ib.tag == 'mixed-in' || ib.type == 'mixed') {
|
||||
if (has_mixed)
|
||||
continue;
|
||||
ib.type = 'mixed';
|
||||
ib.tag = 'mixed-in';
|
||||
ib.listen = '0.0.0.0';
|
||||
ib.listen_port = mixed_port;
|
||||
has_mixed = true;
|
||||
push(normalized, ib);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ib.tag == 'dns-in') {
|
||||
if (has_dns_in)
|
||||
continue;
|
||||
ib.type = 'direct';
|
||||
ib.tag = 'dns-in';
|
||||
ib.listen = '0.0.0.0';
|
||||
ib.listen_port = dns_port;
|
||||
has_dns_in = true;
|
||||
push(normalized, ib);
|
||||
continue;
|
||||
}
|
||||
|
||||
push(normalized, ib);
|
||||
}
|
||||
|
||||
if (!has_redirect) {
|
||||
push(normalized, {
|
||||
type: 'redirect',
|
||||
tag: 'redirect-in',
|
||||
listen: '0.0.0.0',
|
||||
listen_port: redir_port
|
||||
});
|
||||
}
|
||||
|
||||
if (!has_mixed) {
|
||||
push(normalized, {
|
||||
type: 'mixed',
|
||||
tag: 'mixed-in',
|
||||
listen: '0.0.0.0',
|
||||
listen_port: mixed_port
|
||||
});
|
||||
}
|
||||
|
||||
if (wants_tun && !has_tun) {
|
||||
let tun_ib = {
|
||||
type: 'tun',
|
||||
tag: 'tun-in',
|
||||
address: [ '172.19.0.1/30', 'fdfe:dcba:9876::1/126' ],
|
||||
auto_route: true,
|
||||
auto_redirect: true,
|
||||
auto_redirect_input_mark: '0x2023',
|
||||
auto_redirect_output_mark: '0x2024',
|
||||
strict_route: true,
|
||||
stack: tun_stack
|
||||
};
|
||||
ensure_tun_dnsmasq_exclude(tun_ib);
|
||||
ensure_tun_cn_exclude(tun_ib);
|
||||
push(normalized, tun_ib);
|
||||
}
|
||||
|
||||
if (!has_tproxy) {
|
||||
push(normalized, {
|
||||
type: 'tproxy',
|
||||
tag: 'tproxy-in',
|
||||
listen: '0.0.0.0',
|
||||
listen_port: tproxy_port,
|
||||
network: 'udp'
|
||||
});
|
||||
}
|
||||
|
||||
if (!has_dns_in) {
|
||||
push(normalized, {
|
||||
type: 'direct',
|
||||
tag: 'dns-in',
|
||||
listen: '0.0.0.0',
|
||||
listen_port: dns_port
|
||||
});
|
||||
}
|
||||
|
||||
cfg.inbounds = normalized;
|
||||
|
||||
for (let ob in (cfg.outbounds || [])) {
|
||||
if (!ob || type(ob) != 'object')
|
||||
continue;
|
||||
|
||||
let t = ob.type || '';
|
||||
if (t == 'selector' || t == 'urltest' || t == 'fallback' || t == 'load_balance' || t == 'dns' || t == 'block')
|
||||
continue;
|
||||
|
||||
/* Clash SS obfs is plugin=obfs + plugin-opts object; sing-box wants
|
||||
* plugin=obfs-local + "obfs=http;obfs-host=..." string. */
|
||||
if (ob.plugin == 'obfs')
|
||||
ob.plugin = 'obfs-local';
|
||||
if (ob.plugin_opts != null && type(ob.plugin_opts) == 'object') {
|
||||
let _parts = [];
|
||||
let _map = { mode: 'obfs', host: 'obfs-host', uri: 'obfs-uri' };
|
||||
for (let _k in ob.plugin_opts) {
|
||||
let _v = ob.plugin_opts[_k];
|
||||
if (type(_v) == 'object' || type(_v) == 'array') continue;
|
||||
push(_parts, (_map[_k] || _k) + '=' + _v);
|
||||
}
|
||||
ob.plugin_opts = join(';', _parts);
|
||||
}
|
||||
if (ob.plugin != null && ob.plugin != 'obfs-local' && ob.plugin != 'v2ray-plugin' && ob.plugin != 'shadow-tls') {
|
||||
delete ob.plugin;
|
||||
delete ob.plugin_opts;
|
||||
}
|
||||
|
||||
/* Force ipv4_only on DIRECT outbound: most devices are IPv4-only egress,
|
||||
* a CN domain resolving AAAA stalls on "network is unreachable". */
|
||||
if (ob.type == 'direct' && ob.tag == 'DIRECT')
|
||||
ob.domain_resolver = { server: 'dns_resolver', strategy: 'ipv4_only' };
|
||||
|
||||
if (wants_tun) {
|
||||
delete ob.routing_mark;
|
||||
if (ob.type == 'direct' && ob.tag == 'DIRECT' && default_interface)
|
||||
ob.bind_interface = ob.bind_interface || default_interface;
|
||||
} else if (ob.routing_mark == null)
|
||||
ob.routing_mark = routing_mark;
|
||||
}
|
||||
|
||||
/* Drop airline pseudo-nodes (Traffic:/Expire:/quota/官网/QQ/etc.) from selector/urltest lists.
|
||||
* They're real SS/Vmess (kept so the UI reads traffic/expiry) but don't forward; in a
|
||||
* selector/urltest they win and swallow foreign traffic ("CN ok, foreign out"). */
|
||||
let _is_pseudo_tag = function(t) {
|
||||
if (!t) return false;
|
||||
return match(t, /^Traffic[::]/) || match(t, /^Expire[::]/) ||
|
||||
match(t, /剩余流量|剩余[::]/) || match(t, /距离下次重置/) ||
|
||||
match(t, /到期(时间|日期)?[::]/) ||
|
||||
match(t, /官网[::]|网站[::]|套餐[::]?|客服[::]/) ||
|
||||
match(t, /QQ[群]?[::]/) || match(t, /Telegram|TG群|官方群/) ||
|
||||
match(t, /续费|订阅地址|流量重置/);
|
||||
};
|
||||
for (let ob in (cfg.outbounds || [])) {
|
||||
if (!ob || type(ob) != 'object') continue;
|
||||
let t = ob.type || '';
|
||||
if (t != 'selector' && t != 'urltest' && t != 'fallback' && t != 'load_balance') continue;
|
||||
if (type(ob.outbounds) != 'array') continue;
|
||||
let cleaned = [];
|
||||
for (let tag in ob.outbounds) if (!_is_pseudo_tag(tag)) push(cleaned, tag);
|
||||
ob.outbounds = cleaned;
|
||||
}
|
||||
|
||||
cfg.route = cfg.route || {};
|
||||
cfg.route.rules = cfg.route.rules || [];
|
||||
let has_dns_hijack = false;
|
||||
for (let rule in cfg.route.rules) {
|
||||
if (!rule || type(rule) != 'object')
|
||||
continue;
|
||||
if (rule.inbound == 'dns-in' && rule.action == 'hijack-dns') {
|
||||
has_dns_hijack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_dns_hijack) {
|
||||
unshift(cfg.route.rules, {
|
||||
inbound: 'dns-in',
|
||||
action: 'hijack-dns'
|
||||
});
|
||||
}
|
||||
cfg.route.auto_detect_interface = true;
|
||||
apply_dns_from_uci();
|
||||
|
||||
/* DNS leak protection: reject DoT/DoQ (853) to force DNS through the core; after hijack-dns so the dns inbound survives */
|
||||
if (opt_bool(uci_opt('dns_leak_protect', '0'), false)) {
|
||||
let _has_853 = false;
|
||||
for (let _r in cfg.route.rules) {
|
||||
if (_r && type(_r) == 'object' && _r.action == 'reject' && _r.port) {
|
||||
if (type(_r.port) == 'array') {
|
||||
for (let _p in _r.port) if (_p == 853) { _has_853 = true; break; }
|
||||
} else if (_r.port == 853) {
|
||||
_has_853 = true;
|
||||
}
|
||||
}
|
||||
if (_has_853) break;
|
||||
}
|
||||
if (!_has_853) {
|
||||
let _new_rules = [];
|
||||
let _inserted = false;
|
||||
for (let _ri = 0; _ri < length(cfg.route.rules); _ri++) {
|
||||
push(_new_rules, cfg.route.rules[_ri]);
|
||||
if (!_inserted && cfg.route.rules[_ri] && cfg.route.rules[_ri].action == 'hijack-dns') {
|
||||
push(_new_rules, { port: [853], action: 'reject' });
|
||||
_inserted = true;
|
||||
}
|
||||
}
|
||||
if (!_inserted) unshift(_new_rules, { port: [853], action: 'reject' });
|
||||
cfg.route.rules = _new_rules;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Custom routing rules (UCI: config addtype) -> route.rules, mirrors mihomo iprules.sh =====
|
||||
* User "domain/IP -> direct/proxy" rules from LuCI, inserted after the DNS funnel
|
||||
* (hijack-dns / 853-reject) and before other rules, so overrides win without bypassing it. */
|
||||
let _proxy_outbound_tag = function() {
|
||||
for (let ob in (cfg.outbounds || []))
|
||||
if (ob && ob.tag == '🚀 节点选择' && (ob.type == 'selector' || ob.type == 'urltest'))
|
||||
return ob.tag;
|
||||
for (let ob in (cfg.outbounds || []))
|
||||
if (ob && ob.type == 'selector' && s_len(ob.tag || '') && !_is_pseudo_tag(ob.tag))
|
||||
return ob.tag;
|
||||
return direct_outbound_tag();
|
||||
};
|
||||
let _load_addtype_rules = function() {
|
||||
let out = [];
|
||||
let dtag = direct_outbound_tag();
|
||||
let ptag = null;
|
||||
for (let s in uci_sections('addtype')) {
|
||||
let o = s.options || {};
|
||||
let addr = trim_s(o.ipaaddr || '');
|
||||
let rtype = trim_s(o.type || '');
|
||||
let pg = trim_s(o.pgroup || '');
|
||||
if (!s_len(addr) || !s_len(rtype) || !s_len(pg))
|
||||
continue;
|
||||
let r = {};
|
||||
if (rtype == 'DOMAIN')
|
||||
r.domain = addr;
|
||||
else if (rtype == 'DOMAIN-SUFFIX')
|
||||
r.domain_suffix = addr;
|
||||
else if (rtype == 'DOMAIN-KEYWORD')
|
||||
r.domain_keyword = addr;
|
||||
else if (rtype == 'IP-CIDR' || rtype == 'IP-CIDR6')
|
||||
r.ip_cidr = addr;
|
||||
else
|
||||
continue;
|
||||
if (pg == 'DIRECT')
|
||||
r.outbound = dtag;
|
||||
else {
|
||||
if (ptag == null)
|
||||
ptag = _proxy_outbound_tag();
|
||||
r.outbound = ptag;
|
||||
}
|
||||
push(out, r);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
let _addtype_rules = _load_addtype_rules();
|
||||
if (length(_addtype_rules)) {
|
||||
let _nr = [];
|
||||
let _ins = false;
|
||||
for (let _i = 0; _i < length(cfg.route.rules); _i++) {
|
||||
let _ru = cfg.route.rules[_i];
|
||||
let _dns_front = _ru && type(_ru) == 'object' &&
|
||||
(_ru.action == 'hijack-dns' || (_ru.action == 'reject' && _ru.port));
|
||||
if (!_ins && !_dns_front) {
|
||||
for (let _cr in _addtype_rules)
|
||||
push(_nr, _cr);
|
||||
_ins = true;
|
||||
}
|
||||
push(_nr, _ru);
|
||||
}
|
||||
if (!_ins)
|
||||
for (let _cr in _addtype_rules)
|
||||
push(_nr, _cr);
|
||||
cfg.route.rules = _nr;
|
||||
}
|
||||
|
||||
if (uci_opt('enhanced_mode', 'fake-ip') == 'fake-ip') {
|
||||
add_remote_rule_set('geolocation-!cn',
|
||||
'https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/geolocation-!cn.srs');
|
||||
}
|
||||
|
||||
/* Local .srs -> local; remaining remote rule_sets must keep/get a download_detour.
|
||||
* CN first-start deadlock: no srs -> fetch rules -> via auto-select -> urltest dials
|
||||
* the proxy -> resolves its domain -> pulled into an unloaded rule_set by DNS rules -> deadline.
|
||||
* The srs URLs use a CN-reachable mirror, so download_detour
|
||||
* must be DIRECT (real direct-outbound tag if any, else 'DIRECT'). */
|
||||
let _pick_dl_detour = function() {
|
||||
if (cfg.outbounds) {
|
||||
for (let ob in cfg.outbounds) {
|
||||
if (ob && ob.type == 'direct' && ob.tag) return ob.tag;
|
||||
}
|
||||
}
|
||||
return 'DIRECT';
|
||||
};
|
||||
let _dl_detour = _pick_dl_detour();
|
||||
for (let rs in (cfg.route || {}).rule_set || []) {
|
||||
if (!rs) continue;
|
||||
if (rs.type != 'remote') { delete rs.download_detour; continue; }
|
||||
let path = local_rule_set_path(rs.tag || '');
|
||||
if (rs.tag && access(path, 'r')) {
|
||||
delete rs.url; delete rs.download_detour; rs.type = 'local'; rs.path = path;
|
||||
continue;
|
||||
}
|
||||
if (keep_remote_rule_set(rs) && rs.url) {
|
||||
rs.url = normalize_rule_set_url(rs.url);
|
||||
rs.download_detour = _dl_detour;
|
||||
continue;
|
||||
}
|
||||
/* No local cache -> skip this remote rule_set so a failed download doesn't block start.
|
||||
* Rule-sets are fetched to /usr/share/clashoo/ruleset/ once proxied. */
|
||||
continue;
|
||||
|
||||
}
|
||||
/* filter out remote rule_sets with no local cache */
|
||||
let _clean_rs = [];
|
||||
for (let _rsi = 0; _rsi < length(cfg.route.rule_set); _rsi++) {
|
||||
let _rs = cfg.route.rule_set[_rsi];
|
||||
if (!_rs) continue;
|
||||
if (_rs.type == 'remote' && _rs.url != null) {
|
||||
let _local_path = local_rule_set_path(_rs.tag || '');
|
||||
if (!_rs.tag || !access(_local_path, 'r'))
|
||||
if (keep_remote_rule_set(_rs) && _rs.url) {
|
||||
_rs.url = normalize_rule_set_url(_rs.url);
|
||||
_rs.download_detour = _dl_detour;
|
||||
push(_clean_rs, _rs);
|
||||
continue;
|
||||
} else {
|
||||
continue; /* no local cache -> drop from final list */
|
||||
}
|
||||
/* has local cache -> convert to local */
|
||||
delete _rs.url; delete _rs.download_detour; _rs.type = 'local'; _rs.path = _local_path;
|
||||
}
|
||||
push(_clean_rs, _rs);
|
||||
}
|
||||
cfg.route.rule_set = _clean_rs;
|
||||
prune_tun_cn_exclude_if_missing();
|
||||
|
||||
/* drop DNS/route rules referencing a removed rule_set, else sing-box FATAL: rule-set not found */
|
||||
let _existing_tags = {};
|
||||
for (let _rs in cfg.route.rule_set || []) if (_rs && _rs.tag) _existing_tags[_rs.tag] = true;
|
||||
let _rule_has_ref = function(_r) {
|
||||
if (!_r || type(_r) != 'object') return false;
|
||||
if (_r.rule_set) {
|
||||
if (type(_r.rule_set) == 'array') { for (let _t in _r.rule_set) if (!_existing_tags[_t]) return false; }
|
||||
else if (!_existing_tags[_r.rule_set]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/* clean dns.rules */
|
||||
if (cfg.dns && type(cfg.dns.rules) == 'array') {
|
||||
let _clean_dns_rules = [];
|
||||
for (let _dr in cfg.dns.rules) if (_rule_has_ref(_dr)) push(_clean_dns_rules, _dr);
|
||||
cfg.dns.rules = _clean_dns_rules;
|
||||
}
|
||||
/* clean route.rules */
|
||||
if (cfg.route && type(cfg.route.rules) == 'array') {
|
||||
let _clean_rt_rules = [];
|
||||
for (let _rr in cfg.route.rules) if (_rule_has_ref(_rr)) push(_clean_rt_rules, _rr);
|
||||
cfg.route.rules = _clean_rt_rules;
|
||||
}
|
||||
cfg.experimental = cfg.experimental || {};
|
||||
cfg.experimental.clash_api = cfg.experimental.clash_api || {};
|
||||
cfg.experimental.clash_api.external_controller = '0.0.0.0:' + dash_port;
|
||||
cfg.experimental.clash_api.external_ui = '/etc/clashoo/dashboard';
|
||||
cfg.experimental.clash_api.secret = dash_secret;
|
||||
|
||||
cfg.experimental.cache_file = {
|
||||
enabled: true,
|
||||
store_fakeip: true
|
||||
};
|
||||
|
||||
/* OpenWrt already owns system time sync. Keep sing-box NTP disabled by
|
||||
* default to avoid noisy IPv6 UDP/123 failures on IPv4-only routers. */
|
||||
cfg.ntp = cfg.ntp || {};
|
||||
cfg.ntp.enabled = false;
|
||||
|
||||
cfg.log = cfg.log || {};
|
||||
cfg.log.output = '/var/log/clashoo/core.log';
|
||||
if (!cfg.log.level)
|
||||
cfg.log.level = 'info';
|
||||
|
||||
if (writefile(path, sprintf('%J', cfg)) === null) {
|
||||
print("write failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
print("normalized\n");
|
||||
@@ -1,138 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info",
|
||||
"timestamp": true
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "tcp", "tag": "dns_proxy", "server": "1.1.1.1", "domain_resolver": "dns_resolver" },
|
||||
{ "type": "https", "tag": "dns_direct", "server": "dns.alidns.com", "domain_resolver": "dns_resolver" },
|
||||
{ "type": "udp", "tag": "dns_resolver", "server": "223.5.5.5" },
|
||||
{ "type": "fakeip", "tag": "dns_fakeip", "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18" }
|
||||
],
|
||||
"rules": [
|
||||
{ "rule_set": "geolocation-!cn", "query_type": ["A", "AAAA", "CNAME"], "server": "dns_fakeip" },
|
||||
{ "query_type": ["A", "AAAA", "CNAME"], "invert": true, "action": "reject" }
|
||||
],
|
||||
"final": "dns_direct"
|
||||
},
|
||||
"ntp": {
|
||||
"enabled": false,
|
||||
"server": "time.apple.com",
|
||||
"server_port": 123,
|
||||
"interval": "30m"
|
||||
},
|
||||
"inbounds": [
|
||||
{ "type": "mixed", "tag": "mixed-in", "listen": "0.0.0.0", "listen_port": 2080 },
|
||||
{ "type": "tun", "tag": "tun-in", "address": ["172.19.0.1/30"], "auto_route": true, "auto_redirect": true, "strict_route": true, "stack": "mixed", "route_exclude_address_set": ["cn-ip"] }
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "selector", "tag": "🚀 节点选择",
|
||||
"outbounds": ["🌍 全部节点","♻️ 自动选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","DIRECT"],
|
||||
"default": "🌍 全部节点"
|
||||
},
|
||||
{ "type": "selector", "tag": "▶️ YouTube", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🔍 Google", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🔮 OpenAI", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🧠 Claude", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🤖 Gemini", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🐦 Twitter", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "📦 GitHub", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "Ⓜ️ Microsoft", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🎵 TikTok", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "📺 Netflix", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🎬 流媒体", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "💳 PayPal", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "📲 电报消息", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🍏 苹果服务", "outbounds": ["DIRECT","🚀 节点选择","♻️ 自动选择"] },
|
||||
{ "type": "selector", "tag": "🎯 全球直连", "outbounds": ["DIRECT","🚀 节点选择","♻️ 自动选择"] },
|
||||
{ "type": "selector", "tag": "🏠 私有网络", "outbounds": ["DIRECT","🚀 节点选择","♻️ 自动选择"] },
|
||||
{ "type": "selector", "tag": "🌐 非中国", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
{ "type": "selector", "tag": "🐟 漏网之鱼", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] },
|
||||
|
||||
{ "type": "selector", "tag": "🇭🇰 香港节点", "outbounds": ["__NODES_HK__"] },
|
||||
{ "type": "selector", "tag": "🇯🇵 日本节点", "outbounds": ["__NODES_JP__"] },
|
||||
{ "type": "selector", "tag": "🇺🇲 美国节点", "outbounds": ["__NODES_US__"] },
|
||||
{ "type": "selector", "tag": "🇸🇬 新加坡节点","outbounds": ["__NODES_SG__"] },
|
||||
{ "type": "selector", "tag": "🇺🇳 集合节点", "outbounds": ["__NODES_OTHER__"] },
|
||||
{ "type": "selector", "tag": "🌍 全部节点", "outbounds": ["__NODES__"] },
|
||||
|
||||
{ "type": "urltest", "tag": "♻️ 香港自动", "outbounds": ["__NODES_HK__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true },
|
||||
{ "type": "urltest", "tag": "♻️ 日本自动", "outbounds": ["__NODES_JP__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true },
|
||||
{ "type": "urltest", "tag": "♻️ 美国自动", "outbounds": ["__NODES_US__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true },
|
||||
{ "type": "urltest", "tag": "♻️ 新加坡自动", "outbounds": ["__NODES_SG__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true },
|
||||
{ "type": "urltest", "tag": "♻️ 集合自动", "outbounds": ["__NODES_OTHER__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true },
|
||||
{ "type": "urltest", "tag": "♻️ 自动选择", "outbounds": ["__NODES__"], "url": "https://www.gstatic.com/generate_204", "interval": "10m", "tolerance": 50, "interrupt_exist_connections": true },
|
||||
|
||||
{ "type": "direct", "tag": "DIRECT" }
|
||||
],
|
||||
"route": {
|
||||
"default_domain_resolver": "dns_resolver",
|
||||
"rule_set": [
|
||||
{ "tag": "openai", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/openai.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "anthropic", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/anthropic.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "google-gemini", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/google-gemini.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "youtube", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/youtube.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "google", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/google.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "twitter", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/twitter.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "github", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/github.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "gitlab", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/gitlab.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "microsoft", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/microsoft.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "tiktok", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/tiktok.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "netflix", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/netflix.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "hulu", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/hulu.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "disney", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/disney.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "hbo", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/hbo.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "amazon", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/amazon.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "bahamut", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/bahamut.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "paypal", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/paypal.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "apple", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/apple.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "geolocation-cn", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/geolocation-cn.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "cn", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/cn.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "geolocation-!cn", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/geolocation-!cn.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "google-ip", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/google.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "private-ip", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/private.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "cn-ip", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/cn.srs", "download_detour": "DIRECT" },
|
||||
{ "tag": "telegram-ip", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/telegram.srs", "download_detour": "DIRECT" }
|
||||
],
|
||||
"rules": [
|
||||
{ "action": "sniff" },
|
||||
{ "clash_mode": "direct", "outbound": "DIRECT" },
|
||||
{ "clash_mode": "global", "outbound": "♻️ 自动选择" },
|
||||
{ "protocol": "dns", "action": "hijack-dns" },
|
||||
{ "rule_set": "openai", "outbound": "🔮 OpenAI" },
|
||||
{ "rule_set": "anthropic", "outbound": "🧠 Claude" },
|
||||
{ "rule_set": "google-gemini", "outbound": "🤖 Gemini" },
|
||||
{ "rule_set": "youtube", "outbound": "▶️ YouTube" },
|
||||
{ "rule_set": "google", "outbound": "🔍 Google" },
|
||||
{ "rule_set": "twitter", "outbound": "🐦 Twitter" },
|
||||
{ "rule_set": "tiktok", "outbound": "🎵 TikTok" },
|
||||
{ "rule_set": "netflix", "outbound": "📺 Netflix" },
|
||||
{ "rule_set": ["hulu", "disney", "hbo", "amazon", "bahamut"], "outbound": "🎬 流媒体" },
|
||||
{ "rule_set": "paypal", "outbound": "💳 PayPal" },
|
||||
{ "rule_set": "microsoft", "outbound": "Ⓜ️ Microsoft" },
|
||||
{ "rule_set": "apple", "outbound": "🍏 苹果服务" },
|
||||
{ "rule_set": ["github", "gitlab"], "outbound": "📦 GitHub" },
|
||||
{ "rule_set": "telegram-ip", "outbound": "📲 电报消息" },
|
||||
{ "rule_set": "private-ip", "outbound": "🏠 私有网络" },
|
||||
{ "rule_set": ["geolocation-cn", "cn"], "outbound": "🎯 全球直连" },
|
||||
{ "rule_set": "cn-ip", "outbound": "🎯 全球直连" },
|
||||
{ "domain_suffix": ["cn"], "outbound": "🎯 全球直连" },
|
||||
{ "rule_set": "geolocation-!cn", "outbound": "🌐 非中国" },
|
||||
{ "rule_set": "google-ip", "outbound": "🔍 Google" }
|
||||
],
|
||||
"auto_detect_interface": true,
|
||||
"final": "🐟 漏网之鱼"
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": { "enabled": true, "store_fakeip": true },
|
||||
"clash_api": {
|
||||
"external_controller": "0.0.0.0:9090",
|
||||
"external_ui": "dashboard",
|
||||
"secret": "clashoo",
|
||||
"external_ui_download_url": "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
|
||||
"external_ui_download_detour": "DIRECT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,468 +0,0 @@
|
||||
#!/usr/bin/ucode
|
||||
/* yaml2singbox.uc — convert mihomo/clash YAML proxies to sing-box outbounds
|
||||
* Usage: ucode yaml2singbox.uc <yaml-in> [template-json] [json-out]
|
||||
* Supports: ss, vmess, vless, trojan, hysteria2, tuic. Others are skipped.
|
||||
* "__NODES__" in the template selector outbounds is expanded to all node tags. */
|
||||
|
||||
'use strict';
|
||||
|
||||
import { readfile, writefile, popen } from 'fs';
|
||||
|
||||
const TPL_DEFAULT = '/usr/share/clashoo/lib/templates/default.json';
|
||||
const SUPPORTED = {
|
||||
'ss': 'shadowsocks',
|
||||
'shadowsocks':'shadowsocks',
|
||||
'vmess': 'vmess',
|
||||
'vless': 'vless',
|
||||
'trojan': 'trojan',
|
||||
'hysteria2': 'hysteria2',
|
||||
'hy2': 'hysteria2',
|
||||
'tuic': 'tuic'
|
||||
};
|
||||
|
||||
function logerr(msg) {
|
||||
warn(sprintf("[yaml2singbox] %s\n", msg));
|
||||
}
|
||||
|
||||
function die(msg, code) {
|
||||
logerr(msg);
|
||||
exit(code || 1);
|
||||
}
|
||||
|
||||
/* ---------- YAML read (via yq) ---------- */
|
||||
function quote_sh(s) {
|
||||
/* single-quote for shell; escape embedded quotes */
|
||||
return "'" + replace(s, "'", "'\\''") + "'";
|
||||
}
|
||||
|
||||
function read_yaml_as_json(path) {
|
||||
const cmd = sprintf("yq -o=json eval . %s 2>/dev/null", quote_sh(path));
|
||||
const h = popen(cmd, "r");
|
||||
if (!h)
|
||||
die(sprintf("popen failed: %s", cmd));
|
||||
let buf = "", chunk;
|
||||
while ((chunk = h.read(65536)))
|
||||
buf += chunk;
|
||||
h.close();
|
||||
if (!length(buf))
|
||||
die(sprintf("yq returned empty for %s (yaml loadable?)", path));
|
||||
const data = json(buf);
|
||||
if (data === null)
|
||||
die("json parse of yq output failed");
|
||||
return data;
|
||||
}
|
||||
|
||||
/* ---------- helpers ---------- */
|
||||
function pick(obj, ...keys) {
|
||||
for (let k in keys)
|
||||
if (obj[k] != null)
|
||||
return obj[k];
|
||||
return null;
|
||||
}
|
||||
|
||||
function tobool(v) {
|
||||
if (v === true || v === 'true' || v === 1 || v === '1') return true;
|
||||
if (v === false || v === 'false' || v === 0 || v === '0') return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
function toint(v) {
|
||||
if (v == null) return null;
|
||||
let n = +v;
|
||||
return (n === n) ? n : null; /* NaN check */
|
||||
}
|
||||
|
||||
function strip_null(obj) {
|
||||
if (type(obj) !== 'object') return obj;
|
||||
const out = {};
|
||||
for (let k in obj) {
|
||||
const v = obj[k];
|
||||
if (v == null) continue;
|
||||
if (type(v) === 'object') {
|
||||
const sv = strip_null(v);
|
||||
if (length(sv) > 0) out[k] = sv;
|
||||
} else if (type(v) === 'array') {
|
||||
const arr = map(v, (x) => (type(x) === 'object') ? strip_null(x) : x);
|
||||
if (length(arr) > 0) out[k] = arr;
|
||||
} else {
|
||||
out[k] = v;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ---------- TLS block assembly ---------- */
|
||||
function build_tls(p) {
|
||||
const enabled = tobool(p.tls) || (p.sni && length(p.sni) > 0) || (p.servername && length(p.servername) > 0);
|
||||
if (!enabled) return null;
|
||||
const tls = {
|
||||
enabled: true,
|
||||
server_name: pick(p, 'sni', 'servername'),
|
||||
insecure: tobool(p['skip-cert-verify']) === true ? true : null,
|
||||
alpn: p.alpn
|
||||
};
|
||||
const reality = p['reality-opts'];
|
||||
if (reality && type(reality) === 'object') {
|
||||
tls.reality = {
|
||||
enabled: true,
|
||||
public_key: reality['public-key'],
|
||||
short_id: reality['short-id']
|
||||
};
|
||||
}
|
||||
const client_fp = p['client-fingerprint'];
|
||||
if (client_fp)
|
||||
tls.utls = { enabled: true, fingerprint: client_fp };
|
||||
return strip_null(tls);
|
||||
}
|
||||
|
||||
/* ---------- transport (ws / grpc / http) ---------- */
|
||||
function build_transport(p) {
|
||||
const net = p.network;
|
||||
if (!net || net === 'tcp') return null;
|
||||
if (net === 'ws') {
|
||||
const opts = p['ws-opts'] || {};
|
||||
return strip_null({
|
||||
type: 'ws',
|
||||
path: opts.path,
|
||||
headers: opts.headers,
|
||||
max_early_data: toint(opts['max-early-data']),
|
||||
early_data_header_name: opts['early-data-header-name']
|
||||
});
|
||||
}
|
||||
if (net === 'grpc') {
|
||||
const opts = p['grpc-opts'] || {};
|
||||
return { type: 'grpc', service_name: opts['grpc-service-name'] };
|
||||
}
|
||||
if (net === 'http' || net === 'h2') {
|
||||
const opts = p['h2-opts'] || p['http-opts'] || {};
|
||||
return strip_null({
|
||||
type: 'http',
|
||||
host: opts.host || opts.Host,
|
||||
path: opts.path
|
||||
});
|
||||
}
|
||||
if (net === 'httpupgrade') {
|
||||
const opts = p['http-upgrade-opts'] || {};
|
||||
return strip_null({ type: 'httpupgrade', path: opts.path, host: opts.host });
|
||||
}
|
||||
logerr(sprintf("unknown transport network '%s' for node '%s', ignored", net, p.name));
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---------- per-protocol conversion ---------- */
|
||||
function convert_ss(p) {
|
||||
const plugin = p.plugin, popts = p['plugin-opts'] || {};
|
||||
let plugin_opts = null;
|
||||
if (plugin && type(popts) === 'object') {
|
||||
/* Field mapping: Clash simple-obfs -> sing-box obfs-local
|
||||
* Clash: mode=http;host=xxx → sing-box: obfs=http;obfs-host=xxx */
|
||||
const FIELD_MAP = {
|
||||
'mode': 'obfs',
|
||||
'host': 'obfs-host',
|
||||
'uri': 'obfs-uri'
|
||||
};
|
||||
const parts = [];
|
||||
for (let k in popts) {
|
||||
const v = popts[k];
|
||||
if (type(v) === 'object') continue;
|
||||
push(parts, (FIELD_MAP[k] || k) + '=' + v);
|
||||
}
|
||||
plugin_opts = join(';', parts);
|
||||
}
|
||||
/* Clash "obfs" maps to sing-box plugin "obfs-local" */
|
||||
let sb_plugin = null;
|
||||
if (plugin === 'obfs') sb_plugin = 'obfs-local';
|
||||
else if (plugin === 'v2ray-plugin') sb_plugin = 'v2ray-plugin';
|
||||
else if (plugin === 'shadow-tls') sb_plugin = 'shadow-tls';
|
||||
else if (plugin) sb_plugin = plugin;
|
||||
|
||||
return strip_null({
|
||||
type: 'shadowsocks',
|
||||
tag: p.name,
|
||||
server: p.server,
|
||||
server_port: toint(p.port),
|
||||
method: p.cipher,
|
||||
password: p.password,
|
||||
plugin: sb_plugin,
|
||||
plugin_opts: plugin_opts
|
||||
});
|
||||
}
|
||||
|
||||
function convert_vmess(p) {
|
||||
return strip_null({
|
||||
type: 'vmess',
|
||||
tag: p.name,
|
||||
server: p.server,
|
||||
server_port: toint(p.port),
|
||||
uuid: p.uuid,
|
||||
alter_id: toint(p.alterId) || 0,
|
||||
security: p.cipher || 'auto',
|
||||
tls: build_tls(p),
|
||||
transport: build_transport(p)
|
||||
});
|
||||
}
|
||||
|
||||
function convert_vless(p) {
|
||||
return strip_null({
|
||||
type: 'vless',
|
||||
tag: p.name,
|
||||
server: p.server,
|
||||
server_port: toint(p.port),
|
||||
uuid: p.uuid,
|
||||
flow: p.flow,
|
||||
tls: build_tls(p),
|
||||
transport: build_transport(p)
|
||||
});
|
||||
}
|
||||
|
||||
function convert_trojan(p) {
|
||||
return strip_null({
|
||||
type: 'trojan',
|
||||
tag: p.name,
|
||||
server: p.server,
|
||||
server_port: toint(p.port),
|
||||
password: p.password,
|
||||
tls: build_tls(p) || { enabled: true, server_name: p.sni || p.server },
|
||||
transport: build_transport(p)
|
||||
});
|
||||
}
|
||||
|
||||
function convert_hysteria2(p) {
|
||||
const obfs_pass = p['obfs-password'];
|
||||
return strip_null({
|
||||
type: 'hysteria2',
|
||||
tag: p.name,
|
||||
server: p.server,
|
||||
server_port: toint(p.port),
|
||||
password: pick(p, 'password', 'auth'),
|
||||
up_mbps: toint(p.up),
|
||||
down_mbps: toint(p.down),
|
||||
obfs: (p.obfs && obfs_pass) ? { type: p.obfs, password: obfs_pass } : null,
|
||||
tls: build_tls(p) || { enabled: true, server_name: p.sni || p.server }
|
||||
});
|
||||
}
|
||||
|
||||
function convert_tuic(p) {
|
||||
return strip_null({
|
||||
type: 'tuic',
|
||||
tag: p.name,
|
||||
server: p.server,
|
||||
server_port: toint(p.port),
|
||||
uuid: p.uuid,
|
||||
password: p.password,
|
||||
congestion_control: p['congestion-controller'] || p.congestion,
|
||||
udp_relay_mode: p['udp-relay-mode'],
|
||||
tls: build_tls(p) || { enabled: true, server_name: p.sni || p.server, alpn: p.alpn }
|
||||
});
|
||||
}
|
||||
|
||||
function convert_proxy(p) {
|
||||
const sb_type = SUPPORTED[p.type];
|
||||
if (!sb_type) {
|
||||
if (p.type && p.type !== 'select')
|
||||
logerr(sprintf("skip unsupported type '%s' for '%s'", p.type, p.name));
|
||||
return null;
|
||||
}
|
||||
if (!p.server || !p.port || !p.name) {
|
||||
logerr(sprintf("skip proxy missing server/port/name (type=%s)", p.type));
|
||||
return null;
|
||||
}
|
||||
switch (p.type) {
|
||||
case 'ss':
|
||||
case 'shadowsocks': return convert_ss(p);
|
||||
case 'vmess': return convert_vmess(p);
|
||||
case 'vless': return convert_vless(p);
|
||||
case 'trojan': return convert_trojan(p);
|
||||
case 'hysteria2':
|
||||
case 'hy2': return convert_hysteria2(p);
|
||||
case 'tuic': return convert_tuic(p);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---------- dedup (tag must be unique) ---------- */
|
||||
function dedupe_tags(nodes) {
|
||||
const seen = {};
|
||||
for (let n in nodes) {
|
||||
let base = n.tag, t = base, i = 2;
|
||||
while (seen[t]) {
|
||||
t = base + '_' + i;
|
||||
i++;
|
||||
}
|
||||
n.tag = t;
|
||||
seen[t] = true;
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/* Drop airline pseudo-nodes (Traffic:/Expire:/quota/官网/QQ etc.) from selectors
|
||||
* and urltests. They're real outbounds (so the UI can read traffic/expiry) but
|
||||
* don't forward — in a selector/urltest they win (0ms) and swallow all traffic. */
|
||||
function is_pseudo_node_tag(tag) {
|
||||
if (!tag) return false;
|
||||
const t = '' + tag;
|
||||
const patterns = [
|
||||
/^Traffic[::]/i,
|
||||
/^Expire[::]/i,
|
||||
/剩余流量|剩余[::]/,
|
||||
/距离下次重置/,
|
||||
/到期(时间|日期)?[::]/,
|
||||
/官网[::]/,
|
||||
/网站[::]/,
|
||||
/套餐[::]?/,
|
||||
/客服[::]/,
|
||||
/QQ[群]?[::]/i,
|
||||
/Telegram|TG群|官方群/i,
|
||||
/续费|订阅地址|流量重置/,
|
||||
];
|
||||
for (let re in patterns) if (match(t, re)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ---------- detect region from tag -> 'HK'/'JP'/'US'/'SG'/'OTHER'/'' ---------- */
|
||||
function region_of(tag) {
|
||||
if (!tag) return '';
|
||||
const t = '' + tag;
|
||||
if (match(t, /港|🇭🇰|HK[^A-Za-z]|[^A-Za-z]HK|^HK$|[Hh]ong[Kk]/)) return 'HK';
|
||||
if (match(t, /日|🇯🇵|JP[^A-Za-z]|[^A-Za-z]JP|^JP$|[Jj]apan/)) return 'JP';
|
||||
if (match(t, /美|🇺🇸|US[^A-Za-z]|[^A-Za-z]US|^US$|[Uu]nited.?[Ss]tates|[Aa]merica/)) return 'US';
|
||||
if (match(t, /新加坡|🇸🇬|SG[^A-Za-z]|[^A-Za-z]SG|^SG$|[Ss]ingapore/)) return 'SG';
|
||||
if (match(t, /台湾|台|🇹🇼|TW[^A-Za-z]|[^A-Za-z]TW|^TW$|[Tt]aiwan|韩国|韩|🇰🇷|KR[^A-Za-z]|[^A-Za-z]KR|^KR$|[Kk]orea/)) return 'OTHER';
|
||||
return '';
|
||||
}
|
||||
|
||||
/* ---------- expand __NODES__/__NODES_XX__ placeholders to real tags (dropping pseudo-nodes) ---------- */
|
||||
function expand_node_placeholder(outbounds, node_tags) {
|
||||
const real_tags = [];
|
||||
for (let t in node_tags) if (!is_pseudo_node_tag(t)) push(real_tags, t);
|
||||
|
||||
/* bucket by region */
|
||||
let tags_hk = [], tags_jp = [], tags_us = [], tags_sg = [], tags_other = [];
|
||||
for (let t in real_tags) {
|
||||
const r = region_of(t);
|
||||
if (r === 'HK') push(tags_hk, t);
|
||||
else if (r === 'JP') push(tags_jp, t);
|
||||
else if (r === 'US') push(tags_us, t);
|
||||
else if (r === 'SG') push(tags_sg, t);
|
||||
else if (r === 'OTHER') push(tags_other, t);
|
||||
}
|
||||
/* when a region has no nodes, fall back to all nodes (avoid empty outbounds) */
|
||||
if (!length(tags_hk)) tags_hk = real_tags;
|
||||
if (!length(tags_jp)) tags_jp = real_tags;
|
||||
if (!length(tags_us)) tags_us = real_tags;
|
||||
if (!length(tags_sg)) tags_sg = real_tags;
|
||||
if (!length(tags_other)) tags_other = real_tags;
|
||||
|
||||
for (let ob in outbounds) {
|
||||
if (ob.type !== 'selector' && ob.type !== 'urltest') continue;
|
||||
if (!ob.outbounds || type(ob.outbounds) !== 'array') continue;
|
||||
const expanded = [];
|
||||
for (let item in ob.outbounds) {
|
||||
let list = null;
|
||||
if (item === '__NODES__') list = real_tags;
|
||||
else if (item === '__NODES_HK__') list = tags_hk;
|
||||
else if (item === '__NODES_JP__') list = tags_jp;
|
||||
else if (item === '__NODES_US__') list = tags_us;
|
||||
else if (item === '__NODES_SG__') list = tags_sg;
|
||||
else if (item === '__NODES_OTHER__') list = tags_other;
|
||||
if (list !== null) { for (let t in list) push(expanded, t); }
|
||||
else { push(expanded, item); }
|
||||
}
|
||||
ob.outbounds = expanded;
|
||||
}
|
||||
return outbounds;
|
||||
}
|
||||
|
||||
/* ---------- proxy-providers ---------- */
|
||||
function resolve_providers(yaml) {
|
||||
if (type(yaml['proxy-providers']) !== 'object')
|
||||
return [];
|
||||
const all = [];
|
||||
for (let name in yaml['proxy-providers']) {
|
||||
const p = yaml['proxy-providers'][name];
|
||||
if (p.type !== 'http' || !p.url) {
|
||||
logerr(sprintf("provider '%s': skip (not http or no url)", name));
|
||||
continue;
|
||||
}
|
||||
const cmd = sprintf("wget -q -O- --timeout=20 %s 2>/dev/null | yq -o=json eval . 2>/dev/null", quote_sh(p.url));
|
||||
const h = popen(cmd, "r");
|
||||
if (!h) { logerr(sprintf("provider '%s': download failed", name)); continue; }
|
||||
let buf = "", chunk;
|
||||
while ((chunk = h.read(65536))) buf += chunk;
|
||||
h.close();
|
||||
if (!length(buf)) { logerr(sprintf("provider '%s': empty response", name)); continue; }
|
||||
const data = json(buf);
|
||||
if (!data || type(data.proxies) !== 'array') {
|
||||
logerr(sprintf("provider '%s': no proxies array in response", name));
|
||||
continue;
|
||||
}
|
||||
logerr(sprintf("provider '%s': %d proxies", name, length(data.proxies)));
|
||||
for (let px in data.proxies) push(all, px);
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
/* ---------- main ---------- */
|
||||
const yaml_path = ARGV[0];
|
||||
const tpl_path = ARGV[1] || TPL_DEFAULT;
|
||||
const out_path = ARGV[2];
|
||||
|
||||
if (!yaml_path)
|
||||
die("usage: ucode yaml2singbox.uc <yaml-in> [template-json] [json-out]");
|
||||
|
||||
const yaml = read_yaml_as_json(yaml_path);
|
||||
|
||||
let proxies = [];
|
||||
if (type(yaml.proxies) === 'array')
|
||||
proxies = yaml.proxies;
|
||||
else
|
||||
logerr("no inline proxies array, checking proxy-providers...");
|
||||
|
||||
if (type(yaml['proxy-providers']) === 'object') {
|
||||
const pproxies = resolve_providers(yaml);
|
||||
for (let p in pproxies) push(proxies, p);
|
||||
}
|
||||
|
||||
if (!length(proxies))
|
||||
die(sprintf("no proxies found in %s (inline or via providers)", yaml_path));
|
||||
|
||||
const tpl_raw = readfile(tpl_path);
|
||||
if (!tpl_raw)
|
||||
die(sprintf("cannot read template %s", tpl_path));
|
||||
const tpl = json(tpl_raw);
|
||||
if (!tpl || type(tpl.outbounds) !== 'array')
|
||||
die(sprintf("template %s has no outbounds[]", tpl_path));
|
||||
|
||||
/* convert proxies */
|
||||
const nodes = [];
|
||||
let skipped = 0;
|
||||
for (let p in proxies) {
|
||||
const o = convert_proxy(p);
|
||||
if (o) push(nodes, o); else skipped++;
|
||||
}
|
||||
dedupe_tags(nodes);
|
||||
|
||||
if (!length(nodes))
|
||||
die(sprintf("no usable nodes converted from %d proxies (skipped=%d)", length(proxies), skipped));
|
||||
|
||||
logerr(sprintf("converted=%d skipped=%d", length(nodes), skipped));
|
||||
|
||||
/* prepend converted nodes to template outbounds */
|
||||
const final_outbounds = [];
|
||||
for (let n in nodes) push(final_outbounds, n);
|
||||
for (let ob in tpl.outbounds) push(final_outbounds, ob);
|
||||
|
||||
/* expand __NODES__ placeholders */
|
||||
const node_tags = map(nodes, (n) => n.tag);
|
||||
expand_node_placeholder(final_outbounds, node_tags);
|
||||
|
||||
tpl.outbounds = final_outbounds;
|
||||
|
||||
const out = sprintf("%.J\n", tpl);
|
||||
if (out_path) {
|
||||
if (!writefile(out_path, out))
|
||||
die(sprintf("writefile failed: %s", out_path));
|
||||
logerr(sprintf("wrote %s (%d bytes, %d nodes)", out_path, length(out), length(nodes)));
|
||||
} else {
|
||||
print(out);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -f
|
||||
|
||||
[ "$(uci -q get clashoo.config.fake_ip_filter_migrated 2>/dev/null)" = "1" ] && exit 0
|
||||
|
||||
filters="$(uci -q get clashoo.config.fake_ip_filter 2>/dev/null)"
|
||||
has_legacy=0
|
||||
for filter in $filters; do
|
||||
[ "$filter" = "*.lan" ] && has_legacy=1
|
||||
done
|
||||
|
||||
if [ "$has_legacy" = "1" ]; then
|
||||
uci -q delete clashoo.config.fake_ip_filter
|
||||
added_lan=0
|
||||
added_local=0
|
||||
for filter in $filters; do
|
||||
case "$filter" in
|
||||
'*.lan'|'+.lan')
|
||||
if [ "$added_lan" = "0" ]; then
|
||||
uci -q add_list clashoo.config.fake_ip_filter='+.lan'
|
||||
added_lan=1
|
||||
fi
|
||||
;;
|
||||
'+.local')
|
||||
if [ "$added_local" = "0" ]; then
|
||||
uci -q add_list clashoo.config.fake_ip_filter='+.local'
|
||||
added_local=1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
uci -q add_list clashoo.config.fake_ip_filter="$filter"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
if [ "$added_local" = "0" ]; then
|
||||
uci -q add_list clashoo.config.fake_ip_filter='+.local'
|
||||
fi
|
||||
fi
|
||||
|
||||
uci -q set clashoo.config.fake_ip_filter_migrated='1'
|
||||
uci -q commit clashoo
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ "$(uci -q get clashoo.config.acl_migrated 2>/dev/null)" = "1" ] && exit 0
|
||||
if uci -q show clashoo 2>/dev/null | grep -q '=lan_acl$'; then
|
||||
uci -q set clashoo.config.acl_migrated='1'
|
||||
uci -q commit clashoo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mode="$(uci -q get clashoo.config.access_control 2>/dev/null)"
|
||||
|
||||
add_group() {
|
||||
local dns="$1" proxy="$2" list_key="${3:-}" section value
|
||||
|
||||
section="$(uci -q add clashoo lan_acl)" || exit 1
|
||||
uci -q set clashoo."$section".enabled='1'
|
||||
uci -q set clashoo."$section".dns="$dns"
|
||||
uci -q set clashoo."$section".proxy="$proxy"
|
||||
if [ -n "$list_key" ]; then
|
||||
for value in $(uci -q get clashoo.config."$list_key" 2>/dev/null); do
|
||||
uci -q add_list clashoo."$section".ip="$value"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
case "$mode" in
|
||||
1)
|
||||
add_group 1 1 proxy_lan_ips
|
||||
add_group 0 0
|
||||
;;
|
||||
2)
|
||||
add_group 0 0 reject_lan_ips
|
||||
add_group 1 1
|
||||
;;
|
||||
esac
|
||||
|
||||
uci -q set clashoo.config.acl_migrated='1'
|
||||
uci -q commit clashoo
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/bin/sh
|
||||
# clashoo access check
|
||||
# 用法: access_check.sh <url> [mode]
|
||||
# mode = direct | proxy(默认 proxy)
|
||||
# direct: 经 openwrt 主干直连,不走任何代理
|
||||
# proxy: 走 clashoo 本地代理端口,验证核心出站
|
||||
|
||||
url="$1"
|
||||
mode="${2:-proxy}"
|
||||
[ -n "$url" ] || exit 1
|
||||
|
||||
attempts=1
|
||||
ok=0
|
||||
sum_ms=0
|
||||
last_code=000
|
||||
|
||||
url_host() {
|
||||
printf '%s' "$1" | sed -n 's#^[a-zA-Z][a-zA-Z0-9+.-]*://\([^/:?#]*\).*$#\1#p'
|
||||
}
|
||||
|
||||
url_port() {
|
||||
_u="$1"
|
||||
_p="$(printf '%s' "$_u" | sed -n 's#^[a-zA-Z][a-zA-Z0-9+.-]*://[^/:?#]*:\([0-9][0-9]*\).*$#\1#p')"
|
||||
[ -n "$_p" ] && { printf '%s' "$_p"; return; }
|
||||
case "$_u" in
|
||||
https://*) printf '443' ;;
|
||||
http://*) printf '80' ;;
|
||||
*) printf '443' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_with_timeout() {
|
||||
_secs="$1"
|
||||
shift
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout "$_secs" "$@" 2>/dev/null
|
||||
else
|
||||
"$@" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_real_ip() {
|
||||
_host="$1"
|
||||
[ -n "$_host" ] || return 0
|
||||
for _dns in 223.5.5.5 119.29.29.29 1.1.1.1; do
|
||||
_ip="$(run_with_timeout 1 nslookup "$_host" "$_dns" | awk '/^Address: /{print $2} /^Address [0-9]+: /{print $3}' | grep -E '^[0-9]+(\.[0-9]+){3}$' | tail -n1)"
|
||||
[ -n "$_ip" ] && { printf '%s' "$_ip"; return; }
|
||||
done
|
||||
}
|
||||
|
||||
set -- -4 -L --max-time 5 --connect-timeout 3 -s -o /dev/null -w '%{http_code} %{time_total}'
|
||||
if [ "$mode" = "direct" ]; then
|
||||
# 强制不走任何代理,避免误读 http_proxy 环境变量
|
||||
set -- "$@" --noproxy '*'
|
||||
host="$(url_host "$url")"
|
||||
port="$(url_port "$url")"
|
||||
real_ip="$(resolve_real_ip "$host")"
|
||||
[ -n "$host" ] && [ -n "$port" ] && [ -n "$real_ip" ] && set -- "$@" --resolve "${host}:${port}:${real_ip}"
|
||||
else
|
||||
proxy_port="$(uci get clashoo.config.mixed_port 2>/dev/null)"
|
||||
[ -z "$proxy_port" ] && proxy_port="$(uci get clashoo.config.http_port 2>/dev/null)"
|
||||
[ -z "$proxy_port" ] && proxy_port=7890
|
||||
set -- "$@" -x "http://127.0.0.1:${proxy_port}"
|
||||
fi
|
||||
set -- "$@" "$url"
|
||||
|
||||
i=1
|
||||
while [ "$i" -le "$attempts" ]; do
|
||||
out="$(curl "$@" 2>/dev/null || true)"
|
||||
code="$(printf '%s' "$out" | awk '{print $1}')"
|
||||
time_s="$(printf '%s' "$out" | awk '{print $2}')"
|
||||
[ -n "$code" ] || code=000
|
||||
[ -n "$time_s" ] || time_s=0
|
||||
ms="$(awk -v t="$time_s" 'BEGIN{printf "%d", t*1000}')"
|
||||
last_code="$code"
|
||||
if [ "$code" = "200" ] || [ "$code" = "204" ] || [ "$code" = "301" ] || [ "$code" = "302" ]; then
|
||||
ok=$((ok + 1))
|
||||
sum_ms=$((sum_ms + ms))
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
loss=$((attempts - ok))
|
||||
avg_ms=0
|
||||
if [ "$ok" -gt 0 ]; then
|
||||
avg_ms=$((sum_ms / ok))
|
||||
fi
|
||||
|
||||
echo "ok=$ok attempts=$attempts loss=$loss avg_ms=$avg_ms code=$last_code"
|
||||
@@ -1,195 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
CACHE_DIR="/tmp/clashoo"
|
||||
CACHE_FILE="/tmp/clashoo_check_cache"
|
||||
LOCK_DIR="${CACHE_DIR}/access_check.lock"
|
||||
LOCK_PID_FILE="${LOCK_DIR}/pid"
|
||||
UPDATING_FLAG="${CACHE_DIR}/access_check_updating"
|
||||
TMP_FILE="${CACHE_FILE}.tmp.$$"
|
||||
DIAG_LOG="/tmp/clashoo_access_check.log"
|
||||
|
||||
take_lock() {
|
||||
mkdir "$LOCK_DIR" 2>/dev/null || return 1
|
||||
printf '%s\n' "$$" > "$LOCK_PID_FILE" 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
pid_is_cache_worker() {
|
||||
[ -r "/proc/$1/cmdline" ] || return 1
|
||||
tr '\0' ' ' <"/proc/$1/cmdline" 2>/dev/null | grep -q 'access_check_cache\.sh'
|
||||
}
|
||||
|
||||
lock_is_dead() {
|
||||
_pid="$(cat "$LOCK_PID_FILE" 2>/dev/null)"
|
||||
case "$_pid" in
|
||||
''|*[!0-9]*)
|
||||
[ -n "$(find "$LOCK_DIR" -maxdepth 0 -mmin +3 2>/dev/null)" ]
|
||||
return $?
|
||||
;;
|
||||
esac
|
||||
[ ! -d "/proc/$_pid" ] && return 0
|
||||
! pid_is_cache_worker "$_pid"
|
||||
}
|
||||
|
||||
mkdir -p "$CACHE_DIR"
|
||||
if ! take_lock; then
|
||||
lock_is_dead || exit 0
|
||||
rm -rf "$LOCK_DIR" >/dev/null 2>&1
|
||||
take_lock || exit 0
|
||||
fi
|
||||
trap 'rm -rf "$LOCK_DIR" "$UPDATING_FLAG" "$TMP_FILE"' EXIT INT TERM
|
||||
: > "$UPDATING_FLAG"
|
||||
|
||||
safe_int() {
|
||||
case "${1:-}" in
|
||||
''|*[!0-9]*)
|
||||
printf '0'
|
||||
;;
|
||||
*)
|
||||
printf '%s' "$1"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
safe_code() {
|
||||
case "${1:-}" in
|
||||
''|*[!0-9A-Za-z]*)
|
||||
printf '000'
|
||||
;;
|
||||
*)
|
||||
printf '%s' "$1"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
parse_field() {
|
||||
_line="$1"
|
||||
_key="$2"
|
||||
printf '%s\n' "$_line" | sed -n "s/.*${_key}=\\([^ ]*\\).*/\\1/p"
|
||||
}
|
||||
|
||||
probe_json() {
|
||||
_line="$1"
|
||||
_ok="$(safe_int "$(parse_field "$_line" "ok")")"
|
||||
_attempts="$(safe_int "$(parse_field "$_line" "attempts")")"
|
||||
_loss="$(safe_int "$(parse_field "$_line" "loss")")"
|
||||
_avg_ms="$(safe_int "$(parse_field "$_line" "avg_ms")")"
|
||||
_code="$(safe_code "$(parse_field "$_line" "code")")"
|
||||
|
||||
_state="down"
|
||||
if [ "$_ok" -ge "$_attempts" ] && [ "$_attempts" -gt 0 ]; then
|
||||
if [ "$_avg_ms" -ge 2500 ]; then
|
||||
_state="high_latency"
|
||||
else
|
||||
_state="ok"
|
||||
fi
|
||||
elif [ "$_ok" -gt 0 ]; then
|
||||
_state="loss"
|
||||
fi
|
||||
|
||||
_ok_bool=false
|
||||
[ "$_ok" -gt 0 ] && _ok_bool=true
|
||||
|
||||
printf '{"ok":%s,"state":"%s","code":"%s","ok_count":%s,"attempts":%s,"loss":%s,"avg_ms":%s}' \
|
||||
"$_ok_bool" "$_state" "$_code" "$_ok" "$_attempts" "$_loss" "$_avg_ms"
|
||||
}
|
||||
|
||||
log_diag() {
|
||||
_line="$(date '+%Y-%m-%d %H:%M:%S') $1"
|
||||
printf '%s\n' "$_line" >>"$DIAG_LOG" 2>/dev/null
|
||||
_n="$(wc -l <"$DIAG_LOG" 2>/dev/null || echo 0)"
|
||||
if [ "${_n:-0}" -gt 200 ]; then
|
||||
tail -n 100 "$DIAG_LOG" >"${DIAG_LOG}.tmp" 2>/dev/null && mv "${DIAG_LOG}.tmp" "$DIAG_LOG" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
probe_run() {
|
||||
_url="$1"
|
||||
_mode="$2"
|
||||
# nice + ionice:探测 IO/CPU 都低优先级,避免抢占 LuCI dispatcher
|
||||
nice -n 19 /usr/share/clashoo/net/access_check.sh "$_url" "$_mode" 2>/dev/null || true
|
||||
}
|
||||
|
||||
proxy_port="$(uci -q get clashoo.config.mixed_port)"
|
||||
[ -z "$proxy_port" ] && proxy_port="$(uci -q get clashoo.config.http_port)"
|
||||
[ -z "$proxy_port" ] && proxy_port="7890"
|
||||
tcp_mode="$(uci -q get clashoo.config.tcp_mode)"
|
||||
[ -z "$tcp_mode" ] && tcp_mode="redirect"
|
||||
udp_mode="$(uci -q get clashoo.config.udp_mode)"
|
||||
[ -z "$udp_mode" ] && udp_mode="$tcp_mode"
|
||||
|
||||
proxy_listening() {
|
||||
# 检测 mixed-port 是否在 LISTEN(clashoo 未运行时立即标 proxy down,避免显示假绿)
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
ss -tln 2>/dev/null | awk -v p=":${proxy_port}" '$0 ~ "LISTEN" && index($4, p) {found=1; exit} END{exit !found}'
|
||||
elif command -v netstat >/dev/null 2>&1; then
|
||||
netstat -tln 2>/dev/null | awk -v p=":${proxy_port}" '$0 ~ "LISTEN" && index($4, p) {found=1; exit} END{exit !found}'
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# 并行探测,把 CPU 抢占窗口从串行 2s+ 压缩到最慢一路的耗时
|
||||
f_db="${TMP_FILE}.db"
|
||||
f_dy="${TMP_FILE}.dy"
|
||||
f_pb="${TMP_FILE}.pb"
|
||||
f_py="${TMP_FILE}.py"
|
||||
probe_run "https://www.douyin.com/generate_204" "direct" >"$f_db" &
|
||||
probe_run "https://www.youtube.com/generate_204" "direct" >"$f_dy" &
|
||||
if proxy_listening; then
|
||||
proxy_skipped=0
|
||||
probe_run "https://www.douyin.com/generate_204" "proxy" >"$f_pb" &
|
||||
probe_run "https://www.youtube.com/generate_204" "proxy" >"$f_py" &
|
||||
else
|
||||
# 代理端口未监听(clashoo 已停止),跳过探测,直接标 down
|
||||
proxy_skipped=1
|
||||
echo "ok=0 attempts=1 loss=1 avg_ms=0 code=skip" >"$f_pb"
|
||||
echo "ok=0 attempts=1 loss=1 avg_ms=0 code=skip" >"$f_py"
|
||||
fi
|
||||
wait
|
||||
direct_bytedance="$(cat "$f_db" 2>/dev/null)"
|
||||
direct_youtube="$(cat "$f_dy" 2>/dev/null)"
|
||||
proxy_bytedance="$(cat "$f_pb" 2>/dev/null)"
|
||||
proxy_youtube="$(cat "$f_py" 2>/dev/null)"
|
||||
rm -f "$f_db" "$f_dy" "$f_pb" "$f_py"
|
||||
|
||||
updated_at="$(date +%s)"
|
||||
|
||||
probe_all_down() {
|
||||
for _p in "$direct_bytedance" "$direct_youtube" "$proxy_bytedance" "$proxy_youtube"; do
|
||||
case "$_p" in
|
||||
''|*"ok=0 "*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "$(uci -q get clashoo.config.access_check_debug)" = "1" ] || probe_all_down; then
|
||||
log_diag "port=${proxy_port} proxy_skipped=${proxy_skipped} direct_bd=[${direct_bytedance}] direct_yt=[${direct_youtube}] proxy_bd=[${proxy_bytedance}] proxy_yt=[${proxy_youtube}]"
|
||||
fi
|
||||
|
||||
cat >"$TMP_FILE" <<EOF
|
||||
{
|
||||
"proxy_port": "${proxy_port}",
|
||||
"tcp_mode": "${tcp_mode}",
|
||||
"udp_mode": "${udp_mode}",
|
||||
"updated_at": ${updated_at},
|
||||
"proxy_skipped": ${proxy_skipped},
|
||||
"stale": false,
|
||||
"updating": false,
|
||||
"direct": {
|
||||
"bytedance": $(probe_json "$direct_bytedance"),
|
||||
"youtube": $(probe_json "$direct_youtube")
|
||||
},
|
||||
"proxy": {
|
||||
"bytedance": $(probe_json "$proxy_bytedance"),
|
||||
"youtube": $(probe_json "$proxy_youtube")
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
mv -f "$TMP_FILE" "$CACHE_FILE"
|
||||
exit 0
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
RUNDIR="/tmp/clashoo"
|
||||
PID_FILE="${RUNDIR}/access_check_daemon.pid"
|
||||
LOCK_DIR="${RUNDIR}/access_check_daemon.lock"
|
||||
# 周期:默认 30s,ACCESS_CHECK_INTERVAL 环境变量可固定覆盖
|
||||
INTERVAL_FIXED="${ACCESS_CHECK_INTERVAL:-}"
|
||||
|
||||
mkdir -p "$RUNDIR"
|
||||
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
||||
old_pid="$(cat "$PID_FILE" 2>/dev/null)"
|
||||
if [ -n "$old_pid" ] && [ -d "/proc/$old_pid" ] && \
|
||||
tr '\0' ' ' <"/proc/$old_pid/cmdline" 2>/dev/null | grep -q 'access_check_daemon\.sh'; then
|
||||
exit 0
|
||||
fi
|
||||
rm -rf "$LOCK_DIR" "$PID_FILE" >/dev/null 2>&1
|
||||
mkdir "$LOCK_DIR" 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$LOCK_DIR" "$PID_FILE"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'cleanup; exit 0' INT TERM
|
||||
|
||||
echo "$$" >"$PID_FILE"
|
||||
|
||||
while :; do
|
||||
[ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ] || break
|
||||
/usr/share/clashoo/net/access_check_cache.sh >/dev/null 2>&1 || true
|
||||
if [ -n "$INTERVAL_FIXED" ]; then
|
||||
sleep "$INTERVAL_FIXED"
|
||||
else
|
||||
sleep 30
|
||||
fi
|
||||
done
|
||||
@@ -1,240 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
APPLY=0
|
||||
TIMEOUT="${CLASHOO_DNS_TEST_TIMEOUT:-2}"
|
||||
START_TS="$(date +%s 2>/dev/null || echo 0)"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--apply) APPLY=1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
json_escape() {
|
||||
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
||||
}
|
||||
|
||||
json_pair() {
|
||||
printf '"%s":"%s"' "$1" "$(json_escape "$2")"
|
||||
}
|
||||
|
||||
is_ipv4() {
|
||||
echo "$1" | grep -Eq '^[0-9]+(\.[0-9]+){3}$'
|
||||
}
|
||||
|
||||
append_unique() {
|
||||
list="$1"
|
||||
item="$2"
|
||||
[ -n "$item" ] || { printf '%s' "$list"; return; }
|
||||
case " $list " in
|
||||
*" $item "*) printf '%s' "$list" ;;
|
||||
*) printf '%s %s' "$list" "$item" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
current_default_nameservers() {
|
||||
uci -q get clashoo.config.default_nameserver 2>/dev/null || true
|
||||
}
|
||||
|
||||
current_dns_servers() {
|
||||
uci -q show clashoo 2>/dev/null | awk -F= '
|
||||
function clean(v) {
|
||||
gsub(/^'\''|'\''$/, "", v);
|
||||
return v;
|
||||
}
|
||||
/^clashoo\.@dnsservers\[[0-9]+\]\./ {
|
||||
line=$1;
|
||||
val=clean($2);
|
||||
sub(/^clashoo\.@dnsservers\[/, "", line);
|
||||
idx=line;
|
||||
sub(/\].*$/, "", idx);
|
||||
opt=line;
|
||||
sub(/^[0-9]+\]\./, "", opt);
|
||||
data[idx, opt]=val;
|
||||
seen[idx]=1;
|
||||
}
|
||||
END {
|
||||
for (idx in seen) {
|
||||
if (data[idx, "enabled"] == "0") continue;
|
||||
role=data[idx, "ser_type"];
|
||||
addr=data[idx, "ser_address"];
|
||||
proto=data[idx, "protocol"];
|
||||
port=data[idx, "ser_port"];
|
||||
if (addr == "") continue;
|
||||
if (proto != "" && proto != "none" && addr !~ /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//) {
|
||||
addr=proto addr;
|
||||
if (port != "" && addr !~ /:[0-9]+$/) addr=addr ":" port;
|
||||
}
|
||||
print role "|" addr;
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
servers_for_role() {
|
||||
role="$1"
|
||||
current_dns_servers | awk -F'|' -v role="$role" '$1 == role { print $2 }'
|
||||
}
|
||||
|
||||
candidate_host_port() {
|
||||
uri="$1"
|
||||
default_port="$2"
|
||||
hostport="${uri#*://}"
|
||||
hostport="${hostport%%/*}"
|
||||
host="${hostport%%:*}"
|
||||
port="${hostport##*:}"
|
||||
[ "$port" != "$hostport" ] || port="$default_port"
|
||||
printf '%s %s\n' "$host" "$port"
|
||||
}
|
||||
|
||||
probe_tls() {
|
||||
uri="$1"
|
||||
set -- $(candidate_host_port "$uri" 853)
|
||||
host="$1"
|
||||
port="$2"
|
||||
command -v nc >/dev/null 2>&1 || return 1
|
||||
nc -w "$TIMEOUT" "$host" "$port" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
probe_ip_dns() {
|
||||
ip="$1"
|
||||
domain="$2"
|
||||
command -v nslookup >/dev/null 2>&1 || return 1
|
||||
nslookup "$domain" "$ip" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
probe_latency_ms() {
|
||||
candidate="$1"
|
||||
domain="$2"
|
||||
if [ "${candidate#https://}" != "$candidate" ]; then
|
||||
if printf '%s' "$candidate" | grep -q '?'; then
|
||||
q="$candidate&name=$domain&type=A"
|
||||
else
|
||||
q="$candidate?name=$domain&type=A"
|
||||
fi
|
||||
command -v curl >/dev/null 2>&1 || return 1
|
||||
t="$(curl -fsS --connect-timeout "$TIMEOUT" --max-time "$TIMEOUT" \
|
||||
-H 'accept: application/dns-json' -o /dev/null -w '%{time_total}' "$q" 2>/dev/null)" || return 1
|
||||
printf '%s' "$t" | awk '{ms=$1*1000; if (ms<1) ms=1; printf "%d", ms}'
|
||||
elif [ "${candidate#tls://}" != "$candidate" ]; then
|
||||
probe_tls "$candidate" || return 1
|
||||
printf '900'
|
||||
elif [ "${candidate#udp://}" != "$candidate" ]; then
|
||||
probe_ip_dns "${candidate#udp://}" "$domain" || return 1
|
||||
printf '950'
|
||||
elif [ "${candidate#tcp://}" != "$candidate" ]; then
|
||||
probe_ip_dns "${candidate#tcp://}" "$domain" || return 1
|
||||
printf '950'
|
||||
else
|
||||
is_ipv4 "$candidate" && probe_ip_dns "$candidate" "$domain" || return 1
|
||||
printf '950'
|
||||
fi
|
||||
}
|
||||
|
||||
select_best() {
|
||||
list="$1"
|
||||
domain="$2"
|
||||
fallback="$3"
|
||||
best=""
|
||||
best_score=999999
|
||||
order=0
|
||||
for candidate in $list; do
|
||||
order=$((order + 1))
|
||||
ms="$(probe_latency_ms "$candidate" "$domain")" || { FAILED_COUNT=$((FAILED_COUNT + 1)); continue; }
|
||||
score=$((ms * 100 + order))
|
||||
if [ "$score" -lt "$best_score" ]; then
|
||||
best="$candidate"
|
||||
best_score="$score"
|
||||
fi
|
||||
done
|
||||
[ -n "$best" ] || best="$fallback"
|
||||
printf '%s' "$best"
|
||||
}
|
||||
|
||||
add_dnsserver() {
|
||||
role="$1"
|
||||
addr="$2"
|
||||
proto="$3"
|
||||
port="$4"
|
||||
sec="$(uci -q add clashoo dnsservers)" || return 1
|
||||
[ -n "$sec" ] || return 1
|
||||
uci -q set clashoo."$sec".enabled='1' || return 1
|
||||
uci -q set clashoo."$sec".ser_type="$role" || return 1
|
||||
uci -q set clashoo."$sec".ser_address="$addr" || return 1
|
||||
uci -q set clashoo."$sec".protocol="$proto" || return 1
|
||||
if [ -n "$port" ]; then
|
||||
uci -q set clashoo."$sec".ser_port="$port" || return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
set_default_nameserver() {
|
||||
uci -q delete clashoo.config.default_nameserver >/dev/null 2>&1 || true
|
||||
uci -q add_list clashoo.config.default_nameserver="$1" || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
apply_result() {
|
||||
uci -q set clashoo.config.enable_dns='1' || { uci -q revert clashoo; return 1; }
|
||||
set_default_nameserver "$BOOTSTRAP" || { uci -q revert clashoo; return 1; }
|
||||
while uci -q delete clashoo.@dnsservers[0] >/dev/null 2>&1; do :; done
|
||||
add_dnsserver 'nameserver' "$NAMESERVER" 'none' '' || { uci -q revert clashoo; return 1; }
|
||||
add_dnsserver 'direct-nameserver' "$BOOTSTRAP" 'udp://' '' || { uci -q revert clashoo; return 1; }
|
||||
add_dnsserver 'proxy-server-nameserver' "$PROXY_NS" 'none' '' || { uci -q revert clashoo; return 1; }
|
||||
add_dnsserver 'fallback' "$FALLBACK_NS" 'none' '' || { uci -q revert clashoo; return 1; }
|
||||
uci -q commit clashoo || { uci -q revert clashoo; return 1; }
|
||||
return 0
|
||||
}
|
||||
|
||||
is_domestic_dns() {
|
||||
printf '%s' "$1" | grep -qE 'alidns|doh\.pub|dnspod|223\.5\.5\.5|223\.6\.6\.6|119\.29\.29\.29|119\.28\.28\.28|114\.114|180\.184|onedns'
|
||||
}
|
||||
is_overseas_dns() {
|
||||
printf '%s' "$1" | grep -qE 'cloudflare|1\.1\.1\.1|1\.0\.0\.1|one\.one\.one\.one|dns\.google|8\.8\.8\.8|8\.8\.4\.4|9\.9\.9\.9|quad9|opendns|208\.67\.'
|
||||
}
|
||||
|
||||
FAILED_COUNT=0
|
||||
BOOTSTRAP_CANDIDATES="223.5.5.5 119.29.29.29 180.184.1.1"
|
||||
DOMESTIC_CANDIDATES="https://dns.alidns.com/dns-query https://doh.pub/dns-query udp://223.5.5.5 udp://119.29.29.29"
|
||||
FALLBACK_CANDIDATES="https://cloudflare-dns.com/dns-query https://dns.google/dns-query tls://1.1.1.1:853"
|
||||
|
||||
for ns in $(current_default_nameservers); do
|
||||
is_ipv4 "$ns" && BOOTSTRAP_CANDIDATES="$(append_unique "$BOOTSTRAP_CANDIDATES" "$ns")"
|
||||
done
|
||||
for ns in $(servers_for_role 'nameserver') $(servers_for_role 'direct-nameserver'); do
|
||||
is_domestic_dns "$ns" && DOMESTIC_CANDIDATES="$(append_unique "$DOMESTIC_CANDIDATES" "$ns")"
|
||||
done
|
||||
for ns in $(servers_for_role 'fallback'); do
|
||||
is_overseas_dns "$ns" && FALLBACK_CANDIDATES="$(append_unique "$FALLBACK_CANDIDATES" "$ns")"
|
||||
done
|
||||
|
||||
BOOTSTRAP="$(select_best "$BOOTSTRAP_CANDIDATES" www.baidu.com 223.5.5.5)"
|
||||
NAMESERVER="$(select_best "$DOMESTIC_CANDIDATES" www.baidu.com https://dns.alidns.com/dns-query)"
|
||||
PROXY_NS="tls://1.1.1.1:853"
|
||||
FALLBACK_NS="$(select_best "$FALLBACK_CANDIDATES" www.google.com https://cloudflare-dns.com/dns-query)"
|
||||
|
||||
APPLIED=false
|
||||
if [ "$APPLY" = "1" ]; then
|
||||
if apply_result; then
|
||||
APPLIED=true
|
||||
else
|
||||
printf '{"success":false,"error":"apply_failed","message":"DNS 自动配置写入失败"}\n'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
END_TS="$(date +%s 2>/dev/null || echo "$START_TS")"
|
||||
ELAPSED_MS=$(( (END_TS - START_TS) * 1000 ))
|
||||
|
||||
printf '{'
|
||||
printf '"success":true,'
|
||||
printf '"applied":%s,' "$APPLIED"
|
||||
printf '"restarted":false,'
|
||||
json_pair bootstrap "$BOOTSTRAP"; printf ','
|
||||
json_pair nameserver "$NAMESERVER"; printf ','
|
||||
json_pair direct_nameserver "$BOOTSTRAP"; printf ','
|
||||
json_pair proxy_nameserver "$PROXY_NS"; printf ','
|
||||
json_pair fallback "$FALLBACK_NS"; printf ','
|
||||
printf '"failed_count":%s,' "$FAILED_COUNT"
|
||||
printf '"elapsed_ms":%s' "$ELAPSED_MS"
|
||||
printf '}\n'
|
||||
@@ -1,898 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
NFT_DIR="/var/run/clash"
|
||||
SETS_RULES="${NFT_DIR}/fw4_sets.nft"
|
||||
DSTNAT_RULES="${NFT_DIR}/fw4_dstnat.nft"
|
||||
MANGLE_RULES="${NFT_DIR}/fw4_mangle.nft"
|
||||
OUTPUT_RULES="${NFT_DIR}/fw4_output.nft"
|
||||
BUILTIN_NFT_DIR="/usr/share/clashoo/nftables"
|
||||
GEOIP_CN_NFT="${BUILTIN_NFT_DIR}/geoip_cn.nft"
|
||||
GEOIP6_CN_NFT="${BUILTIN_NFT_DIR}/geoip6_cn.nft"
|
||||
LOCAL_OUTPUT_TABLE="clashoo_local"
|
||||
QUIC_BLOCK_TABLE="clashoo_quic"
|
||||
# PROXY_FWMARK: inbound TPROXY mark, ip rule -> table PROXY_ROUTE_TABLE -> lo.
|
||||
# CORE_ROUTING_MARK: mihomo outbound SO_MARK (= routing-mark in config).
|
||||
# Must differ: otherwise mihomo egress is pulled to lo -> unreachable.
|
||||
# When changing CORE_ROUTING_MARK, also update yum_change.sh routing_mark_dec.
|
||||
PROXY_FWMARK="0x162"
|
||||
PROXY_ROUTE_TABLE="0x162"
|
||||
CORE_ROUTING_MARK="0x1a0a" # = 6666
|
||||
ACL_BYPASS_FWMARK="0x163"
|
||||
SINGBOX_BYPASS_FWMARK="0x2024"
|
||||
ACL_BYPASS_PREF="8998"
|
||||
DNSMASQ_BYPASS_PREF="8999"
|
||||
|
||||
uci_get() {
|
||||
uci -q get "$1" 2>/dev/null || true
|
||||
}
|
||||
|
||||
bool_enabled() {
|
||||
case "$1" in
|
||||
1|true|TRUE|yes|on) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
acl_bool() {
|
||||
[ -n "$1" ] || return 0
|
||||
bool_enabled "$1"
|
||||
}
|
||||
|
||||
tun_available() {
|
||||
ip tuntap add mode tun name cotuntest >/dev/null 2>&1 || return 1
|
||||
ip link del cotuntest >/dev/null 2>&1 || true
|
||||
return 0
|
||||
}
|
||||
|
||||
config_redir_port() {
|
||||
uci_get clashoo.config.redir_port
|
||||
}
|
||||
|
||||
config_tproxy_port() {
|
||||
local port
|
||||
port="$(uci_get clashoo.config.tproxy_port)"
|
||||
if [ -n "$port" ]; then
|
||||
printf '%s\n' "$port"
|
||||
else
|
||||
config_redir_port
|
||||
fi
|
||||
}
|
||||
|
||||
config_tcp_mode() {
|
||||
uci_get clashoo.config.tcp_mode
|
||||
}
|
||||
|
||||
config_udp_mode() {
|
||||
uci_get clashoo.config.udp_mode
|
||||
}
|
||||
|
||||
config_access_control() {
|
||||
[ "$(uci_get clashoo.config.acl_migrated)" = "1" ] && {
|
||||
printf '0\n'
|
||||
return
|
||||
}
|
||||
uci_get clashoo.config.access_control
|
||||
}
|
||||
|
||||
acl_sections() {
|
||||
uci -q show clashoo 2>/dev/null | sed -n 's/^clashoo\.\([^.=]*\)=lan_acl$/\1/p'
|
||||
}
|
||||
|
||||
acl_list() {
|
||||
uci_get "$1"
|
||||
}
|
||||
|
||||
acl_sections_catchall_last() {
|
||||
local section
|
||||
for section in $(acl_sections); do
|
||||
[ -z "$(acl_list "clashoo.${section}.ip")$(acl_list "clashoo.${section}.ip6")$(acl_list "clashoo.${section}.mac")" ] || printf '%s\n' "$section"
|
||||
done
|
||||
for section in $(acl_sections); do
|
||||
[ -n "$(acl_list "clashoo.${section}.ip")$(acl_list "clashoo.${section}.ip6")$(acl_list "clashoo.${section}.mac")" ] || printf '%s\n' "$section"
|
||||
done
|
||||
}
|
||||
|
||||
grouped_acl_enabled() {
|
||||
local section
|
||||
[ "$(uci_get clashoo.config.acl_migrated)" = "1" ] || return 1
|
||||
for section in $(acl_sections); do
|
||||
[ "$(uci_get "clashoo.${section}.enabled")" != "0" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
acl_has_catchall() {
|
||||
local section
|
||||
for section in $(acl_sections); do
|
||||
[ "$(uci_get "clashoo.${section}.enabled")" != "0" ] || continue
|
||||
[ -z "$(acl_list "clashoo.${section}.ip")$(acl_list "clashoo.${section}.ip6")$(acl_list "clashoo.${section}.mac")" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
lan_acl_enabled() {
|
||||
grouped_acl_enabled && return 0
|
||||
case "$(config_access_control)" in
|
||||
1|2) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
config_enable_dns() {
|
||||
uci_get clashoo.config.enable_dns
|
||||
}
|
||||
|
||||
config_dns_port() {
|
||||
local port=""
|
||||
if [ "$(uci_get clashoo.config.core_type)" != "singbox" ]; then
|
||||
port="$(sed -n 's/^[[:space:]]*listen:.*:[[:space:]]*['"'"'"]*\([0-9]\{1,\}\)['"'"'"]*[[:space:]]*$/\1/p' /etc/clashoo/config.yaml 2>/dev/null | head -n1)"
|
||||
fi
|
||||
[ -n "$port" ] || port="$(uci_get clashoo.config.listen_port)"
|
||||
printf '%s' "$port" | grep -Eq '^[0-9]+$' || port=1053
|
||||
printf '%s\n' "$port"
|
||||
}
|
||||
|
||||
lan_dns_split_enabled() {
|
||||
lan_acl_enabled || return 1
|
||||
bool_enabled "$(config_enable_dns)" || return 1
|
||||
bool_enabled "$(config_ipv4_dns_hijack)" || bool_enabled "$(config_ipv6_dns_hijack)"
|
||||
}
|
||||
|
||||
tun_acl_enabled() {
|
||||
lan_acl_enabled || return 1
|
||||
[ "$(config_tcp_mode)" = "tun" ] || [ "$(config_udp_mode)" = "tun" ]
|
||||
}
|
||||
|
||||
config_ipv4_dns_hijack() {
|
||||
uci_get clashoo.config.ipv4_dns_hijack
|
||||
}
|
||||
|
||||
config_ipv6_dns_hijack() {
|
||||
uci_get clashoo.config.ipv6_dns_hijack
|
||||
}
|
||||
|
||||
config_ipv4_proxy() {
|
||||
uci_get clashoo.config.ipv4_proxy
|
||||
}
|
||||
|
||||
config_ipv6_proxy() {
|
||||
uci_get clashoo.config.ipv6_proxy
|
||||
}
|
||||
|
||||
singbox_tun_active() {
|
||||
[ "$(uci_get clashoo.config.core_type)" = "singbox" ] || return 1
|
||||
[ "$(config_tcp_mode)" = "tun" ] || [ "$(config_udp_mode)" = "tun" ]
|
||||
}
|
||||
|
||||
config_bypass_china() {
|
||||
uci_get clashoo.config.bypass_china
|
||||
}
|
||||
|
||||
config_bypass_china_ipv6() {
|
||||
local value
|
||||
value="$(uci_get clashoo.config.bypass_china_ipv6)"
|
||||
if [ -n "$value" ]; then
|
||||
printf '%s\n' "$value"
|
||||
else
|
||||
config_bypass_china
|
||||
fi
|
||||
}
|
||||
|
||||
config_block_quic() {
|
||||
uci_get clashoo.config.block_quic
|
||||
}
|
||||
|
||||
config_bypass_port_mode() {
|
||||
uci_get clashoo.config.bypass_port_mode
|
||||
}
|
||||
|
||||
config_bypass_port_custom() {
|
||||
uci_get clashoo.config.bypass_port_custom
|
||||
}
|
||||
|
||||
config_legacy_bypass_port() {
|
||||
uci_list clashoo.config.bypass_port
|
||||
}
|
||||
|
||||
config_proxy_tcp_dport() {
|
||||
local mode custom legacy value
|
||||
mode="$(config_bypass_port_mode)"
|
||||
custom="$(config_bypass_port_custom)"
|
||||
legacy="$(uci_get clashoo.config.proxy_tcp_dport)"
|
||||
[ -z "$legacy" ] && legacy="$(config_legacy_bypass_port)"
|
||||
|
||||
case "$mode" in
|
||||
all)
|
||||
# 空值在 render_port_match 中表示该协议的全部端口
|
||||
printf '%s\n' ''
|
||||
;;
|
||||
common)
|
||||
printf '%s\n' '22,53,80,443,8080,8443'
|
||||
;;
|
||||
custom)
|
||||
value="$custom"
|
||||
[ -z "$value" ] && value="$legacy"
|
||||
printf '%s\n' "$value"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$legacy"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
config_proxy_udp_dport() {
|
||||
local mode custom legacy value
|
||||
mode="$(config_bypass_port_mode)"
|
||||
custom="$(config_bypass_port_custom)"
|
||||
legacy="$(uci_get clashoo.config.proxy_udp_dport)"
|
||||
[ -z "$legacy" ] && legacy="$(config_legacy_bypass_port)"
|
||||
|
||||
case "$mode" in
|
||||
all)
|
||||
printf '%s\n' ''
|
||||
;;
|
||||
common)
|
||||
printf '%s\n' '22,53,80,443,8080,8443'
|
||||
;;
|
||||
custom)
|
||||
value="$custom"
|
||||
[ -z "$value" ] && value="$legacy"
|
||||
printf '%s\n' "$value"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$legacy"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
config_bypass_dscp() {
|
||||
uci_list clashoo.config.bypass_dscp
|
||||
}
|
||||
|
||||
config_bypass_fwmark() {
|
||||
uci_list clashoo.config.bypass_fwmark
|
||||
}
|
||||
|
||||
config_fake_ip_range6() {
|
||||
local value
|
||||
value="$(uci_get clashoo.config.fake_ip_range6)"
|
||||
[ -n "$value" ] || value="fc00::/18"
|
||||
[ "$(uci_get clashoo.config.enhanced_mode)" = "redir-host" ] && return 0
|
||||
bool_enabled "$(uci_get clashoo.config.enable_ipv6)" || return 0
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
config_fake_ip_range() {
|
||||
local value
|
||||
value="$(uci_get clashoo.config.fake_ip_range)"
|
||||
[ -n "$value" ] && {
|
||||
printf '%s\n' "$value"
|
||||
return
|
||||
}
|
||||
printf '198.18.0.1/16\n'
|
||||
}
|
||||
|
||||
uci_list() {
|
||||
local key="$1"
|
||||
uci -q show "$key" 2>/dev/null | sed -n "s/^${key}=//p" | sed "s/'//g"
|
||||
}
|
||||
|
||||
ensure_firewall_include() {
|
||||
local name="$1"
|
||||
local path="$2"
|
||||
local chain="${3:-}"
|
||||
local position="${4:-chain-pre}"
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
set firewall.${name}=include
|
||||
set firewall.${name}.type='nftables'
|
||||
set firewall.${name}.path='${path}'
|
||||
set firewall.${name}.position='${position}'
|
||||
$( [ -n "$chain" ] && printf "set firewall.%s.chain='%s'\n" "$name" "$chain" )
|
||||
commit firewall
|
||||
EOF
|
||||
}
|
||||
|
||||
remove_firewall_include() {
|
||||
local name="$1"
|
||||
uci -q delete firewall."${name}" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
render_common_returns() {
|
||||
local fake6
|
||||
fake6="$(config_fake_ip_range6)"
|
||||
printf '%s\n' 'meta nfproto ipv4 ip daddr { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/4, 240.0.0.0/4 } return'
|
||||
if [ -n "$fake6" ]; then
|
||||
printf 'meta nfproto ipv6 ip6 daddr { ::1/128, fc00::/7, fe80::/10, ff00::/8 } ip6 daddr != %s return\n' "$fake6"
|
||||
else
|
||||
printf '%s\n' 'meta nfproto ipv6 ip6 daddr { ::1/128, fc00::/7, fe80::/10, ff00::/8 } return'
|
||||
fi
|
||||
}
|
||||
|
||||
render_ip_elements() {
|
||||
local list="$1"
|
||||
local first=1 entry
|
||||
for entry in $list; do
|
||||
if [ "$first" -eq 0 ]; then
|
||||
printf ', '
|
||||
fi
|
||||
printf '%s' "$entry"
|
||||
first=0
|
||||
done
|
||||
}
|
||||
|
||||
render_token_elements() {
|
||||
printf '%s\n' "$1" | tr ',\t' ' ' | awk '
|
||||
BEGIN { first = 1 }
|
||||
{
|
||||
for (i = 1; i <= NF; i++) {
|
||||
if ($i == "")
|
||||
continue
|
||||
if (!first)
|
||||
printf ", "
|
||||
printf "%s", $i
|
||||
first = 0
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
detect_coexist_fwmarks() {
|
||||
# Merge co-installed proxy plugin fwmarks into bypass to avoid intercepting
|
||||
# their traffic. Only when init.d exists (passwall:0x1 passwall2:0xff
|
||||
# nikki:tproxy 0x80/tun 0x81 mask 0xff).
|
||||
local marks=""
|
||||
[ -x /etc/init.d/passwall ] && marks="$marks 0x1"
|
||||
[ -x /etc/init.d/passwall2 ] && marks="$marks 0xff"
|
||||
if [ -x /etc/init.d/nikki ]; then
|
||||
local nm
|
||||
nm="$(uci -q get nikki.routing.tproxy_fw_mark) $(uci -q get nikki.routing.tun_fw_mark)"
|
||||
[ -z "$(echo "$nm" | tr -d ' ')" ] && nm="0x80 0x81"
|
||||
marks="$marks $nm"
|
||||
fi
|
||||
printf '%s\n' "$marks"
|
||||
}
|
||||
|
||||
merge_fwmark_tokens() {
|
||||
# Always bypass PROXY_FWMARK (inbound TPROXY) and CORE_ROUTING_MARK (core egress)
|
||||
# so nft never traps them. Also merge co-installed plugin fwmarks for coexistence.
|
||||
local coexist
|
||||
coexist="$(detect_coexist_fwmarks)"
|
||||
printf '%s %s %s %s\n' "$1" "$PROXY_FWMARK" "$CORE_ROUTING_MARK" "$coexist" | tr ',\t' ' ' | awk '
|
||||
BEGIN { first = 1 }
|
||||
{
|
||||
for (i = 1; i <= NF; i++) {
|
||||
if ($i == "" || seen[$i]++)
|
||||
continue
|
||||
if (!first)
|
||||
printf ", "
|
||||
printf "%s", $i
|
||||
first = 0
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
render_port_match() {
|
||||
local proto="$1"
|
||||
local ports="$2"
|
||||
local port_elements
|
||||
|
||||
port_elements="$(render_token_elements "$ports")"
|
||||
if [ -n "$port_elements" ]; then
|
||||
printf 'meta l4proto %s %s dport { %s }' "$proto" "$proto" "$port_elements"
|
||||
else
|
||||
printf 'meta l4proto %s' "$proto"
|
||||
fi
|
||||
}
|
||||
|
||||
render_acl_dns_rules() {
|
||||
local redirect_port="$1" section ipv4 ipv6 mac ipv4_elements ipv6_elements mac_elements action
|
||||
for section in $(acl_sections_catchall_last); do
|
||||
[ "$(uci_get "clashoo.${section}.enabled")" != "0" ] || continue
|
||||
if acl_bool "$(uci_get "clashoo.${section}.dns")"; then
|
||||
action="counter redirect to :${redirect_port}"
|
||||
elif singbox_tun_active; then
|
||||
action="meta mark set ${SINGBOX_BYPASS_FWMARK} ct mark set meta mark counter return"
|
||||
else
|
||||
action="counter return"
|
||||
fi
|
||||
ipv4="$(acl_list "clashoo.${section}.ip")"
|
||||
ipv6="$(acl_list "clashoo.${section}.ip6")"
|
||||
mac="$(acl_list "clashoo.${section}.mac")"
|
||||
ipv4_elements="$(render_ip_elements "$ipv4")"
|
||||
ipv6_elements="$(render_ip_elements "$ipv6")"
|
||||
mac_elements="$(render_ip_elements "$mac")"
|
||||
|
||||
if [ -z "$ipv4_elements$ipv6_elements$mac_elements" ]; then
|
||||
bool_enabled "$(config_ipv4_dns_hijack)" && printf 'meta nfproto ipv4 meta l4proto { tcp, udp } th dport 53 %s\n' "$action"
|
||||
bool_enabled "$(config_ipv6_dns_hijack)" && printf 'meta nfproto ipv6 meta l4proto { tcp, udp } th dport 53 %s\n' "$action"
|
||||
continue
|
||||
fi
|
||||
if [ -n "$ipv4_elements" ] && bool_enabled "$(config_ipv4_dns_hijack)"; then
|
||||
printf 'meta nfproto ipv4 ip saddr { %s } meta l4proto { tcp, udp } th dport 53 %s\n' "$ipv4_elements" "$action"
|
||||
fi
|
||||
if [ -n "$ipv6_elements" ] && bool_enabled "$(config_ipv6_dns_hijack)"; then
|
||||
printf 'meta nfproto ipv6 ip6 saddr { %s } meta l4proto { tcp, udp } th dport 53 %s\n' "$ipv6_elements" "$action"
|
||||
fi
|
||||
if [ -n "$mac_elements" ]; then
|
||||
bool_enabled "$(config_ipv4_dns_hijack)" && printf 'meta nfproto ipv4 ether saddr { %s } meta l4proto { tcp, udp } th dport 53 %s\n' "$mac_elements" "$action"
|
||||
bool_enabled "$(config_ipv6_dns_hijack)" && printf 'meta nfproto ipv6 ether saddr { %s } meta l4proto { tcp, udp } th dport 53 %s\n' "$mac_elements" "$action"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
render_acl_proxy_rules() {
|
||||
local mode="$1" proto="$2" port_match="$3" target_port="$4"
|
||||
local section ipv4 ipv6 mac ipv4_elements ipv6_elements mac_elements enabled action4 action6
|
||||
|
||||
for section in $(acl_sections_catchall_last); do
|
||||
[ "$(uci_get "clashoo.${section}.enabled")" != "0" ] || continue
|
||||
enabled="$(uci_get "clashoo.${section}.proxy")"
|
||||
case "$mode" in
|
||||
redirect)
|
||||
if acl_bool "$enabled"; then
|
||||
if singbox_tun_active; then
|
||||
action4="${port_match} ct mark set ${SINGBOX_BYPASS_FWMARK} counter redirect to :${target_port}"
|
||||
else
|
||||
action4="${port_match} counter redirect to :${target_port}"
|
||||
fi
|
||||
action6="$action4"
|
||||
elif singbox_tun_active; then
|
||||
action4="meta l4proto ${proto} meta mark set ${SINGBOX_BYPASS_FWMARK} ct mark set meta mark counter return"
|
||||
action6="$action4"
|
||||
else
|
||||
action4="meta l4proto ${proto} counter return"
|
||||
action6="$action4"
|
||||
fi
|
||||
;;
|
||||
tproxy)
|
||||
if acl_bool "$enabled"; then
|
||||
if singbox_tun_active; then
|
||||
action4="${port_match} ct mark set ${SINGBOX_BYPASS_FWMARK} tproxy ip to :${target_port} meta mark set ${PROXY_FWMARK} counter accept"
|
||||
action6="${port_match} ct mark set ${SINGBOX_BYPASS_FWMARK} tproxy ip6 to :${target_port} meta mark set ${PROXY_FWMARK} counter accept"
|
||||
else
|
||||
action4="${port_match} tproxy ip to :${target_port} meta mark set ${PROXY_FWMARK} counter accept"
|
||||
action6="${port_match} tproxy ip6 to :${target_port} meta mark set ${PROXY_FWMARK} counter accept"
|
||||
fi
|
||||
elif singbox_tun_active; then
|
||||
action4="meta l4proto ${proto} meta mark set ${SINGBOX_BYPASS_FWMARK} ct mark set meta mark counter return"
|
||||
action6="$action4"
|
||||
else
|
||||
action4="meta l4proto ${proto} counter return"
|
||||
action6="$action4"
|
||||
fi
|
||||
;;
|
||||
tun)
|
||||
if acl_bool "$enabled"; then
|
||||
action4="meta l4proto ${proto} counter return"
|
||||
elif singbox_tun_active; then
|
||||
action4="meta l4proto ${proto} meta mark set ${SINGBOX_BYPASS_FWMARK} ct mark set meta mark counter accept"
|
||||
else
|
||||
action4="meta l4proto ${proto} meta mark set ${ACL_BYPASS_FWMARK} counter accept"
|
||||
fi
|
||||
action6="$action4"
|
||||
;;
|
||||
esac
|
||||
|
||||
ipv4="$(acl_list "clashoo.${section}.ip")"
|
||||
ipv6="$(acl_list "clashoo.${section}.ip6")"
|
||||
mac="$(acl_list "clashoo.${section}.mac")"
|
||||
ipv4_elements="$(render_ip_elements "$ipv4")"
|
||||
ipv6_elements="$(render_ip_elements "$ipv6")"
|
||||
mac_elements="$(render_ip_elements "$mac")"
|
||||
|
||||
if [ -z "$ipv4_elements$ipv6_elements$mac_elements" ]; then
|
||||
bool_enabled "$(config_ipv4_proxy)" && printf 'meta nfproto ipv4 %s\n' "$action4"
|
||||
bool_enabled "$(config_ipv6_proxy)" && printf 'meta nfproto ipv6 %s\n' "$action6"
|
||||
continue
|
||||
fi
|
||||
if [ -n "$ipv4_elements" ] && bool_enabled "$(config_ipv4_proxy)"; then
|
||||
printf 'meta nfproto ipv4 ip saddr { %s } %s\n' "$ipv4_elements" "$action4"
|
||||
fi
|
||||
if [ -n "$ipv6_elements" ] && bool_enabled "$(config_ipv6_proxy)"; then
|
||||
printf 'meta nfproto ipv6 ip6 saddr { %s } %s\n' "$ipv6_elements" "$action6"
|
||||
fi
|
||||
if [ -n "$mac_elements" ]; then
|
||||
bool_enabled "$(config_ipv4_proxy)" && printf 'meta nfproto ipv4 ether saddr { %s } %s\n' "$mac_elements" "$action4"
|
||||
bool_enabled "$(config_ipv6_proxy)" && printf 'meta nfproto ipv6 ether saddr { %s } %s\n' "$mac_elements" "$action6"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Split proxied clients to the core DNS while ACL-bypassed clients use dnsmasq.
|
||||
# Gated on UCI IPv4/IPv6 toggles.
|
||||
# return 0: no output when both off, avoid set -e false-positive.
|
||||
render_dns_hijack() {
|
||||
local access_control="$1" redirect_port=53
|
||||
if lan_dns_split_enabled; then
|
||||
redirect_port="$(config_dns_port)"
|
||||
fi
|
||||
if grouped_acl_enabled; then
|
||||
render_acl_dns_rules "$redirect_port"
|
||||
acl_has_catchall && return 0
|
||||
else
|
||||
[ "$access_control" = "1" ] && printf '%s\n' 'ip saddr != @clash_proxy_lan return'
|
||||
[ "$access_control" = "2" ] && printf '%s\n' 'ip saddr @clash_reject_lan return'
|
||||
fi
|
||||
bool_enabled "$(config_ipv4_dns_hijack)" && \
|
||||
printf '%s\n' "meta nfproto ipv4 meta l4proto { tcp, udp } th dport 53 counter redirect to :${redirect_port}"
|
||||
bool_enabled "$(config_ipv6_dns_hijack)" && \
|
||||
printf '%s\n' "meta nfproto ipv6 meta l4proto { tcp, udp } th dport 53 counter redirect to :${redirect_port}"
|
||||
return 0
|
||||
}
|
||||
|
||||
apply_local_output_rule() {
|
||||
local redir_port fake_ip_range tcp_mode bypass_fwmark bypass_china
|
||||
local fwmark_elements fwmark_rule china_set china_rule dnsmasq_rule dnsmasq_uid
|
||||
redir_port="$(config_redir_port)"
|
||||
fake_ip_range="$(config_fake_ip_range)"
|
||||
tcp_mode="$(config_tcp_mode)"
|
||||
bypass_fwmark="$(config_bypass_fwmark)"
|
||||
bypass_china="$(config_bypass_china)"
|
||||
|
||||
# Keep local-output redirect usable when tun mode is selected but tun
|
||||
# device is unavailable on the system.
|
||||
if [ "$tcp_mode" = "tun" ] && ! tun_available; then
|
||||
tcp_mode="redirect"
|
||||
fi
|
||||
|
||||
nft delete table ip ${LOCAL_OUTPUT_TABLE} >/dev/null 2>&1 || true
|
||||
|
||||
# Only apply local output redirect when tcp_mode is redirect
|
||||
[ "$tcp_mode" != "redirect" ] && return 0
|
||||
|
||||
# bypass mihomo's own marks (prevent self-hijack -> dead loop)
|
||||
fwmark_rule=""
|
||||
fwmark_elements="$(merge_fwmark_tokens "$bypass_fwmark")"
|
||||
[ -n "$fwmark_elements" ] && fwmark_rule="meta mark { ${fwmark_elements} } return"
|
||||
|
||||
# CN IP bypass (uses clashoo_china set from geoip_cn.nft)
|
||||
china_set=""
|
||||
china_rule=""
|
||||
if bool_enabled "$bypass_china" && [ -s "$GEOIP_CN_NFT" ]; then
|
||||
china_set="$(cat "$GEOIP_CN_NFT")"
|
||||
china_rule="ip daddr @clashoo_china return"
|
||||
fi
|
||||
|
||||
dnsmasq_rule=""
|
||||
if lan_dns_split_enabled; then
|
||||
dnsmasq_uid="$(id -u dnsmasq 2>/dev/null)"
|
||||
printf '%s' "$dnsmasq_uid" | grep -Eq '^[0-9]+$' && dnsmasq_rule="meta skuid ${dnsmasq_uid} return"
|
||||
fi
|
||||
|
||||
nft -f - <<EOF
|
||||
table ip ${LOCAL_OUTPUT_TABLE} {
|
||||
set clashoo_localnetwork {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
auto-merge
|
||||
elements = { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8,
|
||||
169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16,
|
||||
224.0.0.0/4, 240.0.0.0/4 }
|
||||
}
|
||||
${china_set}
|
||||
chain output {
|
||||
type nat hook output priority dstnat; policy accept;
|
||||
${dnsmasq_rule}
|
||||
${fwmark_rule}
|
||||
ip daddr @clashoo_localnetwork return
|
||||
${china_rule}
|
||||
ip daddr ${fake_ip_range} tcp dport != 53 redirect to :${redir_port}
|
||||
meta l4proto tcp redirect to :${redir_port}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
remove_local_output_rule() {
|
||||
nft delete table ip ${LOCAL_OUTPUT_TABLE} >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
remove_acl_bypass_rules() {
|
||||
local dnsmasq_uid
|
||||
ip rule del pref "$ACL_BYPASS_PREF" fwmark "$ACL_BYPASS_FWMARK" lookup main >/dev/null 2>&1 || true
|
||||
ip -6 rule del pref "$ACL_BYPASS_PREF" fwmark "$ACL_BYPASS_FWMARK" lookup main >/dev/null 2>&1 || true
|
||||
dnsmasq_uid="$(id -u dnsmasq 2>/dev/null)"
|
||||
if printf '%s' "$dnsmasq_uid" | grep -Eq '^[0-9]+$'; then
|
||||
ip rule del pref "$DNSMASQ_BYPASS_PREF" uidrange "${dnsmasq_uid}-${dnsmasq_uid}" lookup main >/dev/null 2>&1 || true
|
||||
ip -6 rule del pref "$DNSMASQ_BYPASS_PREF" uidrange "${dnsmasq_uid}-${dnsmasq_uid}" lookup main >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
apply_acl_bypass_rules() {
|
||||
local dnsmasq_uid
|
||||
remove_acl_bypass_rules
|
||||
tun_acl_enabled || return 0
|
||||
ip rule add pref "$ACL_BYPASS_PREF" fwmark "$ACL_BYPASS_FWMARK" lookup main >/dev/null 2>&1 || true
|
||||
ip -6 rule add pref "$ACL_BYPASS_PREF" fwmark "$ACL_BYPASS_FWMARK" lookup main >/dev/null 2>&1 || true
|
||||
dnsmasq_uid="$(id -u dnsmasq 2>/dev/null)"
|
||||
if printf '%s' "$dnsmasq_uid" | grep -Eq '^[0-9]+$'; then
|
||||
ip rule add pref "$DNSMASQ_BYPASS_PREF" uidrange "${dnsmasq_uid}-${dnsmasq_uid}" lookup main >/dev/null 2>&1 || true
|
||||
ip -6 rule add pref "$DNSMASQ_BYPASS_PREF" uidrange "${dnsmasq_uid}-${dnsmasq_uid}" lookup main >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
apply_block_quic_rule() {
|
||||
local fake_ip_range china_set china_rule scope
|
||||
nft delete table inet ${QUIC_BLOCK_TABLE} >/dev/null 2>&1 || true
|
||||
bool_enabled "$(config_block_quic)" || return 0
|
||||
|
||||
fake_ip_range="$(config_fake_ip_range)"
|
||||
china_set=""
|
||||
scope="ip daddr ${fake_ip_range}"
|
||||
if [ "$(uci_get clashoo.config.enhanced_mode)" != "fake-ip" ]; then
|
||||
if bool_enabled "$(config_bypass_china)" && [ -s "$GEOIP_CN_NFT" ]; then
|
||||
china_set="$(cat "$GEOIP_CN_NFT")"
|
||||
china_rule="ip daddr @clashoo_china return"
|
||||
fi
|
||||
scope=""
|
||||
fi
|
||||
|
||||
nft -f - <<EOF
|
||||
table inet ${QUIC_BLOCK_TABLE} {
|
||||
${china_set}
|
||||
chain prerouting {
|
||||
type filter hook prerouting priority mangle - 1; policy accept;
|
||||
${china_rule:-}
|
||||
${scope} udp dport 443 counter reject with icmpx type port-unreachable
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority -10; policy accept;
|
||||
${china_rule:-}
|
||||
${scope} udp dport 443 counter reject with icmpx type port-unreachable
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
remove_block_quic_rule() {
|
||||
nft delete table inet ${QUIC_BLOCK_TABLE} >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
write_empty_set() {
|
||||
local set_name="$1"
|
||||
local set_type="$2"
|
||||
|
||||
printf 'set %s {\n\ttype %s;\n\tflags interval;\n\tauto-merge;\n}\n\n' "$set_name" "$set_type"
|
||||
}
|
||||
|
||||
append_set_from_file_or_empty() {
|
||||
local file_path="$1"
|
||||
local set_name="$2"
|
||||
local set_type="$3"
|
||||
|
||||
if [ -s "$file_path" ]; then
|
||||
cat "$file_path"
|
||||
printf '\n'
|
||||
else
|
||||
write_empty_set "$set_name" "$set_type"
|
||||
fi
|
||||
}
|
||||
|
||||
generate_rules() {
|
||||
local redir_port tproxy_port tcp_mode udp_mode access_control fake_ip_range proxy_lan_ips reject_lan_ips
|
||||
local proxy_tcp_dport proxy_udp_dport bypass_dscp bypass_fwmark bypass_china bypass_china_ipv6
|
||||
local acl_catchall=0
|
||||
redir_port="$(config_redir_port)"
|
||||
tproxy_port="$(config_tproxy_port)"
|
||||
tcp_mode="$(config_tcp_mode)"
|
||||
udp_mode="$(config_udp_mode)"
|
||||
access_control="$(config_access_control)"
|
||||
grouped_acl_enabled && acl_has_catchall && acl_catchall=1
|
||||
bypass_china="$(config_bypass_china)"
|
||||
bypass_china_ipv6="$(config_bypass_china_ipv6)"
|
||||
proxy_tcp_dport="$(config_proxy_tcp_dport)"
|
||||
proxy_udp_dport="$(config_proxy_udp_dport)"
|
||||
bypass_dscp="$(config_bypass_dscp)"
|
||||
bypass_fwmark="$(config_bypass_fwmark)"
|
||||
fake_ip_range="$(config_fake_ip_range)"
|
||||
proxy_lan_ips="$(uci_list clashoo.config.proxy_lan_ips)"
|
||||
reject_lan_ips="$(uci_list clashoo.config.reject_lan_ips)"
|
||||
|
||||
# When tun device is unavailable, fall back to non-tun transparent modes
|
||||
# so routing rules still take effect for sing-box redirect/tproxy inbounds.
|
||||
if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then
|
||||
if ! tun_available; then
|
||||
[ "$tcp_mode" = "tun" ] && tcp_mode="redirect"
|
||||
[ "$udp_mode" = "tun" ] && udp_mode="tproxy"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$NFT_DIR"
|
||||
|
||||
# Build optional elements lines (nftables rejects empty elements = {})
|
||||
local proxy_elements reject_elements dscp_elements fwmark_elements
|
||||
proxy_elements="$(render_ip_elements "$proxy_lan_ips")"
|
||||
reject_elements="$(render_ip_elements "$reject_lan_ips")"
|
||||
dscp_elements="$(render_token_elements "$bypass_dscp")"
|
||||
fwmark_elements="$(merge_fwmark_tokens "$bypass_fwmark")"
|
||||
tcp_match="$(render_port_match tcp "$proxy_tcp_dport")"
|
||||
udp_match="$(render_port_match udp "$proxy_udp_dport")"
|
||||
|
||||
{
|
||||
printf 'set clashoo_localnetwork {\n\ttype ipv4_addr;\n\tflags interval;\n\tauto-merge;\n'
|
||||
printf '\telements = { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/4, 240.0.0.0/4 }\n}\n\n'
|
||||
|
||||
append_set_from_file_or_empty "$GEOIP_CN_NFT" clashoo_china ipv4_addr
|
||||
append_set_from_file_or_empty "$GEOIP6_CN_NFT" clashoo_china6 ipv6_addr
|
||||
|
||||
printf 'set clash_proxy_lan {\n\ttype ipv4_addr;\n\tflags interval;\n\tauto-merge;\n'
|
||||
[ -n "$proxy_elements" ] && printf '\telements = { %s }\n' "$proxy_elements"
|
||||
printf '}\n\n'
|
||||
|
||||
printf 'set clash_reject_lan {\n\ttype ipv4_addr;\n\tflags interval;\n\tauto-merge;\n'
|
||||
[ -n "$reject_elements" ] && printf '\telements = { %s }\n' "$reject_elements"
|
||||
printf '}\n\n'
|
||||
|
||||
if grouped_acl_enabled && [ "$tcp_mode" = "tun" ]; then
|
||||
printf 'chain clashoo_acl_tun_tcp {\n'
|
||||
render_acl_proxy_rules tun tcp "$tcp_match" "$tproxy_port"
|
||||
printf '}\n\n'
|
||||
fi
|
||||
if grouped_acl_enabled && [ "$udp_mode" = "tun" ]; then
|
||||
printf 'chain clashoo_acl_tun_udp {\n'
|
||||
render_acl_proxy_rules tun udp "$udp_match" "$tproxy_port"
|
||||
printf '}\n'
|
||||
fi
|
||||
} > "$SETS_RULES"
|
||||
|
||||
: > "$OUTPUT_RULES"
|
||||
|
||||
|
||||
# DNS hijack rules go atop DSTNAT_RULES: must run for all TCP modes
|
||||
# (redirect/tproxy/tun) and BEFORE proxy redirect rules so port 53 is not stolen.
|
||||
render_dns_hijack "$access_control" > "$DSTNAT_RULES"
|
||||
|
||||
|
||||
# TCP rules: redirect mode appends proxy redirect; tproxy/tun skip this chain
|
||||
case "$tcp_mode" in
|
||||
redirect)
|
||||
tcp_match="$(render_port_match tcp "$proxy_tcp_dport")"
|
||||
cat >> "$DSTNAT_RULES" <<EOF
|
||||
$( render_common_returns )
|
||||
$( bool_enabled "$bypass_china_ipv6" && printf '%s\n' 'ip6 daddr @clashoo_china6 return' )
|
||||
$( bool_enabled "$bypass_china" && printf '%s\n' 'ip daddr @clashoo_china return' )
|
||||
$( [ -n "$dscp_elements" ] && printf '%s\n' "ip dscp { ${dscp_elements} } return" )
|
||||
$( [ -n "$dscp_elements" ] && printf '%s\n' "ip6 dscp { ${dscp_elements} } return" )
|
||||
$( [ -n "$fwmark_elements" ] && printf '%s\n' "meta mark { ${fwmark_elements} } return" )
|
||||
$( ! bool_enabled "$(config_ipv4_proxy)" && printf '%s\n' 'meta nfproto ipv4 return' )
|
||||
$( ! bool_enabled "$(config_ipv6_proxy)" && printf '%s\n' 'meta nfproto ipv6 return' )
|
||||
$( grouped_acl_enabled && render_acl_proxy_rules redirect tcp "$tcp_match" "$redir_port" )
|
||||
$( [ "$access_control" = "1" ] && printf '%s\n' 'ip saddr != @clash_proxy_lan return' )
|
||||
$( [ "$access_control" = "2" ] && printf '%s\n' 'ip saddr @clash_reject_lan return' )
|
||||
$( [ "$acl_catchall" -eq 0 ] && { singbox_tun_active && printf '%s ct mark set %s redirect to :%s\n' "$tcp_match" "$SINGBOX_BYPASS_FWMARK" "$redir_port" || printf '%s redirect to :%s\n' "$tcp_match" "$redir_port"; } )
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
|
||||
# UDP rules: tproxy via mangle. TUN uses mangle only for LAN ACL bypass.
|
||||
# Also handle TCP tproxy mode here (both TCP+UDP in mangle).
|
||||
local need_mangle=0 tun_acl=0
|
||||
[ "$tcp_mode" = "tproxy" ] && need_mangle=1
|
||||
[ "$udp_mode" = "tproxy" ] && need_mangle=1
|
||||
if tun_acl_enabled "$access_control"; then
|
||||
need_mangle=1
|
||||
tun_acl=1
|
||||
fi
|
||||
|
||||
if [ "$need_mangle" -eq 1 ]; then
|
||||
tcp_match="$(render_port_match tcp "$proxy_tcp_dport")"
|
||||
udp_match="$(render_port_match udp "$proxy_udp_dport")"
|
||||
{
|
||||
render_common_returns
|
||||
if bool_enabled "$bypass_china_ipv6"; then
|
||||
printf 'meta nfproto ipv6 ip6 daddr @clashoo_china6 return\n'
|
||||
fi
|
||||
if bool_enabled "$bypass_china"; then
|
||||
printf 'ip daddr @clashoo_china return\n'
|
||||
fi
|
||||
if [ -n "$dscp_elements" ]; then
|
||||
printf 'ip dscp { %s } return\n' "$dscp_elements"
|
||||
printf 'ip6 dscp { %s } return\n' "$dscp_elements"
|
||||
fi
|
||||
if [ -n "$fwmark_elements" ]; then
|
||||
printf 'meta mark { %s } return\n' "$fwmark_elements"
|
||||
fi
|
||||
bool_enabled "$(config_ipv4_proxy)" || printf 'meta nfproto ipv4 return\n'
|
||||
bool_enabled "$(config_ipv6_proxy)" || printf 'meta nfproto ipv6 return\n'
|
||||
if grouped_acl_enabled; then
|
||||
[ "$tcp_mode" = "tproxy" ] && render_acl_proxy_rules tproxy tcp "$tcp_match" "$tproxy_port"
|
||||
[ "$tcp_mode" = "tun" ] && printf 'meta l4proto tcp jump clashoo_acl_tun_tcp\n'
|
||||
[ "$udp_mode" = "tproxy" ] && render_acl_proxy_rules tproxy udp "$udp_match" "$tproxy_port"
|
||||
[ "$udp_mode" = "tun" ] && printf 'meta l4proto udp jump clashoo_acl_tun_udp\n'
|
||||
elif [ "$tun_acl" -eq 1 ]; then
|
||||
[ "$access_control" = "1" ] && printf 'ip saddr != @clash_proxy_lan meta mark set %s return\n' "$ACL_BYPASS_FWMARK"
|
||||
[ "$access_control" = "2" ] && printf 'ip saddr @clash_reject_lan meta mark set %s return\n' "$ACL_BYPASS_FWMARK"
|
||||
else
|
||||
[ "$access_control" = "1" ] && printf 'ip saddr != @clash_proxy_lan return\n'
|
||||
[ "$access_control" = "2" ] && printf 'ip saddr @clash_reject_lan return\n'
|
||||
fi
|
||||
if bool_enabled "$(config_block_quic)"; then
|
||||
printf 'meta l4proto udp udp dport 443 return\n'
|
||||
fi
|
||||
if [ "$tcp_mode" = "tproxy" ] && [ "$acl_catchall" -eq 0 ]; then
|
||||
if singbox_tun_active; then
|
||||
printf '%s ct mark set %s tproxy to :%s meta mark set %s accept\n' "$tcp_match" "$SINGBOX_BYPASS_FWMARK" "$tproxy_port" "$PROXY_FWMARK"
|
||||
else
|
||||
printf '%s tproxy to :%s meta mark set %s accept\n' "$tcp_match" "$tproxy_port" "$PROXY_FWMARK"
|
||||
fi
|
||||
fi
|
||||
if [ "$udp_mode" = "tproxy" ] && [ "$acl_catchall" -eq 0 ]; then
|
||||
if singbox_tun_active; then
|
||||
printf '%s ct mark set %s tproxy to :%s meta mark set %s accept\n' "$udp_match" "$SINGBOX_BYPASS_FWMARK" "$tproxy_port" "$PROXY_FWMARK"
|
||||
else
|
||||
printf '%s tproxy to :%s meta mark set %s accept\n' "$udp_match" "$tproxy_port" "$PROXY_FWMARK"
|
||||
fi
|
||||
fi
|
||||
} > "$MANGLE_RULES"
|
||||
else
|
||||
: > "$MANGLE_RULES"
|
||||
fi
|
||||
}
|
||||
|
||||
apply_rules() {
|
||||
generate_rules
|
||||
ensure_firewall_include clash_fw4_sets "$SETS_RULES" '' table-pre
|
||||
ensure_firewall_include clash_fw4_dstnat "$DSTNAT_RULES" dstnat
|
||||
remove_firewall_include clash_fw4_output
|
||||
if [ -s "$MANGLE_RULES" ]; then
|
||||
_route_table_dec="$((PROXY_ROUTE_TABLE))"
|
||||
ensure_firewall_include clash_fw4_mangle "$MANGLE_RULES" mangle_prerouting
|
||||
ip rule show 2>/dev/null | grep -q "fwmark ${PROXY_FWMARK}.*lookup ${_route_table_dec}" ||
|
||||
ip rule add fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip route show table "$PROXY_ROUTE_TABLE" 2>/dev/null | grep -q 'local 0.0.0.0/0 dev lo' ||
|
||||
ip route add local 0.0.0.0/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
if bool_enabled "$(config_ipv6_proxy)"; then
|
||||
ip -6 rule show 2>/dev/null | grep -q "fwmark ${PROXY_FWMARK}.*lookup ${_route_table_dec}" ||
|
||||
ip -6 rule add fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip -6 route show table "$PROXY_ROUTE_TABLE" 2>/dev/null | grep -q 'local default dev lo' ||
|
||||
ip -6 route add local ::/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
else
|
||||
ip -6 rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip -6 route del local ::/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
else
|
||||
remove_firewall_include clash_fw4_mangle
|
||||
ip rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip route del local 0.0.0.0/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip -6 rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip -6 route del local ::/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
/etc/init.d/firewall restart >/dev/null 2>&1 || /etc/init.d/firewall reload >/dev/null 2>&1 || true
|
||||
apply_acl_bypass_rules
|
||||
apply_local_output_rule
|
||||
apply_block_quic_rule
|
||||
}
|
||||
|
||||
remove_rules() {
|
||||
remove_acl_bypass_rules
|
||||
remove_local_output_rule
|
||||
remove_block_quic_rule
|
||||
remove_firewall_include clash_fw4_sets
|
||||
remove_firewall_include clash_fw4_dstnat
|
||||
remove_firewall_include clash_fw4_output
|
||||
remove_firewall_include clash_fw4_mangle
|
||||
remove_firewall_include clash_fw4_forward
|
||||
uci commit firewall >/dev/null 2>&1 || true
|
||||
rm -f "$SETS_RULES" "$DSTNAT_RULES" "$OUTPUT_RULES" "$MANGLE_RULES"
|
||||
ip rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip route del local 0.0.0.0/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip -6 rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
ip -6 route del local ::/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true
|
||||
/etc/init.d/firewall restart >/dev/null 2>&1 || /etc/init.d/firewall reload >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
apply)
|
||||
apply_rules
|
||||
;;
|
||||
remove)
|
||||
remove_rules
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {apply|remove}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,43 +0,0 @@
|
||||
# clashoo 运行日志格式化
|
||||
# 输出: MM-DD HH:MM:SS msg
|
||||
|
||||
# mihomo 原生行: time="YYYY-MM-DDTHH:MM:SS..." level=... msg="..."
|
||||
/^time="[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/ {
|
||||
# extract YYYY-MM-DD and HH:MM:SS
|
||||
ts_date = substr($0, 12, 5) # MM-DD
|
||||
ts_time = substr($0, 18, 8) # HH:MM:SS
|
||||
utc_h = substr(ts_time, 1, 2) + 0
|
||||
cst_h = (utc_h + 8) % 24
|
||||
ts = sprintf("%s %02d:%s", ts_date, cst_h, substr(ts_time, 4))
|
||||
|
||||
prefix = ""
|
||||
if (match($0, /level=warning /)) prefix = " [warn]"
|
||||
else if (match($0, /level=error /)) prefix = " [err]"
|
||||
else if (match($0, /level=fatal /)) prefix = " [fatal]"
|
||||
|
||||
i = index($0, "msg=\"")
|
||||
if (i > 0) {
|
||||
rest = substr($0, i + 5)
|
||||
sub(/"[[:space:]]*$/, "", rest)
|
||||
print ts prefix " " rest
|
||||
next
|
||||
}
|
||||
print ts " " $0
|
||||
next
|
||||
}
|
||||
|
||||
# log_msg 行: " YYYY-MM-DD HH:MM:SS - msg" → "MM-DD HH:MM:SS msg"
|
||||
/^[[:space:]]+[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][[:space:]]+[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/ {
|
||||
gsub(/^[[:space:]]+/, "") # trim leading space
|
||||
ts_date = substr($0, 6, 5) # MM-DD
|
||||
ts_time = substr($0, 12, 8) # HH:MM:SS
|
||||
# remove "YYYY-MM-DD HH:MM:SS - " prefix
|
||||
sub(/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][[:space:]]+[0-9][0-9]:[0-9][0-9]:[0-9][0-9][[:space:]]*-[[:space:]]*/, "")
|
||||
print ts_date " " ts_time " " $0
|
||||
next
|
||||
}
|
||||
|
||||
# 空行丢弃
|
||||
NF == 0 { next }
|
||||
|
||||
{ print }
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,44 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
ACTION="$1"
|
||||
LOG_FILE="/tmp/clash_update.txt"
|
||||
|
||||
log_line() {
|
||||
printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
run_init_async() {
|
||||
local action="$1"
|
||||
mkdir -p /tmp/lock >/dev/null 2>&1
|
||||
(flock /tmp/lock/clashoo_rpc_async.lock /etc/init.d/clashoo "$action" >/dev/null 2>&1 </dev/null &)
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
start)
|
||||
run_init_async start
|
||||
exit 0
|
||||
;;
|
||||
stop)
|
||||
run_init_async stop
|
||||
exit 0
|
||||
;;
|
||||
restart)
|
||||
# Fire-and-forget restart used by LuCI RPC to avoid blocking UI apply flow.
|
||||
run_init_async restart
|
||||
exit 0
|
||||
;;
|
||||
update_china_ip)
|
||||
log_line "[china-ip] task started"
|
||||
nohup /usr/share/clashoo/update/update_china_ip.sh >>"$LOG_FILE" 2>&1 </dev/null &
|
||||
exit 0
|
||||
;;
|
||||
update_geoip)
|
||||
log_line "GeoIP 更新任务已触发"
|
||||
(exec 1000>&-; nohup /usr/share/clashoo/update/geoip.sh >/dev/null 2>&1 </dev/null &)
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "usage: $0 {start|stop|restart|update_china_ip|update_geoip}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
#!/bin/sh
|
||||
. /lib/functions.sh
|
||||
|
||||
CURL_GROUP_CACHE="/usr/share/clashbackup/clash_gorup.json"
|
||||
CURL_NOW_CACHE="/usr/share/clashbackup/clash_now.json"
|
||||
CURL_CACHE="/usr/share/clashbackup/clash_curl.json"
|
||||
HISTORY_PATH="/usr/share/clashbackup/history"
|
||||
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)
|
||||
|
||||
curl -m 5 --retry 2 -w %{http_code}"\n" -H "Authorization: Bearer ${SECRET}" -H "Content-Type:application/json" -X GET http://"$LAN_IP":"$PORT"/proxies > "$CURL_CACHE" 2>/dev/null
|
||||
if [ "$(sed -n '$p' "$CURL_CACHE" 2>/dev/null)" = "200" ]; then
|
||||
if [ ! -d /usr/share/clashbackup ];then
|
||||
mkdir -p /usr/share/clashbackup 2>/dev/null
|
||||
fi
|
||||
cat "$CURL_CACHE" |jsonfilter -e '@["proxies"][@.type="Selector"]["name"]' > "$CURL_GROUP_CACHE" 2>/dev/null
|
||||
cat "$CURL_CACHE" |jsonfilter -e '@["proxies"][@.type="Selector"]["now"]' > "$CURL_NOW_CACHE" 2>/dev/null
|
||||
awk 'NR==FNR{a[i]=$0;i++}NR>FNR{print a[j]"#*#"$0;j++}' "$CURL_GROUP_CACHE" "$CURL_NOW_CACHE" > "$HISTORY_PATH" 2>/dev/null
|
||||
fi
|
||||
rm -rf /usr/share/clashbackup/clash_*.json 2>/dev/null
|
||||
@@ -1,347 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
REAL_LOG="/usr/share/clashoo/clashoo_real.txt"
|
||||
UPDATE_LOG="/tmp/clash_update.txt"
|
||||
LIST_FILE="/usr/share/clashbackup/confit_list.conf"
|
||||
SUB_DIR="/usr/share/clashoo/config/sub"
|
||||
TMP_PREFIX="/tmp/clash_sub_$$"
|
||||
DEFAULT_SUB_UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
|
||||
|
||||
subtype="$(uci -q get clashoo.config.subcri 2>/dev/null)"
|
||||
config_name_raw="$(uci -q get clashoo.config.config_name 2>/dev/null)"
|
||||
lang="$(uci -q get luci.main.lang 2>/dev/null)"
|
||||
|
||||
log_text() {
|
||||
if [ "$lang" = "en" ]; then
|
||||
echo "$1" >"$REAL_LOG"
|
||||
else
|
||||
echo "$2" >"$REAL_LOG"
|
||||
fi
|
||||
}
|
||||
|
||||
log_update() {
|
||||
printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$UPDATE_LOG"
|
||||
}
|
||||
|
||||
sanitize_name() {
|
||||
local name
|
||||
name="$1"
|
||||
name="$(printf '%s' "$name" | tr 'A-Z' 'a-z')"
|
||||
name="$(printf '%s' "$name" | sed -e 's/\.yaml$//' -e 's/\.yml$//')"
|
||||
name="$(printf '%s' "$name" | tr ' /' '--')"
|
||||
name="$(printf '%s' "$name" | sed -e 's/[^a-z0-9._-]/-/g' -e 's/--\+/-/g' -e 's/^[._-]*//' -e 's/[._-]*$//')"
|
||||
printf '%s' "$name"
|
||||
}
|
||||
|
||||
sanitize_custom_name() {
|
||||
local name
|
||||
name="$1"
|
||||
name="$(printf '%s' "$name" | sed -e 's/\.yaml$//' -e 's/\.yml$//')"
|
||||
name="$(printf '%s' "$name" | tr ' /' '--')"
|
||||
name="$(printf '%s' "$name" | sed -e 's/[\\]//g' -e 's/\.\.+/-/g' -e 's/--\+/-/g' -e 's/^[._-]*//' -e 's/[._-]*$//')"
|
||||
printf '%s' "$name"
|
||||
}
|
||||
|
||||
url_decode() {
|
||||
printf '%s' "$1" | sed 's/%/\\x/g' | xargs -0 printf '%b' 2>/dev/null || printf '%s' "$1"
|
||||
}
|
||||
|
||||
url_to_name() {
|
||||
local url host qname decoded
|
||||
url="$1"
|
||||
|
||||
qname="$(printf '%s' "$url" | sed -n 's/.*[?&]filename=\([^&#]*\).*/\1/p')"
|
||||
[ -n "$qname" ] || qname="$(printf '%s' "$url" | sed -n 's/.*[?&]name=\([^&#]*\).*/\1/p')"
|
||||
if [ -n "$qname" ]; then
|
||||
# URL decode then strip non-filename chars, keep CJK and alphanumeric
|
||||
decoded="$(url_decode "$qname")"
|
||||
decoded="$(printf '%s' "$decoded" | tr -d '\r\n' | sed -e 's/[[:space:]]/-/g' -e 's/[/\\:*?"<>|]//g' -e 's/\.yaml$//' -e 's/\.yml$//')"
|
||||
[ -n "$decoded" ] && printf '%s' "$decoded" && return
|
||||
fi
|
||||
qname="$(sanitize_name "$qname")"
|
||||
if [ -n "$qname" ]; then
|
||||
printf '%s' "$qname"
|
||||
return
|
||||
fi
|
||||
|
||||
host="$(printf '%s' "$url" | sed -e 's#^[a-zA-Z0-9+.-]*://##' -e 's#/.*$##' -e 's/:.*$//' -e 's#\..*$##')"
|
||||
host="$(sanitize_name "$host")"
|
||||
[ -n "$host" ] || host="sub"
|
||||
printf '%s' "$host"
|
||||
}
|
||||
|
||||
next_available_name() {
|
||||
local base try idx
|
||||
base="$1"
|
||||
base="$(printf '%s' "$base" | sed -e 's/\.yaml$//' -e 's/\.yml$//')"
|
||||
base="$(printf '%s' "$base" | tr ' /' '--')"
|
||||
base="$(printf '%s' "$base" | sed -e 's/[\\]//g' -e 's/\.\.+/-/g' -e 's/--\+/-/g' -e 's/^[._-]*//' -e 's/[._-]*$//')"
|
||||
[ -n "$base" ] || base="sub"
|
||||
|
||||
if [ ! -f "$SUB_DIR/${base}.yaml" ]; then
|
||||
printf '%s' "$base"
|
||||
return
|
||||
fi
|
||||
|
||||
idx=2
|
||||
while :; do
|
||||
try="${base}-${idx}"
|
||||
if [ ! -f "$SUB_DIR/${try}.yaml" ]; then
|
||||
printf '%s' "$try"
|
||||
return
|
||||
fi
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
}
|
||||
|
||||
get_subscription_urls() {
|
||||
uci -q show clashoo.config 2>/dev/null | awk -F"'" '
|
||||
/^clashoo.config.subscribe_url=/ {
|
||||
if (NF >= 3) {
|
||||
for (i = 2; i <= NF; i += 2) {
|
||||
if (length($i) > 0) print $i
|
||||
}
|
||||
} else {
|
||||
sub(/^clashoo.config.subscribe_url=/, "", $0)
|
||||
if (length($0) > 0) print $0
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
ensure_system_dns() {
|
||||
local test_host
|
||||
test_host="github.com"
|
||||
if nslookup "$test_host" 127.0.0.1 >/dev/null 2>&1 || nslookup "$test_host" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
uci delete dhcp.@dnsmasq[0].server >/dev/null 2>&1
|
||||
uci set dhcp.@dnsmasq[0].noresolv='0' >/dev/null 2>&1
|
||||
uci del_list dhcp.@dnsmasq[0].server='127.0.0.1#' >/dev/null 2>&1
|
||||
uci del_list dhcp.@dnsmasq[0].server='127.0.0.1#5300' >/dev/null 2>&1
|
||||
uci add_list dhcp.@dnsmasq[0].server='119.29.29.29' >/dev/null 2>&1
|
||||
uci add_list dhcp.@dnsmasq[0].server='223.5.5.5' >/dev/null 2>&1
|
||||
uci commit dhcp >/dev/null 2>&1
|
||||
/etc/init.d/dnsmasq restart >/dev/null 2>&1
|
||||
sleep 2
|
||||
}
|
||||
|
||||
extract_host() {
|
||||
printf '%s' "$1" | sed -e 's#^[a-zA-Z0-9+.-]*://##' -e 's#/.*$##' -e 's#:.*$##' -e 's#.*@##'
|
||||
}
|
||||
|
||||
resolve_via() {
|
||||
local host dns
|
||||
host="$1"
|
||||
dns="$2"
|
||||
nslookup "$host" "$dns" 2>/dev/null | awk '
|
||||
/^Address/ {
|
||||
ip = $NF
|
||||
if (ip ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ &&
|
||||
ip !~ /^127\./ &&
|
||||
ip !~ /^0\./ &&
|
||||
ip !~ /^198\.18\./ &&
|
||||
ip != "8.8.8.8" && ip != "8.8.4.4" &&
|
||||
ip != "1.1.1.1" && ip != "1.0.0.1" &&
|
||||
ip != "223.5.5.5" && ip != "223.6.6.6" &&
|
||||
ip != "119.29.29.29" && ip != "114.114.114.114") {
|
||||
print ip
|
||||
exit
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
curl_subscription() {
|
||||
local url tmp hdr err ua extra_resolve http_code rc
|
||||
url="$1"
|
||||
tmp="$2"
|
||||
hdr="$3"
|
||||
err="$4"
|
||||
ua="$5"
|
||||
extra_resolve="$6"
|
||||
|
||||
rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
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"
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
download_subscription() {
|
||||
local url target tmp hdr err rc http_code info_line ua err_msg
|
||||
local host ip dns extra
|
||||
url="$1"
|
||||
target="$2"
|
||||
tmp="${TMP_PREFIX}.yaml"
|
||||
hdr="${TMP_PREFIX}.hdr"
|
||||
err="${TMP_PREFIX}.err"
|
||||
|
||||
ua="$(uci -q get clashoo.config.sub_ua 2>/dev/null)"
|
||||
[ -n "$ua" ] || ua="$DEFAULT_SUB_UA"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
http_code="$(curl_subscription "$url" "$tmp" "$hdr" "$err" "$ua" "")"
|
||||
rc=$?
|
||||
|
||||
if [ "$rc" -ne 0 ] || [ "$http_code" = "000" ]; then
|
||||
host="$(extract_host "$url")"
|
||||
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")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] && [ "$http_code" = "200" ] && break
|
||||
done
|
||||
fi
|
||||
fi
|
||||
else
|
||||
wget -q --tries=4 --timeout=20 \
|
||||
--user-agent="$ua" "$url" -O "$tmp" 2>"$err"
|
||||
rc=$?
|
||||
http_code=""
|
||||
fi
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
err_msg="$(grep -a 'curl:' "$err" 2>/dev/null | tail -1)"
|
||||
[ -z "$err_msg" ] && err_msg="$(tail -1 "$err" 2>/dev/null)"
|
||||
log_update "下载失败:$(basename "$target") rc=${rc} ${err_msg}"
|
||||
rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -n "$http_code" ] && [ "$http_code" != "200" ]; then
|
||||
log_update "下载失败:$(basename "$target") HTTP ${http_code}"
|
||||
rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! grep -Eq '^(proxies|proxy-providers):' "$tmp" 2>/dev/null; then
|
||||
log_update "校验失败:$(basename "$target") 内容不含 proxies/proxy-providers"
|
||||
rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1
|
||||
return 1
|
||||
fi
|
||||
|
||||
info_line="$(grep -i 'subscription-userinfo:' "$hdr" 2>/dev/null | head -1 | \
|
||||
sed 's/^[Ss]ubscription-[Uu]serinfo:[[:space:]]*//' | tr -d '\r')"
|
||||
[ -n "$info_line" ] && printf '%s\n' "$info_line" > "${target}.info" || \
|
||||
rm -f "${target}.info" >/dev/null 2>&1
|
||||
|
||||
rm -f "$hdr" "$err" >/dev/null 2>&1
|
||||
mv "$tmp" "$target" >/dev/null 2>&1 || {
|
||||
rm -f "$tmp" >/dev/null 2>&1
|
||||
return 1
|
||||
}
|
||||
printf '%s\n' "$ua" >"${target}.ua"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
upsert_meta() {
|
||||
local filename url typ tmpf
|
||||
filename="$1"
|
||||
url="$2"
|
||||
typ="$3"
|
||||
tmpf="${TMP_PREFIX}.list"
|
||||
|
||||
[ -f "$LIST_FILE" ] || touch "$LIST_FILE"
|
||||
awk -F '#' -v n="$filename" '$1 != n { print $0 }' "$LIST_FILE" >"$tmpf"
|
||||
printf '%s#%s#%s\n' "$filename" "$url" "$typ" >>"$tmpf"
|
||||
mv "$tmpf" "$LIST_FILE"
|
||||
}
|
||||
|
||||
cleanup_tmp() {
|
||||
rm -f "${TMP_PREFIX}.yaml" "${TMP_PREFIX}.urls" "${TMP_PREFIX}.list" \
|
||||
"${TMP_PREFIX}.hdr" "${TMP_PREFIX}.err" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
trap cleanup_tmp EXIT INT TERM
|
||||
|
||||
[ "$subtype" = "clash" ] || [ "$subtype" = "meta" ] || subtype="clash"
|
||||
|
||||
mkdir -p "$SUB_DIR" /usr/share/clashbackup >/dev/null 2>&1
|
||||
[ -f "$LIST_FILE" ] || touch "$LIST_FILE"
|
||||
|
||||
URLS_FILE="${TMP_PREFIX}.urls"
|
||||
get_subscription_urls | sed '/^[[:space:]]*$/d' | awk '!seen[$0]++' >"$URLS_FILE"
|
||||
|
||||
url_count="$(wc -l <"$URLS_FILE" 2>/dev/null | tr -d ' ')"
|
||||
if [ -z "$url_count" ] || [ "$url_count" -eq 0 ]; then
|
||||
log_update "未找到订阅链接"
|
||||
log_text "No subscription URL found" "未找到订阅链接"
|
||||
sleep 2
|
||||
log_text "Clash for OpenWRT" "Clash for OpenWRT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensure_system_dns
|
||||
log_update "开始下载订阅(共 ${url_count} 条)"
|
||||
log_text "Downloading subscription..." "开始下载订阅..."
|
||||
|
||||
base_name="$(sanitize_custom_name "$config_name_raw")"
|
||||
timestamp="$(date +%Y%m%d)"
|
||||
|
||||
success=0
|
||||
failed=0
|
||||
idx=0
|
||||
first_file=""
|
||||
|
||||
while IFS= read -r url; do
|
||||
[ -n "$url" ] || continue
|
||||
idx=$((idx + 1))
|
||||
|
||||
if [ -n "$base_name" ]; then
|
||||
if [ "$url_count" -gt 1 ]; then
|
||||
name_candidate="$(sanitize_name "${base_name}-${idx}")"
|
||||
file_base="$(next_available_name "$name_candidate")"
|
||||
else
|
||||
file_base="$base_name"
|
||||
fi
|
||||
else
|
||||
name_candidate="$(url_to_name "$url")-${timestamp}"
|
||||
if [ "$url_count" -gt 1 ]; then
|
||||
name_candidate="${name_candidate}-${idx}"
|
||||
fi
|
||||
file_base="$(next_available_name "$name_candidate")"
|
||||
fi
|
||||
|
||||
target_file="$SUB_DIR/${file_base}.yaml"
|
||||
if download_subscription "$url" "$target_file"; then
|
||||
upsert_meta "${file_base}.yaml" "$url" "$subtype"
|
||||
log_update "订阅下载成功:${file_base}.yaml"
|
||||
[ -n "$first_file" ] || first_file="$target_file"
|
||||
success=$((success + 1))
|
||||
else
|
||||
log_update "订阅下载失败:${file_base}.yaml"
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
done <"$URLS_FILE"
|
||||
|
||||
if [ "$success" -gt 0 ]; then
|
||||
use_config="$(uci -q get clashoo.config.use_config 2>/dev/null)"
|
||||
if [ -z "$use_config" ] || [ ! -f "$use_config" ]; then
|
||||
uci set clashoo.config.use_config="$first_file"
|
||||
uci set clashoo.config.config_type='1'
|
||||
uci commit clashoo
|
||||
fi
|
||||
log_text "Subscription download completed: ${success} success, ${failed} failed" "订阅下载完成:成功 ${success} 个,失败 ${failed} 个"
|
||||
log_update "订阅下载完成:成功 ${success} 个,失败 ${failed} 个"
|
||||
ret=0
|
||||
else
|
||||
log_text "All subscription downloads failed" "订阅下载失败"
|
||||
log_update "订阅下载失败:全部链接失败"
|
||||
ret=1
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
log_text "Clash for OpenWRT" "Clash for OpenWRT"
|
||||
exit "$ret"
|
||||
@@ -1,451 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Shared DNS helpers for ClashOO runtime generators. Keep POSIX sh compatible.
|
||||
|
||||
dns_trim() {
|
||||
printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
|
||||
}
|
||||
|
||||
dns_has_scheme() {
|
||||
printf '%s' "$1" | grep -Eq '^[A-Za-z][A-Za-z0-9+.-]*://'
|
||||
}
|
||||
|
||||
dns_norm_protocol() {
|
||||
case "$(dns_trim "${1:-}")" in
|
||||
''|'none') printf '' ;;
|
||||
udp|'udp://') printf 'udp://' ;;
|
||||
tcp|'tcp://') printf 'tcp://' ;;
|
||||
dot|tls|'tls://') printf 'tls://' ;;
|
||||
doh|https|'https://') printf 'https://' ;;
|
||||
doq|quic|'quic://') printf 'quic://' ;;
|
||||
*) printf '%s' "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
dns_normalize_server() {
|
||||
local address protocol port prefix
|
||||
address=$(dns_trim "${1:-}")
|
||||
protocol=$(dns_trim "${2:-}")
|
||||
port=$(dns_trim "${3:-}")
|
||||
|
||||
[ -n "$address" ] || return 0
|
||||
|
||||
if dns_has_scheme "$address"; then
|
||||
printf '%s' "$address"
|
||||
return 0
|
||||
fi
|
||||
|
||||
prefix=$(dns_norm_protocol "$protocol")
|
||||
if [ -n "$port" ]; then
|
||||
printf '%s%s:%s' "$prefix" "$address" "$port"
|
||||
else
|
||||
printf '%s%s' "$prefix" "$address"
|
||||
fi
|
||||
}
|
||||
|
||||
dns_yaml_sq() {
|
||||
printf "%s" "$1" | sed "s/'/''/g"
|
||||
}
|
||||
|
||||
dns_yaml_list_item() {
|
||||
local value
|
||||
value=$(dns_yaml_sq "$1")
|
||||
printf " - '%s'\n" "$value"
|
||||
}
|
||||
|
||||
# Route DNS upstreams by rules and preserve the original setting.
|
||||
dns_mihomo_apply_respect_rules() {
|
||||
local cfg="$1" enabled="${2:-0}" tmp="${1}.$$"
|
||||
[ -f "$cfg" ] || return 0
|
||||
|
||||
awk -v enabled="$enabled" '
|
||||
function indent_len(s) { match(s, /[^ ]/); return RSTART ? RSTART - 1 : length(s) }
|
||||
function spaces(n, out,i) {
|
||||
out = ""
|
||||
for (i = 0; i < n; i++)
|
||||
out = out " "
|
||||
return out
|
||||
}
|
||||
function trim_value(s) {
|
||||
sub(/^[[:space:]]*/, "", s)
|
||||
sub(/[[:space:]]*$/, "", s)
|
||||
return s
|
||||
}
|
||||
function flush_dns( i, sp) {
|
||||
if (!in_dns)
|
||||
return
|
||||
sp = spaces(child_indent > 0 ? child_indent : dns_indent + 2)
|
||||
print dns_lines[1]
|
||||
if (enabled == "1") {
|
||||
print sp "# >>> clashoo:dns_leak_protect_respect_rules"
|
||||
print sp "# original-respect-rules: " (original_value != "" ? original_value : "absent")
|
||||
print sp "respect-rules: true"
|
||||
print sp "# <<< clashoo:dns_leak_protect_respect_rules"
|
||||
} else if (original_value != "" && original_value != "absent") {
|
||||
print sp "respect-rules: " original_value
|
||||
}
|
||||
for (i = 2; i <= dns_n; i++)
|
||||
print dns_lines[i]
|
||||
in_dns = 0
|
||||
dns_n = 0
|
||||
}
|
||||
|
||||
/^dns:[[:space:]]*$/ {
|
||||
flush_dns()
|
||||
in_dns = 1
|
||||
dns_indent = indent_len($0)
|
||||
child_indent = -1
|
||||
dns_n = 1
|
||||
dns_lines[dns_n] = $0
|
||||
original_value = ""
|
||||
in_marker = 0
|
||||
next
|
||||
}
|
||||
in_dns {
|
||||
cur_indent = indent_len($0)
|
||||
if ($0 ~ /^[^[:space:]#][^:]*:/ && cur_indent <= dns_indent) {
|
||||
flush_dns()
|
||||
print
|
||||
next
|
||||
}
|
||||
if ($0 ~ /^[[:space:]]*#[[:space:]]*>>>[[:space:]]*clashoo:dns_leak_protect_respect_rules[[:space:]]*$/) {
|
||||
in_marker = 1
|
||||
if (child_indent < 0) child_indent = cur_indent
|
||||
next
|
||||
}
|
||||
if (in_marker) {
|
||||
if ($0 ~ /^[[:space:]]*#[[:space:]]*original-respect-rules:[[:space:]]*/) {
|
||||
value = $0
|
||||
sub(/^[[:space:]]*#[[:space:]]*original-respect-rules:[[:space:]]*/, "", value)
|
||||
original_value = trim_value(value)
|
||||
}
|
||||
if ($0 ~ /^[[:space:]]*#[[:space:]]*<<<[[:space:]]*clashoo:dns_leak_protect_respect_rules[[:space:]]*$/)
|
||||
in_marker = 0
|
||||
next
|
||||
}
|
||||
if ($0 ~ /^[[:space:]]*respect-rules:[[:space:]]*/) {
|
||||
if (original_value == "") {
|
||||
value = $0
|
||||
sub(/^[[:space:]]*respect-rules:[[:space:]]*/, "", value)
|
||||
original_value = trim_value(value)
|
||||
}
|
||||
if (child_indent < 0) child_indent = cur_indent
|
||||
next
|
||||
}
|
||||
if ($0 ~ /^[[:space:]]*[^#[:space:]][^:]*:/ && cur_indent > dns_indent && child_indent < 0)
|
||||
child_indent = cur_indent
|
||||
dns_lines[++dns_n] = $0
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END { flush_dns() }
|
||||
' "$cfg" > "$tmp" && mv "$tmp" "$cfg"
|
||||
rm -f "$tmp" 2>/dev/null
|
||||
}
|
||||
|
||||
# Use fallback DNS for queries; keep bootstrap DNS unchanged.
|
||||
dns_mihomo_apply_leak_nameservers() {
|
||||
local cfg="$1" enabled="${2:-0}" state="${1}.dns-leak-nameserver"
|
||||
local selected="${1}.dns-leak-selected" current="${1}.dns-leak-current" tmp="${1}.$$"
|
||||
[ -f "$cfg" ] || return 0
|
||||
command -v yq >/dev/null 2>&1 || return 0
|
||||
|
||||
if [ "$enabled" = "1" ]; then
|
||||
yq e -r '.dns.fallback[]' "$cfg" > "$selected" 2>/dev/null || true
|
||||
if [ ! -s "$selected" ]; then
|
||||
printf '%s\n' 'https://1.1.1.1/dns-query' 'https://8.8.8.8/dns-query' > "$selected"
|
||||
fi
|
||||
yq e -r '.dns.nameserver[]' "$cfg" > "$current" 2>/dev/null || true
|
||||
|
||||
if [ ! -f "$state" ] ||
|
||||
! grep -q 'clashoo:dns_leak_protect_respect_rules' "$cfg" ||
|
||||
! cmp -s "$current" "$selected"; then
|
||||
awk '
|
||||
function indent_len(s) { match(s, /[^ ]/); return RSTART ? RSTART - 1 : length(s) }
|
||||
/^dns:[[:space:]]*$/ { in_dns = 1; dns_indent = indent_len($0); next }
|
||||
in_dns && /^[^[:space:]#][^:]*:/ { exit }
|
||||
in_dns && /^[[:space:]]*nameserver:/ {
|
||||
print
|
||||
rest = $0
|
||||
sub(/^[[:space:]]*nameserver:[[:space:]]*/, "", rest)
|
||||
sub(/[[:space:]]*(#.*)?$/, "", rest)
|
||||
if (rest == "") { in_ns = 1; ns_indent = indent_len($0) }
|
||||
next
|
||||
}
|
||||
in_ns {
|
||||
cur_indent = indent_len($0)
|
||||
if ($0 ~ /^[[:space:]]*$/ || cur_indent > ns_indent) {
|
||||
print
|
||||
next
|
||||
}
|
||||
exit
|
||||
}
|
||||
' "$cfg" > "$state"
|
||||
[ -s "$state" ] || printf '__ABSENT__\n' > "$state"
|
||||
fi
|
||||
|
||||
awk -v selected="$selected" '
|
||||
function indent_len(s) { match(s, /[^ ]/); return RSTART ? RSTART - 1 : length(s) }
|
||||
function spaces(n, out,i) {
|
||||
out = ""
|
||||
for (i = 0; i < n; i++) out = out " "
|
||||
return out
|
||||
}
|
||||
function print_selected(sp, line) {
|
||||
print sp "nameserver:"
|
||||
while ((getline line < selected) > 0)
|
||||
if (line != "") print sp " - " line
|
||||
close(selected)
|
||||
inserted = 1
|
||||
}
|
||||
/^dns:[[:space:]]*$/ {
|
||||
in_dns = 1
|
||||
dns_indent = indent_len($0)
|
||||
child_indent = -1
|
||||
print
|
||||
next
|
||||
}
|
||||
in_dns && /^[^[:space:]#][^:]*:/ {
|
||||
if (!inserted) print_selected(spaces(child_indent > 0 ? child_indent : dns_indent + 2))
|
||||
in_dns = 0
|
||||
print
|
||||
next
|
||||
}
|
||||
in_dns && /^[[:space:]]*[^#[:space:]][^:]*:/ && child_indent < 0 {
|
||||
child_indent = indent_len($0)
|
||||
}
|
||||
in_dns && /^[[:space:]]*nameserver:/ {
|
||||
ns_indent = indent_len($0)
|
||||
rest = $0
|
||||
sub(/^[[:space:]]*nameserver:[[:space:]]*/, "", rest)
|
||||
sub(/[[:space:]]*(#.*)?$/, "", rest)
|
||||
print_selected(spaces(ns_indent))
|
||||
if (rest == "") skip_ns = 1
|
||||
next
|
||||
}
|
||||
skip_ns {
|
||||
cur_indent = indent_len($0)
|
||||
if ($0 ~ /^[[:space:]]*$/ || cur_indent > ns_indent) next
|
||||
skip_ns = 0
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (in_dns && !inserted)
|
||||
print_selected(spaces(child_indent > 0 ? child_indent : dns_indent + 2))
|
||||
}
|
||||
' "$cfg" > "$tmp" && mv "$tmp" "$cfg"
|
||||
elif [ -f "$state" ]; then
|
||||
awk -v state="$state" '
|
||||
function indent_len(s) { match(s, /[^ ]/); return RSTART ? RSTART - 1 : length(s) }
|
||||
function print_original( line) {
|
||||
if ((getline line < state) > 0 && line != "__ABSENT__") {
|
||||
print line
|
||||
while ((getline line < state) > 0) print line
|
||||
}
|
||||
close(state)
|
||||
restored = 1
|
||||
}
|
||||
/^dns:[[:space:]]*$/ { in_dns = 1; print; next }
|
||||
in_dns && /^[^[:space:]#][^:]*:/ {
|
||||
if (!restored) print_original()
|
||||
in_dns = 0
|
||||
print
|
||||
next
|
||||
}
|
||||
in_dns && /^[[:space:]]*nameserver:/ {
|
||||
ns_indent = indent_len($0)
|
||||
rest = $0
|
||||
sub(/^[[:space:]]*nameserver:[[:space:]]*/, "", rest)
|
||||
sub(/[[:space:]]*(#.*)?$/, "", rest)
|
||||
print_original()
|
||||
if (rest == "") skip_ns = 1
|
||||
next
|
||||
}
|
||||
skip_ns {
|
||||
cur_indent = indent_len($0)
|
||||
if ($0 ~ /^[[:space:]]*$/ || cur_indent > ns_indent) next
|
||||
skip_ns = 0
|
||||
}
|
||||
{ print }
|
||||
END { if (in_dns && !restored) print_original() }
|
||||
' "$cfg" > "$tmp" && mv "$tmp" "$cfg"
|
||||
rm -f "$state"
|
||||
fi
|
||||
rm -f "$selected" "$current" "$tmp" 2>/dev/null
|
||||
}
|
||||
|
||||
|
||||
# handle mihomo dns block:
|
||||
# - strip previous clashoo:dns_leak_protect injection markers (incl. the old
|
||||
# geosite:gfw block written by older versions)
|
||||
# - rewrite the ipv6: line from ipv6_value (skip when empty)
|
||||
# No longer injects fallback-filter.geosite:gfw — mihomo deprecated it and a
|
||||
# GeoSite.dat missing the gfw category froze startup (issue #25). See flush_dns.
|
||||
# (The fallback-filter parsing vars below are now vestigial; clean up separately.)
|
||||
dns_mihomo_apply_leak_dns_block() {
|
||||
local cfg="$1" enabled="${2:-0}" ipv6_value="${3:-}" tmp="${1}.$$"
|
||||
[ -f "$cfg" ] || return 0
|
||||
|
||||
awk -v enabled="$enabled" -v ipv6_value="$ipv6_value" '
|
||||
function indent_len(s) { match(s, /[^ ]/); return RSTART ? RSTART - 1 : length(s) }
|
||||
function spaces(n, out,i) {
|
||||
out = ""
|
||||
for (i = 0; i < n; i++)
|
||||
out = out " "
|
||||
return out
|
||||
}
|
||||
function flush_dns( i, sp, ff_sp) {
|
||||
if (!in_dns)
|
||||
return
|
||||
if (in_ff) ff_end = dns_n
|
||||
sp = spaces(child_indent > 0 ? child_indent : dns_indent + 2)
|
||||
ff_sp = (ff_child_indent > 0 ? spaces(ff_child_indent) : sp " ")
|
||||
|
||||
print dns_lines[1]
|
||||
if (ipv6_value != "")
|
||||
print sp "ipv6: " ipv6_value
|
||||
# NOTE: clashoo used to inject `fallback-filter.geosite: [gfw]` here, but
|
||||
# mihomo deprecated fallback-filter.geosite ("replace with nameserver-policy,
|
||||
# will be removed") AND it hard-fails preflight + freezes startup (~90s) when
|
||||
# the downloaded GeoSite.dat lacks the gfw category (issue #25). Stop
|
||||
# injecting it. DNS-leak protection still works via respect-rules / leak
|
||||
# nameservers / DST-PORT,853,REJECT handled elsewhere. The marker strip below
|
||||
# still removes any gfw block injected by older versions, so disabling /
|
||||
# upgrading cleans it up.
|
||||
for (i = 2; i <= dns_n; i++)
|
||||
print dns_lines[i]
|
||||
in_dns = 0
|
||||
dns_n = 0
|
||||
}
|
||||
|
||||
/^[[:space:]]*#[[:space:]]*>>>[[:space:]]*clashoo:dns_leak_protect[[:space:]]*$/ {
|
||||
in_marker = 1
|
||||
next
|
||||
}
|
||||
in_marker {
|
||||
if ($0 ~ /^[[:space:]]*#[[:space:]]*<<<[[:space:]]*clashoo:dns_leak_protect[[:space:]]*$/)
|
||||
in_marker = 0
|
||||
next
|
||||
}
|
||||
|
||||
/^dns:[[:space:]]*$/ {
|
||||
flush_dns()
|
||||
in_dns = 1
|
||||
dns_indent = indent_len($0)
|
||||
child_indent = -1
|
||||
dns_n = 1
|
||||
dns_lines[dns_n] = $0
|
||||
has_ff = 0
|
||||
in_ff = 0
|
||||
in_ff_geosite = 0
|
||||
ff_indent = -1
|
||||
ff_child_indent = -1
|
||||
ff_geosite_indent = -1
|
||||
ff_has_gfw = 0
|
||||
ff_end = -1
|
||||
next
|
||||
}
|
||||
in_dns {
|
||||
cur_indent = indent_len($0)
|
||||
if ($0 ~ /^[^[:space:]#][^:]*:/ && cur_indent <= dns_indent) {
|
||||
flush_dns()
|
||||
print
|
||||
next
|
||||
}
|
||||
if ($0 ~ /^[[:space:]]*[^#[:space:]][^:]*:/ && cur_indent > dns_indent && child_indent < 0)
|
||||
child_indent = cur_indent
|
||||
if (in_ff && cur_indent <= ff_indent) {
|
||||
ff_end = dns_n
|
||||
in_ff = 0
|
||||
in_ff_geosite = 0
|
||||
}
|
||||
if (in_ff_geosite && cur_indent <= ff_geosite_indent)
|
||||
in_ff_geosite = 0
|
||||
if ($0 ~ /^[[:space:]]*fallback-filter:[[:space:]]*$/) {
|
||||
has_ff = 1
|
||||
in_ff = 1
|
||||
ff_indent = cur_indent
|
||||
ff_child_indent = -1
|
||||
} else if (in_ff && $0 ~ /^[[:space:]]*[^#[:space:]][^:]*:/ && ff_child_indent < 0) {
|
||||
ff_child_indent = cur_indent
|
||||
}
|
||||
if (in_ff && $0 ~ /^[[:space:]]*geosite:[[:space:]]*$/) {
|
||||
in_ff_geosite = 1
|
||||
ff_geosite_indent = cur_indent
|
||||
} else if (in_ff_geosite && $0 ~ /^[[:space:]]*-[[:space:]]*gfw([[:space:]]*(#.*)?)?$/) {
|
||||
ff_has_gfw = 1
|
||||
}
|
||||
if ($0 ~ /^[[:space:]]*ipv6:[[:space:]]*/)
|
||||
next
|
||||
dns_lines[++dns_n] = $0
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END { flush_dns() }
|
||||
' "$cfg" > "$tmp" && mv "$tmp" "$cfg"
|
||||
rm -f "$tmp" 2>/dev/null
|
||||
}
|
||||
|
||||
# handle DST-PORT,853,REJECT injection into rules block
|
||||
# only remove Clashoo marker block, never user original rules
|
||||
dns_mihomo_apply_leak_rule() {
|
||||
local cfg="$1" enabled="${2:-0}" tmp="${1}.$$" has_user_853
|
||||
[ -f "$cfg" ] || return 0
|
||||
|
||||
has_user_853=$(awk '
|
||||
/^[[:space:]]*#[[:space:]]*>>>[[:space:]]*clashoo:dns_leak_protect_rule[[:space:]]*$/ {
|
||||
in_marker = 1
|
||||
next
|
||||
}
|
||||
in_marker {
|
||||
if ($0 ~ /^[[:space:]]*#[[:space:]]*<<<[[:space:]]*clashoo:dns_leak_protect_rule[[:space:]]*$/)
|
||||
in_marker = 0
|
||||
next
|
||||
}
|
||||
/^[[:space:]]*-[[:space:]]*DST-PORT,853,REJECT([[:space:]]*(#.*)?)?$/ { found = 1 }
|
||||
END { print found ? 1 : 0 }
|
||||
' "$cfg")
|
||||
|
||||
awk -v enabled="$enabled" -v has_user_853="$has_user_853" '
|
||||
BEGIN { inserted = 0; saw_rules = 0 }
|
||||
/^[[:space:]]*#[[:space:]]*>>>[[:space:]]*clashoo:dns_leak_protect_rule[[:space:]]*$/ {
|
||||
in_marker = 1
|
||||
next
|
||||
}
|
||||
in_marker {
|
||||
if ($0 ~ /^[[:space:]]*#[[:space:]]*<<<[[:space:]]*clashoo:dns_leak_protect_rule[[:space:]]*$/)
|
||||
in_marker = 0
|
||||
next
|
||||
}
|
||||
/^rules:[[:space:]]*$/ {
|
||||
saw_rules = 1
|
||||
print
|
||||
if (enabled == "1" && has_user_853 != "1") {
|
||||
print " # >>> clashoo:dns_leak_protect_rule"
|
||||
print " - DST-PORT,853,REJECT"
|
||||
print " # <<< clashoo:dns_leak_protect_rule"
|
||||
inserted = 1
|
||||
}
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (enabled == "1" && has_user_853 != "1" && !inserted && !saw_rules) {
|
||||
print ""
|
||||
print "rules:"
|
||||
print " # >>> clashoo:dns_leak_protect_rule"
|
||||
print " - DST-PORT,853,REJECT"
|
||||
print " # <<< clashoo:dns_leak_protect_rule"
|
||||
}
|
||||
}
|
||||
' "$cfg" > "$tmp" && mv "$tmp" "$cfg"
|
||||
rm -f "$tmp" 2>/dev/null
|
||||
}
|
||||
|
||||
dns_mihomo_apply_leak_protect() {
|
||||
local cfg="$1" enabled="${2:-0}" ipv6_value="${3:-}"
|
||||
[ -f "$cfg" ] || return 0
|
||||
dns_mihomo_apply_leak_nameservers "$cfg" "$enabled"
|
||||
dns_mihomo_apply_respect_rules "$cfg" "$enabled"
|
||||
dns_mihomo_apply_leak_dns_block "$cfg" "$enabled" "$ipv6_value"
|
||||
dns_mihomo_apply_leak_rule "$cfg" "$enabled"
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
. as $document |
|
||||
strenv(CLASHOO_DNS_OLD_PORT) as $old_port |
|
||||
strenv(CLASHOO_DNS_HOST_PATTERN) as $host_pattern |
|
||||
(strenv(CLASHOO_DNS_REPLACEMENT) | split(" ") | map(select(length > 0))) as $replacement |
|
||||
("(?i)^(?:[a-z][a-z0-9+.-]*://)?(?:" + $host_pattern + "):" + $old_port + "(?:$|[/#?])") as $old_loopback |
|
||||
(
|
||||
$document |
|
||||
select((.dns."proxy-server-nameserver" | type) == "!!seq") |
|
||||
([.dns."proxy-server-nameserver"[] | select(type == "!!str" and test($old_loopback))] | length) as $match_count |
|
||||
select($match_count > 0) |
|
||||
.dns."proxy-server-nameserver" =
|
||||
([
|
||||
.dns."proxy-server-nameserver"[] |
|
||||
select((type == "!!str" and test($old_loopback)) | not) |
|
||||
select(. as $server | ($replacement | contains([$server])) | not)
|
||||
] + $replacement)
|
||||
) // $document
|
||||
@@ -1,98 +0,0 @@
|
||||
#!/bin/sh
|
||||
. /lib/functions.sh
|
||||
|
||||
|
||||
TMP_PREFIX="/tmp/clashoo_iprules.$$"
|
||||
CUSTOM_RULE_FILE="${TMP_PREFIX}.ipadd.conf"
|
||||
RULE="${TMP_PREFIX}.rules_conf.yaml"
|
||||
CLASH="${TMP_PREFIX}.conf.yaml"
|
||||
CONFIG_YAML="/etc/clashoo/config.yaml"
|
||||
CLASH_CONFIG="${TMP_PREFIX}.config.yaml"
|
||||
trap 'rm -f "$CUSTOM_RULE_FILE" "$RULE" "$CLASH" "$CLASH_CONFIG"' EXIT
|
||||
|
||||
[ -f "$CONFIG_YAML" ] || exit 0
|
||||
|
||||
. /usr/share/clashoo/runtime/primary_group.sh
|
||||
|
||||
|
||||
# __PROXY__ placeholder -> primary proxy group (cached by RPC);
|
||||
# fallback to GLOBAL (built-in mihomo group)
|
||||
CACHED_PRIMARY_GROUP="$(uci -q get clashoo.config.primary_proxy_group)"
|
||||
PRIMARY_GROUP="$(clashoo_resolve_primary_group "$CONFIG_YAML" "$CACHED_PRIMARY_GROUP")"
|
||||
[ -n "$PRIMARY_GROUP" ] || PRIMARY_GROUP="GLOBAL"
|
||||
|
||||
append=$(uci get clashoo.config.append_rules 2>/dev/null)
|
||||
if [ "${append:-0}" -eq 1 ];then
|
||||
|
||||
if [ -f $CLASH_CONFIG ];then
|
||||
rm -rf $CLASH_CONFIG 2>/dev/null
|
||||
fi
|
||||
|
||||
cp $CONFIG_YAML $CLASH_CONFIG 2>/dev/null
|
||||
if [ ! -z "$(grep "^Rule:" "$CLASH_CONFIG")" ]; then
|
||||
sed -i "/^Rule:/i\#RULESTART#" $CLASH_CONFIG 2>/dev/null
|
||||
elif [ ! -z "$(grep "^rules:" "$CLASH_CONFIG")" ]; then
|
||||
sed -i "/^rules:/i\#RULESTART#" $CLASH_CONFIG 2>/dev/null
|
||||
fi
|
||||
sed -i -e "\$a#RULEEND#" $CLASH_CONFIG 2>/dev/null
|
||||
|
||||
awk '/#RULESTART#/,/#RULEEND#/{print}' "$CLASH_CONFIG" 2>/dev/null |sed "s/\'//g" 2>/dev/null |sed 's/\"//g' 2>/dev/null |sed 's/\t/ /g' 2>/dev/null |grep '^ \{0,\}- '|awk -F '- ' '{print "- "$2}' | sed 's/^ \{0,\}//' 2>/dev/null |sed 's/ \{0,\}$//' 2>/dev/null >$RULE 2>&1
|
||||
|
||||
sed -i '/#RULESTART#/,/#RULEEND#/d' "$CLASH_CONFIG" 2>/dev/null
|
||||
|
||||
sed -i -e "\$a " $CLASH_CONFIG 2>/dev/null
|
||||
sed -i "1i\rules:" $RULE 2>/dev/null
|
||||
cat $CLASH_CONFIG $RULE >$CLASH 2>/dev/null
|
||||
mv $CLASH $CONFIG_YAML 2>/dev/null
|
||||
rm -f $RULE $CLASH_CONFIG 2>/dev/null
|
||||
fi
|
||||
|
||||
# ===== custom routing rules (UCI: config addtype), decoupled =====
|
||||
# fields: type(DOMAIN|SUFFIX|KEYWORD|IP-CIDR), ipaaddr, pgroup, res(no-resolve)
|
||||
# pgroup: DIRECT or __PROXY__ (resolved at injection)
|
||||
rm -f "$CUSTOM_RULE_FILE" 2>/dev/null
|
||||
|
||||
ipadd()
|
||||
{
|
||||
local section="$1"
|
||||
config_get "pgroup" "$section" "pgroup" ""
|
||||
config_get "ipaaddr" "$section" "ipaaddr" ""
|
||||
config_get "type" "$section" "type" ""
|
||||
config_get "res" "$section" "res" ""
|
||||
|
||||
[ -z "$type" ] && return
|
||||
[ -z "$ipaaddr" ] && return
|
||||
[ -z "$pgroup" ] && return
|
||||
|
||||
[ "$pgroup" = "__PROXY__" ] && pgroup="$PRIMARY_GROUP"
|
||||
|
||||
# quote whole rule (group name may contain spaces/emoji)
|
||||
# so mixed indent with subscription rules does not break YAML parsing
|
||||
if [ "${res}" = "1" ];then
|
||||
echo "${RULE_INDENT}- \"$type,$ipaaddr,$pgroup,no-resolve\"" >> "$CUSTOM_RULE_FILE"
|
||||
else
|
||||
echo "${RULE_INDENT}- \"$type,$ipaaddr,$pgroup\"" >> "$CUSTOM_RULE_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# detect existing rules indent for alignment (default 2)
|
||||
RULE_INDENT="$(awk '/^[[:space:]]*rules:/{f=1;next} f&&/^[[:space:]]*-/{match($0,/^[[:space:]]*/);print substr($0,1,RLENGTH);exit}' "$CONFIG_YAML" 2>/dev/null)"
|
||||
[ -n "$RULE_INDENT" ] || RULE_INDENT=" "
|
||||
|
||||
config_load "clashoo"
|
||||
config_foreach ipadd "addtype"
|
||||
|
||||
# clean previous injection block (idempotent)
|
||||
sed -i '/#CUSTOMRULESTART#/,/#CUSTOMRULEEND#/d' "$CONFIG_YAML" 2>/dev/null
|
||||
|
||||
if [ -f "$CUSTOM_RULE_FILE" ];then
|
||||
sed -i -e "\$a#CUSTOMRULEEND#" "$CUSTOM_RULE_FILE" 2>/dev/null
|
||||
if [ ! -z "$(grep "^ \{0,\}rules:" "$CONFIG_YAML")" ]; then
|
||||
# 插在 rules: 段最前面,优先于订阅自带规则命中(纠错)
|
||||
sed -i '/^ \{0,\}rules:/a\#CUSTOMRULESTART#' "$CONFIG_YAML" 2>/dev/null
|
||||
else
|
||||
echo "rules:" >> "$CONFIG_YAML" 2>/dev/null
|
||||
echo "#CUSTOMRULESTART#" >> "$CONFIG_YAML" 2>/dev/null
|
||||
fi
|
||||
sed -i "/#CUSTOMRULESTART#/r${CUSTOM_RULE_FILE}" "$CONFIG_YAML" 2>/dev/null
|
||||
fi
|
||||
@@ -1,317 +0,0 @@
|
||||
#!/usr/bin/ucode
|
||||
'use strict';
|
||||
|
||||
import { cursor } from 'uci';
|
||||
const uci = cursor();
|
||||
|
||||
function b(v) {
|
||||
if (v == null || v == '' || v == '0' || v == 'false') return false;
|
||||
if (v == '1' || v == 'true') return true;
|
||||
return v;
|
||||
}
|
||||
function i(v, d) { let n = v != null ? int(v) : null; return n != null ? n : d; }
|
||||
function s(v, d) { let t = trim(v || ''); return length(t) ? t : d; }
|
||||
function a(k) { return uci.get('clashoo', 'config', k); }
|
||||
function ab(k) { return b(a(k)); }
|
||||
|
||||
const cfg = {};
|
||||
|
||||
/* ── 端口与基本设置 ─────────────────────────────────────── */
|
||||
let v;
|
||||
v = i(a('http_port')); if (v != null) cfg['port'] = v;
|
||||
v = i(a('socks_port')); if (v != null) cfg['socks-port'] = v;
|
||||
v = i(a('redir_port')); if (v != null) cfg['redir-port'] = v;
|
||||
v = i(a('mixed_port')); if (v != null) cfg['mixed-port'] = v;
|
||||
v = i(a('tproxy_port')); if (v != null) cfg['tproxy-port'] = v;
|
||||
|
||||
v = a('bind_addr');
|
||||
if (!v || v == '*') v = null;
|
||||
if (v) cfg['bind-address'] = v;
|
||||
|
||||
v = a('p_mode');
|
||||
if (v == 'script') v = 'rule';
|
||||
if (v) cfg['mode'] = v;
|
||||
|
||||
cfg['log-level'] = s(a('level'), 'info');
|
||||
cfg['allow-lan'] = ab('allow_lan');
|
||||
cfg['ipv6'] = ab('enable_ipv6');
|
||||
cfg['routing-mark'] = 6666;
|
||||
cfg['external-controller'] = '0.0.0.0:' + i(a('dash_port'), 9090);
|
||||
cfg['external-ui'] = './dashboard';
|
||||
cfg['secret'] = s(a('dash_pass'), '');
|
||||
|
||||
v = a('interf');
|
||||
if (v && v != '0') cfg['interface-name'] = v;
|
||||
|
||||
/* ── TUN ───────────────────────────────────────────────── */
|
||||
let tun_mode = i(a('tun_mode'), 0);
|
||||
let tcp_mode = s(a('tcp_mode'), 'redirect');
|
||||
let udp_mode = s(a('udp_mode'), 'tproxy');
|
||||
let tun_enabled = (tun_mode == 1 || tcp_mode == 'tun' || udp_mode == 'tun');
|
||||
let acl_enabled = false;
|
||||
uci.foreach('clashoo', 'lan_acl', function(sec) {
|
||||
if (b(sec.enabled) != false) acl_enabled = true;
|
||||
});
|
||||
if (!ab('acl_migrated')) {
|
||||
let access_control = i(a('access_control'), 0);
|
||||
acl_enabled = access_control == 1 || access_control == 2;
|
||||
}
|
||||
let tun_acl = tun_enabled && acl_enabled;
|
||||
|
||||
cfg['tun'] = {
|
||||
enable: tun_enabled,
|
||||
stack: s(a('stack'), 'gvisor'),
|
||||
'auto-route': true,
|
||||
'auto-redirect': !tun_acl,
|
||||
'auto-detect-interface': true,
|
||||
};
|
||||
let tun_mtu = a('tun_mtu');
|
||||
if (tun_mtu != null) cfg['tun']['mtu'] = int(tun_mtu);
|
||||
if (ab('tun_gso')) {
|
||||
cfg['tun']['gso'] = true;
|
||||
cfg['tun']['gso-max-size'] = i(a('tun_gso_max_size'), 65536);
|
||||
}
|
||||
if (ab('tun_dns_hijack')) {
|
||||
cfg['tun']['dns-hijack'] = ['any:53', 'tcp://any:53'];
|
||||
}
|
||||
if (!tun_enabled) {
|
||||
cfg['tun']['enable'] = false;
|
||||
}
|
||||
|
||||
/* ── DNS ───────────────────────────────────────────────── */
|
||||
let dns_port = i(a('listen_port'), 1053);
|
||||
cfg['dns'] = {
|
||||
enable: b(a('enable_dns')) != false,
|
||||
listen: '0.0.0.0:' + dns_port,
|
||||
'enhanced-mode': s(a('enhanced_mode'), 'fake-ip'),
|
||||
'fake-ip-range': s(a('fake_ip_range'), '198.18.0.1/16'),
|
||||
'fake-ip-filter': [],
|
||||
ipv6: ab('enable_ipv6'),
|
||||
};
|
||||
if (cfg['dns']['enhanced-mode'] == 'fake-ip' && ab('enable_ipv6'))
|
||||
cfg['dns']['fake-ip-range6'] = s(a('fake_ip_range6'), 'fc00::/18');
|
||||
/* fake-ip-filter-mode: blacklist (default) | whitelist | rule */
|
||||
let filter_mode = s(a('fake_ip_filter_mode'), 'blacklist');
|
||||
if (filter_mode != 'blacklist') cfg['dns']['fake-ip-filter-mode'] = filter_mode;
|
||||
|
||||
/* map geosite:cn to bundled cn.mrs rule-set so mihomo skips the 10MB geosite.dat */
|
||||
let need_cn_rs = false;
|
||||
let keep_fakeip = length(s(getenv('CLASHOO_KEEP_FAKEIP_FILTER'), ''));
|
||||
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;
|
||||
push(cfg['dns']['fake-ip-filter'], f);
|
||||
};
|
||||
let filters = a('fake_ip_filter');
|
||||
if (type(filters) == 'array') { for (let f in filters) push_filter(f); }
|
||||
else if (filters != null) push_filter(filters);
|
||||
}
|
||||
|
||||
/* fallback-filter(默认 geoip:false,防止冷启动依赖 MMDB) */
|
||||
cfg['dns']['fallback-filter'] = { geoip: ab('fallback_filter_geoip') };
|
||||
|
||||
let dns_present = {};
|
||||
for (let f in split(s(getenv('CLASHOO_DNS_PRESENT'), ''), ','))
|
||||
if (length(trim(f))) dns_present[trim(f)] = true;
|
||||
let dns_force = {};
|
||||
for (let f in split(s(getenv('CLASHOO_DNS_FORCE'), ''), ','))
|
||||
if (length(trim(f))) dns_force[trim(f)] = true;
|
||||
let dns_inherit_mode = s(getenv('CLASHOO_DNS_INHERIT_MODE'), '');
|
||||
let inherited_nameserver = json(s(getenv('CLASHOO_DNS_INHERIT_NAMESERVER'), '[]'));
|
||||
if (type(inherited_nameserver) != 'array') {
|
||||
if (inherited_nameserver != null && inherited_nameserver != '')
|
||||
inherited_nameserver = [inherited_nameserver];
|
||||
else
|
||||
inherited_nameserver = [];
|
||||
}
|
||||
let dns_role_fields = ['nameserver', 'proxy-server-nameserver', 'direct-nameserver',
|
||||
'default-nameserver', 'nameserver-policy', 'respect-rules'];
|
||||
let had_user_dns_roles = false;
|
||||
for (let f in dns_role_fields)
|
||||
if (dns_present[f]) had_user_dns_roles = true;
|
||||
if (length(keys(dns_force)) || !had_user_dns_roles)
|
||||
cfg['dns']['x-clashoo-managed-dns'] = true;
|
||||
|
||||
function dns_scheme(protocol) {
|
||||
switch (trim(protocol || '')) {
|
||||
case '':
|
||||
case 'none': return '';
|
||||
case 'udp': case 'udp://': return 'udp://';
|
||||
case 'tcp': case 'tcp://': return 'tcp://';
|
||||
case 'dot': case 'tls': case 'tls://': return 'tls://';
|
||||
case 'doh': case 'https': case 'https://': return 'https://';
|
||||
case 'doq': case 'quic': case 'quic://': return 'quic://';
|
||||
default: return protocol;
|
||||
}
|
||||
}
|
||||
|
||||
function dns_server(address, protocol, port) {
|
||||
let addr = trim(address || '');
|
||||
if (!length(addr)) return null;
|
||||
if (match(addr, /^[A-Za-z][A-Za-z0-9+.-]*:\/\//)) return addr;
|
||||
let prefix = dns_scheme(protocol);
|
||||
return length(trim(port || '')) ? prefix + addr + ':' + trim(port) : prefix + addr;
|
||||
}
|
||||
|
||||
let dns_roles = {};
|
||||
uci.foreach('clashoo', 'dnsservers', function(sec) {
|
||||
if (b(sec.enabled) == false) return;
|
||||
let role = s(sec.ser_type, 'nameserver');
|
||||
if (role == 'fallback' && cfg['dns']['enhanced-mode'] == 'fake-ip') return;
|
||||
let srv = dns_server(sec.ser_address, sec.protocol, sec.ser_port);
|
||||
if (!srv) return;
|
||||
if (!dns_roles[role]) dns_roles[role] = [];
|
||||
push(dns_roles[role], srv);
|
||||
});
|
||||
for (let role in keys(dns_roles)) {
|
||||
if (role == 'proxy-server-nameserver' && (dns_inherit_mode == 'inherit' || dns_inherit_mode == 'skip') &&
|
||||
!dns_present[role] && !dns_force[role]) continue;
|
||||
if (!dns_present[role] || dns_force[role]) cfg['dns'][role] = dns_roles[role];
|
||||
}
|
||||
|
||||
if (dns_inherit_mode == 'inherit' && length(inherited_nameserver) &&
|
||||
!dns_present['proxy-server-nameserver'] && !dns_force['proxy-server-nameserver'])
|
||||
cfg['dns']['proxy-server-nameserver'] = inherited_nameserver;
|
||||
|
||||
let bootstrap = a('default_nameserver');
|
||||
if (bootstrap == null) bootstrap = a('defaul_nameserver');
|
||||
if (type(bootstrap) != 'array') bootstrap = bootstrap != null ? [bootstrap] : [];
|
||||
if (length(bootstrap) && (!dns_present['default-nameserver'] || dns_force['default-nameserver']))
|
||||
cfg['dns']['default-nameserver'] = bootstrap;
|
||||
|
||||
let policies = {};
|
||||
uci.foreach('clashoo', 'dns_policy', function(sec) {
|
||||
if (b(sec.enabled) == false) return;
|
||||
if (s(sec.policy_type, 'nameserver-policy') != 'nameserver-policy') return;
|
||||
let matcher = trim(sec.matcher || '');
|
||||
let servers = sec.nameserver;
|
||||
if (type(servers) != 'array') servers = servers != null ? [servers] : [];
|
||||
if (!length(matcher) || !length(servers)) return;
|
||||
if (matcher == 'geosite:cn') { matcher = 'rule-set:cn_domain'; need_cn_rs = true; }
|
||||
policies[matcher] = servers;
|
||||
});
|
||||
if (length(keys(policies)) && (!dns_present['nameserver-policy'] || dns_force['nameserver-policy']))
|
||||
cfg['dns']['nameserver-policy'] = policies;
|
||||
|
||||
let respect_rules = a('dns_respect_rules') == null ? true : ab('dns_respect_rules');
|
||||
if (dns_force['respect-rules'])
|
||||
cfg['dns']['respect-rules'] = respect_rules;
|
||||
else if (respect_rules && length(cfg['dns']['proxy-server-nameserver'] || [])
|
||||
&& !dns_present['respect-rules'] && !dns_present['prefer-h3'])
|
||||
cfg['dns']['respect-rules'] = true;
|
||||
|
||||
/* profile */
|
||||
let store_selected = ab('selection_cache');
|
||||
/* fake-ip cache defaults ON to survive restarts; opt out with '0' */
|
||||
let store_fake = a('fake_ip_cache') == null ? true : ab('fake_ip_cache');
|
||||
if (store_selected || store_fake) {
|
||||
cfg['profile'] = {};
|
||||
if (store_selected) cfg['profile']['store-selected'] = true;
|
||||
if (store_fake) cfg['profile']['store-fake-ip'] = true;
|
||||
}
|
||||
|
||||
/* bundled cn.mrs rule-provider, referenced by fake-ip-filter rule-set:cn_domain */
|
||||
if (need_cn_rs) {
|
||||
if (!cfg['rule-providers']) cfg['rule-providers'] = {};
|
||||
cfg['rule-providers']['cn_domain'] = {
|
||||
type: 'file',
|
||||
path: './ruleset/cn.mrs',
|
||||
format: 'mrs',
|
||||
behavior: 'domain',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ── authentication ────────────────────────────────── */
|
||||
if (ab('authentication')) {
|
||||
cfg['authentication'] = [];
|
||||
uci.foreach('clashoo', 'authentication', function(sec) {
|
||||
if (b(sec.enabled) == false) return;
|
||||
let user = trim(sec.username || '');
|
||||
let pass = trim(sec.password || '');
|
||||
if (user) push(cfg['authentication'], user + ':' + pass);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── hosts ────────────────────────────────────────── */
|
||||
cfg['hosts'] = {};
|
||||
uci.foreach('clashoo', 'hosts', function(sec) {
|
||||
if (b(sec.enabled) == false) return;
|
||||
let domain = trim(sec.adress || '');
|
||||
let ip = trim(sec.ip || '');
|
||||
if (domain && ip) cfg['hosts'][domain] = ip;
|
||||
});
|
||||
if (length(keys(cfg['hosts'])) == 0) delete cfg['hosts'];
|
||||
|
||||
/* ── sniffer ──────────────────────────────────────── */
|
||||
if (ab('sniffer_streaming') && !length(s(getenv('CLASHOO_KEEP_SNIFFER'), ''))) {
|
||||
cfg['sniffer'] = {
|
||||
enable: true,
|
||||
'force-dns-mapping': true,
|
||||
'parse-pure-ip': true,
|
||||
sniff: {
|
||||
HTTP: { ports: [80, 8080], 'override-destination': true },
|
||||
TLS: { ports: [443, 8443], 'override-destination': true },
|
||||
QUIC: { ports: [443, 8443], 'override-destination': true },
|
||||
},
|
||||
'force-domain': [
|
||||
'+.youtube.com',
|
||||
'+.googlevideo.com',
|
||||
'+.netflix.com',
|
||||
'+.nflxvideo.net',
|
||||
'+.disneyplus.com',
|
||||
'+.hulu.com',
|
||||
'+.hbomax.com',
|
||||
],
|
||||
'skip-domain': [
|
||||
'+.lan',
|
||||
'+.local',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/* ── Smart kernel injection ───────────────────────── */
|
||||
if (ab('smart_auto_switch')) {
|
||||
/* Smart 策略注入:通过 proxy-groups 覆盖实现 */
|
||||
}
|
||||
|
||||
/* ── ECS ──────────────────────────────────────────── */
|
||||
let ecs = s(a('dns_ecs'));
|
||||
if (ecs) cfg['dns']['edns-client-subnet'] = ecs;
|
||||
|
||||
/* ── cache_file / experimental ──────────────────── */
|
||||
cfg['experimental'] = {
|
||||
'cache_file': { enabled: true },
|
||||
'clash_api': {
|
||||
'external-controller': '0.0.0.0:' + i(a('dash_port'), 9090),
|
||||
'external-ui': './dashboard',
|
||||
'secret': s(a('dash_pass'), ''),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/* ── 输出 JSON (不含 null/空对象) ─────────────────── */
|
||||
function clean(obj) {
|
||||
if (type(obj) == 'array') {
|
||||
let r = [];
|
||||
for (let e in obj) {
|
||||
e = clean(e);
|
||||
if (e != null) push(r, e);
|
||||
}
|
||||
return length(r) ? r : null;
|
||||
}
|
||||
if (type(obj) == 'object') {
|
||||
let r = {};
|
||||
for (let k in keys(obj)) {
|
||||
let v = clean(obj[k]);
|
||||
if (v != null) r[k] = v;
|
||||
}
|
||||
return length(keys(r)) ? r : null;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
print(clean(cfg));
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
clashoo_resolve_primary_group() {
|
||||
local config_yaml="$1"
|
||||
local cached_group="$2"
|
||||
local group_rows
|
||||
local resolved_group
|
||||
|
||||
if [ "$cached_group" = "GLOBAL" ]; then
|
||||
printf '%s\n' "GLOBAL"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$config_yaml" ] || ! command -v yq >/dev/null 2>&1; then
|
||||
printf '%s\n' "GLOBAL"
|
||||
return 0
|
||||
fi
|
||||
|
||||
group_rows="$(yq -M e -r '(."proxy-groups" // [])[] | [.name, .type] | @tsv' "$config_yaml" 2>/dev/null)" || {
|
||||
printf '%s\n' "GLOBAL"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ -n "$cached_group" ] && printf '%s\n' "$group_rows" |
|
||||
awk -F '\t' -v wanted="$cached_group" '$1 == wanted { found = 1; exit } END { exit !found }'
|
||||
then
|
||||
printf '%s\n' "$cached_group"
|
||||
return 0
|
||||
fi
|
||||
|
||||
resolved_group="$(printf '%s\n' "$group_rows" | awk -F '\t' '
|
||||
tolower($2) == "select" &&
|
||||
(index($1, "节点选择") || index($1, "代理") || index($1, "🚀") || tolower($1) ~ /proxy/) {
|
||||
print $1
|
||||
exit
|
||||
}
|
||||
')"
|
||||
|
||||
if [ -z "$resolved_group" ]; then
|
||||
resolved_group="$(printf '%s\n' "$group_rows" | awk -F '\t' '
|
||||
tolower($2) == "select" {
|
||||
print $1
|
||||
exit
|
||||
}
|
||||
')"
|
||||
fi
|
||||
|
||||
printf '%s\n' "${resolved_group:-GLOBAL}"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# If every configured rule points to a proxy without UDP support, mihomo falls
|
||||
# through to DIRECT. Reject only that otherwise-unmatched QUIC traffic so the
|
||||
# client can retry over TCP; rules with a usable UDP target still win first.
|
||||
with(
|
||||
select(has("rules")) |
|
||||
select((.rules | type) == "!!seq") |
|
||||
select(([.rules[] | select(. == "AND,((NETWORK,UDP),(DST-PORT,443)),REJECT")] | length) == 0);
|
||||
.rules += ["AND,((NETWORK,UDP),(DST-PORT,443)),REJECT"]
|
||||
)
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -f /usr/share/clashbackup/history ];then
|
||||
|
||||
HISTORY_PATH="/usr/share/clashbackup/history"
|
||||
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
|
||||
}
|
||||
cat $HISTORY_PATH |while read line
|
||||
do
|
||||
if [ -z "$(echo $line |grep "#*#")" ]; then
|
||||
continue
|
||||
else
|
||||
GORUP_NAME=$(urlencode "$(echo $line |awk -F '#*#' '{print $1}')")
|
||||
NOW_NAME=$(echo $line |awk -F '#*#' '{print $3}')
|
||||
curl -H "Authorization: Bearer ${SECRET}" -H "Content-Type:application/json" -X PUT -d '{"name":"'"$NOW_NAME"'"}' http://"$LAN_IP":"$PORT"/proxies/"$GORUP_NAME" >/dev/null 2>&1
|
||||
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
|
||||
@@ -1,28 +0,0 @@
|
||||
# Preserve every explicit user choice while restoring missing per-protocol defaults.
|
||||
with(
|
||||
.sniffer |
|
||||
select((has("enable") | not) or (.enable == true)) |
|
||||
select((has("override-destination") | not) or (."override-destination" == true));
|
||||
with(
|
||||
select(has("force-dns-mapping") | not);
|
||||
."force-dns-mapping" = true
|
||||
) |
|
||||
with(
|
||||
select(has("parse-pure-ip") | not);
|
||||
."parse-pure-ip" = true
|
||||
) |
|
||||
with(
|
||||
.sniff | select(has("TLS"));
|
||||
with(
|
||||
.TLS | select(type == "!!map" and (has("override-destination") | not));
|
||||
."override-destination" = true
|
||||
)
|
||||
) |
|
||||
with(
|
||||
.sniff | select(has("QUIC"));
|
||||
with(
|
||||
.QUIC | select(type == "!!map" and (has("override-destination") | not));
|
||||
."override-destination" = true
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1,63 +0,0 @@
|
||||
time.windows.com
|
||||
time.nist.gov
|
||||
time.apple.com
|
||||
time.asia.apple.com
|
||||
cn.ntp.org.cn
|
||||
edu.ntp.org.cn
|
||||
hk.ntp.org.cn
|
||||
tw.ntp.org.cn
|
||||
us.ntp.org.cn
|
||||
sgp.ntp.org.cn
|
||||
kr.ntp.org.cn
|
||||
jp.ntp.org.cn
|
||||
de.ntp.org.cn
|
||||
ina.ntp.org.cn
|
||||
0.openwrt.pool.ntp.org
|
||||
1.openwrt.pool.ntp.org
|
||||
2.openwrt.pool.ntp.org
|
||||
3.openwrt.pool.ntp.org
|
||||
ntp.aliyun.com
|
||||
ntp1.aliyun.com
|
||||
ntp2.aliyun.com
|
||||
ntp3.aliyun.com
|
||||
ntp4.aliyun.com
|
||||
ntp5.aliyun.com
|
||||
ntp6.aliyun.com
|
||||
ntp7.aliyun.com
|
||||
time1.aliyun.com
|
||||
time2.aliyun.com
|
||||
time3.aliyun.com
|
||||
time4.aliyun.com
|
||||
time5.aliyun.com
|
||||
time6.aliyun.com
|
||||
time7.aliyun.com
|
||||
s1c.time.edu.cn
|
||||
s2m.time.edu.cn
|
||||
s1b.time.edu.cn
|
||||
s1e.time.edu.cn
|
||||
s2a.time.edu.cn
|
||||
s2b.time.edu.cn
|
||||
time1.apple.com
|
||||
time2.apple.com
|
||||
time3.apple.com
|
||||
time4.apple.com
|
||||
time5.apple.com
|
||||
time6.apple.com
|
||||
time7.apple.com
|
||||
time1.google.com
|
||||
time2.google.com
|
||||
time3.google.com
|
||||
time4.google.com
|
||||
.music.163.com
|
||||
.music.126.net
|
||||
man.netease.com
|
||||
api.iplay.163.com
|
||||
ac.dun.163yun.com
|
||||
mr.da.netease.com
|
||||
crash.163.com
|
||||
imap.163.com
|
||||
msftconnecttest.com
|
||||
msftncsi.com
|
||||
captive.qq.com
|
||||
ssl.ptlogin2.qq.com
|
||||
login.qq.com
|
||||
@@ -1,642 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
LOG_FILE="/tmp/clashoo_component_update.log"
|
||||
RUN_FILE="/var/run/clashoo_component_update"
|
||||
STATE_FILE="/tmp/clashoo_component_update_state"
|
||||
TMP_DIR="/tmp/clashoo-component-update"
|
||||
APK_UPGRADE_FLAG="/var/run/clashoo_package_upgrade"
|
||||
FEED_BASE_URL="https://down.dllkids.xyz/openwrt-feed/clashoo"
|
||||
GITHUB_API_URL="https://api.github.com/repos/kenzok8/openwrt-clashoo/releases/latest"
|
||||
ENV_GITHUB_PROXY_PREFIX="${GITHUB_PROXY_PREFIX:-}"
|
||||
UCI_GITHUB_PROXY_PREFIX="$(uci -q get clashoo.config.core_mirror_prefix 2>/dev/null)"
|
||||
GITHUB_PROXY_PREFIX="${UCI_GITHUB_PROXY_PREFIX:-${ENV_GITHUB_PROXY_PREFIX:-https://ghfast.top/}}"
|
||||
case "$GITHUB_PROXY_PREFIX" in
|
||||
*/) ;;
|
||||
*) GITHUB_PROXY_PREFIX="${GITHUB_PROXY_PREFIX}/" ;;
|
||||
esac
|
||||
CONNECT_TIMEOUT="${CONNECT_TIMEOUT:-8}"
|
||||
REQUEST_TIMEOUT="${REQUEST_TIMEOUT:-20}"
|
||||
LOW_SPEED_TIME="${LOW_SPEED_TIME:-30}"
|
||||
LOW_SPEED_LIMIT="${LOW_SPEED_LIMIT:-1024}"
|
||||
|
||||
trap 'rm -f "$APK_UPGRADE_FLAG"' EXIT
|
||||
|
||||
log() {
|
||||
mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$STATE_FILE")" >/dev/null 2>&1
|
||||
printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$LOG_FILE"
|
||||
printf 'component=%s\nstatus=%s\nmessage=%s\n' "${COMPONENT:-}" "${STATUS:-running}" "$*" >"$STATE_FILE"
|
||||
}
|
||||
|
||||
finish() {
|
||||
rc="$1"
|
||||
msg="$2"
|
||||
STATUS="success"
|
||||
[ "$rc" -eq 0 ] || STATUS="failed"
|
||||
log "$msg"
|
||||
rm -f "$RUN_FILE" "$APK_UPGRADE_FLAG"
|
||||
exit "$rc"
|
||||
}
|
||||
|
||||
# In kernel-only mode there is no transparent proxy, so component downloads
|
||||
# would go out direct and stall behind the GFW. Route them through the running
|
||||
# core (shared logic in proxy_lib.sh). Normal mode returns empty -> TPROXY.
|
||||
# Guard the source: on a stale install missing proxy_lib.sh, an unguarded `.`
|
||||
# under `set -e` kills the whole update silently (empty log, nothing happens).
|
||||
# Fall back to no proxy detection (direct / TPROXY) instead of dying.
|
||||
if [ -f /usr/share/clashoo/update/proxy_lib.sh ]; then
|
||||
. /usr/share/clashoo/update/proxy_lib.sh
|
||||
else
|
||||
clashoo_detect_proxy() { :; }
|
||||
fi
|
||||
detect_proxy() { clashoo_detect_proxy; }
|
||||
|
||||
fetch_text() {
|
||||
url="$1"
|
||||
proxy="$(detect_proxy)"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" ${proxy:+--proxy "$proxy"} "$url"
|
||||
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"
|
||||
return $?
|
||||
fi
|
||||
return 127
|
||||
}
|
||||
|
||||
download_file() {
|
||||
url="$1"
|
||||
out="$2"
|
||||
proxy="$(detect_proxy)"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fL --connect-timeout "$CONNECT_TIMEOUT" --speed-time "$LOW_SPEED_TIME" --speed-limit "$LOW_SPEED_LIMIT" ${proxy:+--proxy "$proxy"} "$url" -o "$out"
|
||||
return $?
|
||||
fi
|
||||
if [ -n "$proxy" ]; then
|
||||
http_proxy="$proxy" https_proxy="$proxy" wget -qO "$out" --timeout="$REQUEST_TIMEOUT" --tries=1 "$url"
|
||||
return $?
|
||||
fi
|
||||
wget -qO "$out" --timeout="$REQUEST_TIMEOUT" --tries=1 "$url"
|
||||
}
|
||||
|
||||
download_url() {
|
||||
url="$1"
|
||||
case "$url" in
|
||||
https://github.com/*)
|
||||
printf '%s%s\n' "$GITHUB_PROXY_PREFIX" "$url"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$url"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
root_free_kb() {
|
||||
df -k / 2>/dev/null | awk 'NR==2 {print $4}'
|
||||
}
|
||||
|
||||
kb_to_mib() {
|
||||
awk "BEGIN {printf \"%.0f\", (${1:-0}) / 1024}"
|
||||
}
|
||||
|
||||
ensure_root_space() {
|
||||
need_kb="$1"
|
||||
free_kb="$(root_free_kb)"
|
||||
[ -n "$free_kb" ] || return 0
|
||||
[ "$free_kb" -ge "$need_kb" ] && return 0
|
||||
finish 1 "根分区空间不足:可用 $(kb_to_mib "$free_kb") MiB,至少需要 $(kb_to_mib "$need_kb") MiB"
|
||||
}
|
||||
|
||||
log_install_error() {
|
||||
file="$1"
|
||||
[ -s "$file" ] || return 0
|
||||
tail -8 "$file" 2>/dev/null | while IFS= read -r line; do
|
||||
[ -n "$line" ] && log "$line"
|
||||
done
|
||||
}
|
||||
|
||||
detect_manager() {
|
||||
if command -v opkg >/dev/null 2>&1; then
|
||||
echo opkg
|
||||
return
|
||||
fi
|
||||
if command -v apk >/dev/null 2>&1; then
|
||||
echo apk
|
||||
return
|
||||
fi
|
||||
echo unsupported
|
||||
}
|
||||
|
||||
installed_package_version() {
|
||||
pkg="$1"
|
||||
if command -v opkg >/dev/null 2>&1; then
|
||||
opkg status "$pkg" 2>/dev/null | awk -F': ' '/^Version:/{print $2; exit}'
|
||||
return
|
||||
fi
|
||||
if command -v apk >/dev/null 2>&1; then
|
||||
apk list -I "$pkg" 2>/dev/null | awk '{print $1; exit}' | sed -n "s/^${pkg}-//p"
|
||||
fi
|
||||
}
|
||||
|
||||
package_installed() {
|
||||
pkg="$1"
|
||||
if command -v opkg >/dev/null 2>&1; then
|
||||
opkg status "$pkg" 2>/dev/null | awk '
|
||||
$1 == "Status:" && $2 == "install" && $NF == "installed" { found = 1 }
|
||||
END { exit !found }
|
||||
'
|
||||
return
|
||||
fi
|
||||
apk info -e "$pkg" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
apk_install() {
|
||||
proxy="$(detect_proxy)"
|
||||
if [ -n "$proxy" ]; then
|
||||
http_proxy="$proxy" https_proxy="$proxy" apk add --allow-untrusted --timeout 30 "$@"
|
||||
else
|
||||
apk add --allow-untrusted --timeout 30 "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
apk_bundle_install() {
|
||||
printf '%s\n' "$$" >"$APK_UPGRADE_FLAG"
|
||||
apk_install "$@"
|
||||
rc=$?
|
||||
rm -f "$APK_UPGRADE_FLAG"
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
verify_downloaded_package() {
|
||||
file="$1"
|
||||
[ -s "$file" ] || return 1
|
||||
if [ "$PM" = "apk" ]; then
|
||||
apk verify --allow-untrusted "$file" >/dev/null 2>&1
|
||||
return
|
||||
fi
|
||||
gzip -t "$file" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
download_package_file() {
|
||||
url="$1"
|
||||
out="$2"
|
||||
name="$3"
|
||||
download_file "$(download_url "$url")" "$out" || finish 1 "下载 ${name} 失败"
|
||||
verify_downloaded_package "$out" || finish 1 "${name} 文件校验失败"
|
||||
}
|
||||
|
||||
detect_arch() {
|
||||
pm="$1"
|
||||
if [ "$pm" = "opkg" ]; then
|
||||
opkg print-architecture 2>/dev/null | awk '/^arch / {print $2}' | tail -n 1
|
||||
return
|
||||
fi
|
||||
# apk: DISTRIB_ARCH is authoritative (e.g. aarch64_cortex-a53); apk --print-arch
|
||||
# only gives the generic "aarch64" which won't match the .apk package suffix.
|
||||
if [ -r /etc/openwrt_release ]; then
|
||||
a="$(sed -n "s/^DISTRIB_ARCH=['\"]\\([^'\"]*\\)['\"]$/\\1/p" /etc/openwrt_release | head -n 1)"
|
||||
[ -n "$a" ] && { printf '%s\n' "$a"; return; }
|
||||
fi
|
||||
apk --print-arch 2>/dev/null
|
||||
}
|
||||
|
||||
detect_sdk() {
|
||||
[ -r /etc/openwrt_release ] || return 1
|
||||
release="$(sed -n "s/^DISTRIB_RELEASE=['\"]\\([^'\"]*\\)['\"]$/\\1/p" /etc/openwrt_release | head -n 1)"
|
||||
[ -n "$release" ] || return 1
|
||||
printf '%s\n' "$release" | grep -Eo '[0-9]+\.[0-9]+' | head -n 1
|
||||
}
|
||||
|
||||
append_unique_word() {
|
||||
value="$1"
|
||||
list="$2"
|
||||
[ -n "$value" ] || {
|
||||
printf '%s\n' "$list"
|
||||
return
|
||||
}
|
||||
case " $list " in
|
||||
*" $value "*) ;;
|
||||
*) list="${list}${list:+ }${value}" ;;
|
||||
esac
|
||||
printf '%s\n' "$list"
|
||||
}
|
||||
|
||||
build_sdk_candidates() {
|
||||
pm="$1"
|
||||
candidates=""
|
||||
detected_sdk="$(detect_sdk || true)"
|
||||
candidates="$(append_unique_word "$detected_sdk" "$candidates")"
|
||||
|
||||
if [ "$pm" = "opkg" ]; then
|
||||
for sdk in 24.10 23.05 22.03 21.02; do
|
||||
candidates="$(append_unique_word "$sdk" "$candidates")"
|
||||
done
|
||||
else
|
||||
for sdk in 25.12 24.10; do
|
||||
candidates="$(append_unique_word "$sdk" "$candidates")"
|
||||
done
|
||||
fi
|
||||
|
||||
printf '%s\n' "$candidates"
|
||||
}
|
||||
|
||||
find_manifest_value() {
|
||||
key="$1"
|
||||
manifest_text="$2"
|
||||
printf '%s\n' "$manifest_text" | sed -n "s/^${key}=//p" | head -n 1
|
||||
}
|
||||
|
||||
load_manifest_urls() {
|
||||
sdk="$1"
|
||||
arch="$2"
|
||||
manifest_url="${FEED_BASE_URL}/${sdk}/${arch}/manifest-clashoo.txt"
|
||||
manifest_text="$(fetch_text "$manifest_url" || true)"
|
||||
[ -n "$manifest_text" ] || return 1
|
||||
|
||||
core_file="$(find_manifest_value "core" "$manifest_text")"
|
||||
luci_file="$(find_manifest_value "luci" "$manifest_text")"
|
||||
i18n_file="$(find_manifest_value "i18n" "$manifest_text")"
|
||||
[ -n "$core_file" ] || return 1
|
||||
[ -n "$luci_file" ] || return 1
|
||||
case "$core_file" in *."$EXT") ;; *) return 1 ;; esac
|
||||
case "$luci_file" in *."$EXT") ;; *) return 1 ;; esac
|
||||
case "$i18n_file" in ""|*."$EXT") ;; *) return 1 ;; esac
|
||||
|
||||
CORE_URL="${FEED_BASE_URL}/${sdk}/${arch}/${core_file}"
|
||||
LUCI_URL="${FEED_BASE_URL}/${sdk}/${arch}/${luci_file}"
|
||||
I18N_URL=""
|
||||
[ -n "$i18n_file" ] && I18N_URL="${FEED_BASE_URL}/${sdk}/${arch}/${i18n_file}"
|
||||
SOURCE_LABEL="R2 feed ${sdk}/${arch}"
|
||||
return 0
|
||||
}
|
||||
|
||||
load_github_urls() {
|
||||
arch="$1"
|
||||
ext="$2"
|
||||
payload="$(fetch_text "$GITHUB_API_URL" || true)"
|
||||
[ -n "$payload" ] || return 1
|
||||
|
||||
urls="$(printf '%s\n' "$payload" | sed -n 's/.*"browser_download_url":[[:space:]]*"\([^"]*\)".*/\1/p')"
|
||||
[ -n "$urls" ] || return 1
|
||||
|
||||
if [ "$ext" = "apk" ]; then
|
||||
CORE_URL="$(printf '%s\n' "$urls" | grep -E '/clashoo-[^-]+.*-r[0-9]+-'"$arch"'\.apk$' | head -n 1)"
|
||||
LUCI_URL="$(printf '%s\n' "$urls" | grep -E '/luci-app-clashoo-[^-]+.*-r[0-9]+-('"$arch"'|all)\.apk$' | head -n 1)"
|
||||
I18N_URL="$(printf '%s\n' "$urls" | grep -E '/luci-i18n-clashoo-zh-cn-[^-]+.*-r[0-9]+-('"$arch"'|all)\.apk$' | head -n 1)"
|
||||
else
|
||||
CORE_URL="$(printf '%s\n' "$urls" | grep -E '/clashoo_.*_'"$arch"'\.ipk$' | head -n 1)"
|
||||
LUCI_URL="$(printf '%s\n' "$urls" | grep -E '/luci-app-clashoo_.*_all\.ipk$' | head -n 1)"
|
||||
I18N_URL="$(printf '%s\n' "$urls" | grep -E '/luci-i18n-clashoo-zh-cn_.*_all\.ipk$' | head -n 1)"
|
||||
fi
|
||||
|
||||
[ -n "$CORE_URL" ] || return 1
|
||||
[ -n "$LUCI_URL" ] || return 1
|
||||
SOURCE_LABEL="GitHub latest"
|
||||
return 0
|
||||
}
|
||||
|
||||
package_version_from_url() {
|
||||
file="${1##*/}"
|
||||
case "$file" in
|
||||
clashoo_*_"$ARCH".ipk)
|
||||
v="${file#clashoo_}"
|
||||
v="${v%_${ARCH}.ipk}"
|
||||
printf '%s\n' "$v" | sed 's/^\([0-9][0-9][0-9][0-9]\.[0-9][0-9]*\.[0-9][0-9]*\)\./\1~/'
|
||||
;;
|
||||
clashoo_*.ipk) printf '%s\n' "$file" | sed -n 's/^clashoo_\(.*\)_[^_][^_]*\.ipk$/\1/p' | sed 's/^\([0-9][0-9][0-9][0-9]\.[0-9][0-9]*\.[0-9][0-9]*\)\./\1~/' ;;
|
||||
luci-app-clashoo_*.ipk) printf '%s\n' "$file" | sed -n 's/^luci-app-clashoo_\(.*\)_all\.ipk$/\1/p' ;;
|
||||
luci-i18n-clashoo-zh-cn_*.ipk) printf '%s\n' "$file" | sed -n 's/^luci-i18n-clashoo-zh-cn_\(.*\)_all\.ipk$/\1/p' ;;
|
||||
clashoo-*.apk)
|
||||
v="$(printf '%s\n' "$file" | sed -n -e 's/^clashoo-\(.*-r[0-9][0-9]*\)-.*\.apk$/\1/p' -e 's/^clashoo-\(.*-r[0-9][0-9]*\)\.apk$/\1/p' | head -n 1)"
|
||||
printf '%s\n' "$v" | sed 's/^\([0-9][0-9][0-9][0-9]\.[0-9][0-9]*\.[0-9][0-9]*\)\./\1~/'
|
||||
;;
|
||||
luci-app-clashoo-*.apk) printf '%s\n' "$file" | sed -n -e 's/^luci-app-clashoo-\(.*-r[0-9][0-9]*\)-.*\.apk$/\1/p' -e 's/^luci-app-clashoo-\(.*-r[0-9][0-9]*\)\.apk$/\1/p' | head -n 1 ;;
|
||||
luci-i18n-clashoo-zh-cn-*.apk) printf '%s\n' "$file" | sed -n -e 's/^luci-i18n-clashoo-zh-cn-\(.*-r[0-9][0-9]*\)-.*\.apk$/\1/p' -e 's/^luci-i18n-clashoo-zh-cn-\(.*-r[0-9][0-9]*\)\.apk$/\1/p' | head -n 1 ;;
|
||||
*) printf '%s\n' "$file" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
resolve_bundle_urls() {
|
||||
PM="$(detect_manager)"
|
||||
[ "$PM" != "unsupported" ] || return 1
|
||||
ARCH="$(detect_arch "$PM")"
|
||||
[ -n "$ARCH" ] || return 1
|
||||
EXT="ipk"
|
||||
[ "$PM" = "apk" ] && EXT="apk"
|
||||
|
||||
CORE_URL=""
|
||||
LUCI_URL=""
|
||||
I18N_URL=""
|
||||
SOURCE_LABEL=""
|
||||
|
||||
SDK_CANDIDATES="$(build_sdk_candidates "$PM")"
|
||||
for sdk in $SDK_CANDIDATES; do
|
||||
if load_manifest_urls "$sdk" "$ARCH"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
load_github_urls "$ARCH" "$EXT"
|
||||
}
|
||||
|
||||
backup_config() {
|
||||
ts="$(date '+%Y%m%d-%H%M%S')"
|
||||
BACKUP_DIR="/etc/clashoo/backup/component-upgrade-${ts}"
|
||||
mkdir -p "$BACKUP_DIR/etc-config" "$BACKUP_DIR/etc-clashoo" >/dev/null 2>&1 || return 1
|
||||
[ -r /etc/config/clashoo ] && cp -a /etc/config/clashoo "$BACKUP_DIR/etc-config/clashoo"
|
||||
# 只备份用户配置;Model.bin 等可重新下载的大文件不入备份,避免每次更新
|
||||
# 复制 ~5MB 的同一模型,把备份目录撑大、堆满本就紧张的根分区。
|
||||
for path in \
|
||||
/etc/clashoo/config.yaml \
|
||||
/etc/clashoo/config.json \
|
||||
/etc/clashoo/*.yaml \
|
||||
/etc/clashoo/*.yml \
|
||||
/etc/clashoo/*.json
|
||||
do
|
||||
[ -e "$path" ] || continue
|
||||
cp -a "$path" "$BACKUP_DIR/etc-clashoo/" 2>/dev/null || true
|
||||
done
|
||||
log "已备份配置:${BACKUP_DIR}"
|
||||
|
||||
# 只保留最近 1 个备份,删除更早的,避免无限堆积占满磁盘。
|
||||
ls -dt /etc/clashoo/backup/component-upgrade-* 2>/dev/null | tail -n +2 | while read -r _old; do
|
||||
[ -n "$_old" ] && rm -rf "$_old" 2>/dev/null
|
||||
done
|
||||
}
|
||||
|
||||
restore_config_backup() {
|
||||
[ -n "${BACKUP_DIR:-}" ] || return 0
|
||||
[ -r "$BACKUP_DIR/etc-config/clashoo" ] && cp -a "$BACKUP_DIR/etc-config/clashoo" /etc/config/clashoo
|
||||
if [ -d "$BACKUP_DIR/etc-clashoo" ]; then
|
||||
mkdir -p /etc/clashoo
|
||||
cp -a "$BACKUP_DIR/etc-clashoo/." /etc/clashoo/ 2>/dev/null || true
|
||||
fi
|
||||
uci commit clashoo >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
clashoo_was_running() {
|
||||
/etc/init.d/clashoo status >/dev/null 2>&1 && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
restart_web_stack() {
|
||||
# reload (not restart) rpcd so the LuCI login session survives a plugin
|
||||
# update — rpcd restart drops every ubus session and forces a re-login
|
||||
# (issue #12). reload still picks up the new ucode RPC backend. uhttpd needs
|
||||
# nothing: updated static assets are served on the next request.
|
||||
/etc/init.d/rpcd reload >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# which: clashoo(仅核心)/ luci(luci-app + 语言包)。两者拆开,clashoo
|
||||
# 核心更新频率高于 LuCI,分别更新便于定位失败。
|
||||
run_pkg_update() {
|
||||
which="$1"
|
||||
log "正在检查组件包"
|
||||
if ! resolve_bundle_urls; then
|
||||
finish 1 "未找到适配当前设备的组件包"
|
||||
fi
|
||||
log "更新来源:${SOURCE_LABEL}"
|
||||
|
||||
core_ver="$(package_version_from_url "$CORE_URL")"
|
||||
luci_ver="$(package_version_from_url "$LUCI_URL")"
|
||||
i18n_ver=""
|
||||
[ -z "$I18N_URL" ] || i18n_ver="$(package_version_from_url "$I18N_URL")"
|
||||
core_ver_before="$(installed_package_version clashoo)"
|
||||
|
||||
apk_core_ready() {
|
||||
package_installed clashoo && [ "$(installed_package_version clashoo)" = "$core_ver" ] \
|
||||
&& [ -x /etc/init.d/clashoo ] && [ -x /usr/share/clashoo/update/component_update.sh ]
|
||||
}
|
||||
|
||||
apk_luci_ready() {
|
||||
package_installed luci-app-clashoo && [ "$(installed_package_version luci-app-clashoo)" = "$luci_ver" ] \
|
||||
&& { [ -z "$I18N_URL" ] || { package_installed luci-i18n-clashoo-zh-cn \
|
||||
&& [ "$(installed_package_version luci-i18n-clashoo-zh-cn)" = "$i18n_ver" ]; }; } \
|
||||
&& [ -r /usr/share/luci/menu.d/luci-app-clashoo.json ] \
|
||||
&& [ -r /usr/share/rpcd/ucode/luci.clashoo ]
|
||||
}
|
||||
|
||||
apk_bundle_ready() {
|
||||
apk_core_ready && apk_luci_ready
|
||||
}
|
||||
|
||||
apk_update_ready() {
|
||||
if [ "$which" = "clashoo" ]; then
|
||||
apk_bundle_ready
|
||||
else
|
||||
package_installed clashoo && apk_luci_ready
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$which" = "clashoo" ]; then
|
||||
log "目标版本:Clashoo 核心 ${core_ver}"
|
||||
cur_ver="$(installed_package_version clashoo)"
|
||||
if [ "$PM" = "apk" ] && apk_update_ready; then
|
||||
finish 0 "Clashoo 核心已是最新版本"
|
||||
elif [ "$PM" != "apk" ] && [ -n "$core_ver" ] && [ "$cur_ver" = "$core_ver" ]; then
|
||||
finish 0 "Clashoo 核心已是最新版本"
|
||||
fi
|
||||
else
|
||||
log "目标版本:客户端 ${luci_ver}"
|
||||
cur_ver="$(installed_package_version luci-app-clashoo)"
|
||||
if [ "$PM" = "apk" ] && apk_update_ready; then
|
||||
finish 0 "客户端已是最新版本"
|
||||
elif [ "$PM" != "apk" ] && [ -n "$luci_ver" ] && [ "$cur_ver" = "$luci_ver" ]; then
|
||||
finish 0 "客户端已是最新版本"
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -rf "$TMP_DIR"
|
||||
mkdir -p "$TMP_DIR" || finish 1 "创建临时目录失败"
|
||||
|
||||
was_running=0
|
||||
clashoo_was_running && was_running=1
|
||||
backup_config || finish 1 "备份配置失败"
|
||||
apk_core_file=""
|
||||
|
||||
if [ "$PM" = "apk" ]; then
|
||||
log "正在下载组件包"
|
||||
if [ "$which" = "clashoo" ] || ! package_installed clashoo; then
|
||||
ensure_root_space 98304
|
||||
download_package_file "$CORE_URL" "$TMP_DIR/core.${EXT}" "clashoo"
|
||||
apk_core_file="$TMP_DIR/core.${EXT}"
|
||||
fi
|
||||
download_package_file "$LUCI_URL" "$TMP_DIR/luci.${EXT}" "luci-app-clashoo"
|
||||
if [ -n "$I18N_URL" ]; then
|
||||
download_package_file "$I18N_URL" "$TMP_DIR/i18n.${EXT}" "语言包"
|
||||
fi
|
||||
elif [ "$which" = "clashoo" ]; then
|
||||
log "正在下载 Clashoo 核心"
|
||||
download_package_file "$CORE_URL" "$TMP_DIR/core.${EXT}" "clashoo"
|
||||
else
|
||||
log "正在下载客户端(LuCI + 语言包)"
|
||||
download_package_file "$LUCI_URL" "$TMP_DIR/luci.${EXT}" "luci-app-clashoo"
|
||||
if [ -n "$I18N_URL" ]; then
|
||||
download_package_file "$I18N_URL" "$TMP_DIR/i18n.${EXT}" "语言包"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PM" != "apk" ] && [ "$which" = "clashoo" ]; then
|
||||
ensure_root_space 98304
|
||||
fi
|
||||
|
||||
if [ "$which" = "clashoo" ]; then
|
||||
log "正在安装 Clashoo 核心"
|
||||
if [ "$PM" = "opkg" ]; then
|
||||
opkg install --force-downgrade "$TMP_DIR/core.${EXT}" >"$TMP_DIR/install.log" 2>&1; rc=$?
|
||||
else
|
||||
apk_bundle_install ${apk_core_file:+"$apk_core_file"} "$TMP_DIR/luci.${EXT}" ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} >"$TMP_DIR/install.log" 2>&1; rc=$?
|
||||
fi
|
||||
else
|
||||
log "正在安装客户端"
|
||||
if [ "$PM" = "opkg" ]; then
|
||||
opkg install --force-downgrade "$TMP_DIR/luci.${EXT}" ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} >"$TMP_DIR/install.log" 2>&1; rc=$?
|
||||
else
|
||||
apk_bundle_install ${apk_core_file:+"$apk_core_file"} "$TMP_DIR/luci.${EXT}" ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} >"$TMP_DIR/install.log" 2>&1; rc=$?
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
if [ "$PM" = "apk" ] && apk_update_ready; then
|
||||
log "apk 报告了其它软件包的错误,本次组件已升级到目标版本"
|
||||
rc=0
|
||||
else
|
||||
log_install_error "$TMP_DIR/install.log"
|
||||
log "安装失败,正在恢复配置备份"
|
||||
restore_config_backup
|
||||
finish "$rc" "组件更新失败,已停止操作,请查看组件更新日志"
|
||||
fi
|
||||
fi
|
||||
|
||||
validation_failed() {
|
||||
log "安装校验失败,正在恢复配置备份"
|
||||
restore_config_backup
|
||||
finish 1 "组件安装校验失败,已停止操作,请查看组件更新日志"
|
||||
}
|
||||
|
||||
if [ "$PM" = "apk" ]; then
|
||||
if ! apk_update_ready; then
|
||||
log "安装校验失败,正在安全重装组件包"
|
||||
if [ -z "$apk_core_file" ] && ! package_installed clashoo; then
|
||||
ensure_root_space 98304
|
||||
download_package_file "$CORE_URL" "$TMP_DIR/core.${EXT}" "clashoo"
|
||||
apk_core_file="$TMP_DIR/core.${EXT}"
|
||||
fi
|
||||
apk_bundle_install --force-reinstall ${apk_core_file:+"$apk_core_file"} "$TMP_DIR/luci.${EXT}" \
|
||||
${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} >>"$TMP_DIR/install.log" 2>&1; repair_rc=$?
|
||||
if [ "$repair_rc" -ne 0 ]; then
|
||||
log_install_error "$TMP_DIR/install.log"
|
||||
validation_failed
|
||||
fi
|
||||
apk_update_ready || validation_failed
|
||||
fi
|
||||
elif [ "$which" = "clashoo" ]; then
|
||||
package_installed clashoo && [ "$(installed_package_version clashoo)" = "$core_ver" ] \
|
||||
&& [ -x /etc/init.d/clashoo ] && [ -x /usr/share/clashoo/update/component_update.sh ] \
|
||||
|| validation_failed
|
||||
else
|
||||
package_installed luci-app-clashoo && [ "$(installed_package_version luci-app-clashoo)" = "$luci_ver" ] \
|
||||
&& [ -r /usr/share/luci/menu.d/luci-app-clashoo.json ] \
|
||||
&& [ -r /usr/share/rpcd/ucode/luci.clashoo ] \
|
||||
|| validation_failed
|
||||
[ -z "$I18N_URL" ] || package_installed luci-i18n-clashoo-zh-cn || validation_failed
|
||||
fi
|
||||
|
||||
# APK component updates install the LuCI bundle in the same transaction.
|
||||
if [ "$which" = "luci" ] || [ "$PM" = "apk" ]; then
|
||||
log "正在刷新 LuCI 服务"
|
||||
restart_web_stack
|
||||
fi
|
||||
core_ver_after="$(installed_package_version clashoo)"
|
||||
restart_clashoo=0
|
||||
[ "$which" = "clashoo" ] && restart_clashoo=1
|
||||
[ "$PM" = "apk" ] && [ "$core_ver_before" != "$core_ver_after" ] && restart_clashoo=1
|
||||
if [ "$restart_clashoo" -eq 1 ] && [ "$was_running" -eq 1 ] && [ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ]; then
|
||||
log "Clashoo 原本运行中,正在重启服务"
|
||||
sh /usr/share/clashoo/rpc/rpc_async.sh restart >/dev/null 2>&1 || /etc/init.d/clashoo restart >/dev/null 2>&1 || true
|
||||
elif [ "$restart_clashoo" -eq 1 ]; then
|
||||
log "Clashoo 原本未运行,保持停止状态"
|
||||
fi
|
||||
|
||||
finish 0 "组件更新完成"
|
||||
}
|
||||
|
||||
run_core_update() {
|
||||
dcore="$1"
|
||||
label="$2"
|
||||
log "正在更新 ${label}"
|
||||
# Pass the target as an arg so core_download refreshes only that binary
|
||||
# without switching the active kernel. It restarts only if the target is the
|
||||
# currently-running core (see core_download.sh finalize). Switching kernels
|
||||
# stays a separate, explicit user action.
|
||||
touch /var/run/core_update
|
||||
sh /usr/share/clashoo/update/core_download.sh "$dcore" >>/tmp/clash_update.txt 2>&1
|
||||
rc=$?
|
||||
rm -f /var/run/core_update
|
||||
[ "$rc" -eq 0 ] || finish "$rc" "${label} 更新失败"
|
||||
finish 0 "${label} 更新完成"
|
||||
}
|
||||
|
||||
run_lgbm_update() {
|
||||
log "正在更新 Smart LightGBM 模型"
|
||||
sh /usr/share/clashoo/update/lgbm_update.sh >/tmp/lgbm_update.log 2>&1
|
||||
rc=$?
|
||||
tail -5 /tmp/lgbm_update.log 2>/dev/null | while IFS= read -r line; do
|
||||
[ -n "$line" ] && log "$line"
|
||||
done
|
||||
[ "$rc" -eq 0 ] || finish "$rc" "LightGBM 模型更新失败"
|
||||
finish 0 "LightGBM 模型更新完成"
|
||||
}
|
||||
|
||||
run_china_update() {
|
||||
log "正在更新大陆白名单"
|
||||
sh /usr/share/clashoo/update/update_china_ip.sh >>/tmp/clash_update.txt 2>&1
|
||||
rc=$?
|
||||
tail -6 /tmp/clash_update.txt 2>/dev/null | while IFS= read -r line; do
|
||||
[ -n "$line" ] && printf '%s\n' "$line" | grep -q '白名单' && log "$line"
|
||||
done
|
||||
[ "$rc" -eq 0 ] || finish "$rc" "大陆白名单更新失败"
|
||||
finish 0 "大陆白名单更新完成"
|
||||
}
|
||||
|
||||
run_geoip_update() {
|
||||
log "正在更新 GeoIP / GeoSite"
|
||||
touch /var/run/geoip_update
|
||||
sh /usr/share/clashoo/update/geoip.sh >/tmp/geoip_update.txt 2>&1
|
||||
rc=$?
|
||||
rm -f /var/run/geoip_update
|
||||
tail -8 /tmp/geoip_update.txt 2>/dev/null | while IFS= read -r line; do
|
||||
[ -n "$line" ] && log "$line"
|
||||
done
|
||||
[ "$rc" -eq 0 ] || finish "$rc" "GeoIP / GeoSite 更新失败"
|
||||
finish 0 "GeoIP / GeoSite 更新完成"
|
||||
}
|
||||
|
||||
COMPONENT="${1:-}"
|
||||
VARIANT="${2:-}" # mihomo/singbox: stable | alpha
|
||||
[ -n "$COMPONENT" ] || {
|
||||
echo "usage: $0 <clashoo|luci|mihomo|singbox|smart|lgbm|china|geoip> [stable|alpha]"
|
||||
exit 2
|
||||
}
|
||||
|
||||
mkdir -p "$(dirname "$RUN_FILE")" >/dev/null 2>&1
|
||||
printf '%s\n' "$COMPONENT" >"$RUN_FILE"
|
||||
STATUS="running"
|
||||
log "组件更新任务启动:${COMPONENT}${VARIANT:+ ($VARIANT)}"
|
||||
|
||||
case "$COMPONENT" in
|
||||
clashoo) run_pkg_update clashoo ;;
|
||||
luci) run_pkg_update luci ;;
|
||||
mihomo)
|
||||
if [ "$VARIANT" = "alpha" ]; then run_core_update 3 "mihomo Alpha 版"
|
||||
else run_core_update 2 "mihomo 稳定版"; fi ;;
|
||||
singbox)
|
||||
if [ "$VARIANT" = "alpha" ]; then run_core_update 5 "sing-box Alpha 版"
|
||||
else run_core_update 4 "sing-box 稳定版"; fi ;;
|
||||
smart) run_core_update 1 "mihomo Smart 版" ;;
|
||||
lgbm) run_lgbm_update ;;
|
||||
china) run_china_update ;;
|
||||
geoip) run_geoip_update ;;
|
||||
*) finish 2 "未知组件:${COMPONENT}" ;;
|
||||
esac
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,267 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
LOG_FILE="/tmp/geoip_update.txt"
|
||||
TMP_DIR="/tmp/clash_geoip_$$"
|
||||
MMDB_MIN_SIZE=2000000
|
||||
GEOSITE_MIN_SIZE=2000000
|
||||
|
||||
geoip_source="$(uci -q get clashoo.config.geoip_source 2>/dev/null)"
|
||||
license_key="$(uci -q get clashoo.config.license_key 2>/dev/null)"
|
||||
|
||||
cfg_mmdb_url="$(uci -q get clashoo.config.geoip_mmdb_url 2>/dev/null)"
|
||||
cfg_geosite_url="$(uci -q get clashoo.config.geosite_url 2>/dev/null)"
|
||||
cfg_geoip_dat_url="$(uci -q get clashoo.config.geoip_dat_url 2>/dev/null)"
|
||||
cfg_geoip_asn_url="$(uci -q get clashoo.config.geoip_asn_url 2>/dev/null)"
|
||||
|
||||
DEFAULT_MMDB_URL="https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
|
||||
DEFAULT_GEOSITE_URL="https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat"
|
||||
DEFAULT_GEOIP_DAT_URL="https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geoip.dat"
|
||||
DEFAULT_GEOIP_ASN_URL="https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb"
|
||||
|
||||
LOYALSOLDIER_MMDB_URL="https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
|
||||
LOYALSOLDIER_GEOSITE_URL="https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat"
|
||||
LOYALSOLDIER_GEOIP_DAT_URL="https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat"
|
||||
|
||||
# v2fly ships no Country.mmdb — borrow Loyalsoldier's for the mmdb slot
|
||||
V2FLY_GEOSITE_URL="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat"
|
||||
V2FLY_GEOIP_DAT_URL="https://github.com/v2fly/geoip/releases/latest/download/geoip.dat"
|
||||
|
||||
log() {
|
||||
echo " $(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR" >/dev/null 2>&1
|
||||
rm -f /var/run/geoip_update >/dev/null 2>&1
|
||||
}
|
||||
|
||||
config_needs_geosite() {
|
||||
_cfg="/etc/clashoo/config.yaml"
|
||||
[ -f "$_cfg" ] || return 1
|
||||
grep -Eq "^[[:space:]]*-[[:space:]]*[\"']?GEOSITE,|^[[:space:]]*geosite:[[:space:]]*|^[[:space:]]*'geosite:|^[[:space:]]*-[[:space:]]*[\"']?geosite:" "$_cfg"
|
||||
}
|
||||
|
||||
# Shared port detection (handles smart core + sing-box profile port); keep the
|
||||
# local anonymity probe so we skip a coexisting plugin's auth-locked proxy.
|
||||
. /usr/share/clashoo/update/proxy_lib.sh
|
||||
detect_proxy() {
|
||||
proxy="$(clashoo_detect_proxy)"
|
||||
[ -n "$proxy" ] || return 0
|
||||
# Only use it if it actually proxies anonymously. A coexisting plugin
|
||||
# (e.g. OpenClash on the same 7890) may hold this port behind auth and
|
||||
# answer HTTP 407 — in that case fall through to the mirrors instead.
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsS --proxy "$proxy" --connect-timeout 3 --max-time 5 -o /dev/null \
|
||||
http://www.gstatic.com/generate_204 >/dev/null 2>&1 || return 0
|
||||
fi
|
||||
echo "$proxy"
|
||||
}
|
||||
|
||||
_fetch() {
|
||||
url="$1"; target="$2"; proxy="$3"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if [ -n "$proxy" ]; then
|
||||
curl --proxy "$proxy" -fsSL --connect-timeout 15 --max-time 120 -A "Clash/OpenWRT" "$url" -o "$target"
|
||||
else
|
||||
curl -fsSL --connect-timeout 15 --max-time 120 -A "Clash/OpenWRT" "$url" -o "$target"
|
||||
fi
|
||||
return $?
|
||||
fi
|
||||
wget -q --timeout=120 --no-check-certificate --user-agent="Clash/OpenWRT" "$url" -O "$target"
|
||||
}
|
||||
|
||||
# 多源拉取:本机代理 → 国内镜像(gh-proxy / ghfast / jsdelivr)→ 原 URL 兜底
|
||||
download_to() {
|
||||
url="$1"; target="$2"
|
||||
[ -z "$url" ] && return 1
|
||||
|
||||
proxy="$(detect_proxy)"
|
||||
|
||||
if [ -n "$proxy" ]; then
|
||||
_fetch "$url" "$target" "$proxy" && return 0
|
||||
fi
|
||||
|
||||
case "$url" in
|
||||
https://raw.githubusercontent.com/*)
|
||||
rest="${url#https://raw.githubusercontent.com/}"
|
||||
owner="${rest%%/*}"; r1="${rest#*/}"
|
||||
repo="${r1%%/*}"; r2="${r1#*/}"
|
||||
branch="${r2%%/*}"; path="${r2#*/}"
|
||||
for m in \
|
||||
"https://gh-proxy.com/${url}" \
|
||||
"https://ghfast.top/${url}" \
|
||||
"https://cdn.jsdelivr.net/gh/${owner}/${repo}@${branch}/${path}"; do
|
||||
_fetch "$m" "$target" "" && return 0
|
||||
done
|
||||
;;
|
||||
https://github.com/*)
|
||||
for m in \
|
||||
"https://gh-proxy.com/${url}" \
|
||||
"https://ghfast.top/${url}"; do
|
||||
_fetch "$m" "$target" "" && return 0
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
_fetch "$url" "$target" ""
|
||||
}
|
||||
|
||||
download_optional() {
|
||||
url="$1"
|
||||
target="$2"
|
||||
name="$3"
|
||||
tmp_target="${TMP_DIR}/$(basename "$target").tmp"
|
||||
if [ -z "$url" ]; then
|
||||
log "$name skip (url empty)"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$tmp_target" >/dev/null 2>&1
|
||||
if download_to "$url" "$tmp_target"; then
|
||||
mv -f "$tmp_target" "$target" >/dev/null 2>&1 || return 1
|
||||
chmod 644 "$target" >/dev/null 2>&1
|
||||
log "$name updated"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$tmp_target" >/dev/null 2>&1
|
||||
log "$name update failed"
|
||||
return 0
|
||||
}
|
||||
|
||||
download_geosite() {
|
||||
url="$1"
|
||||
tmp_target="${TMP_DIR}/GeoSite.dat.tmp"
|
||||
size=0
|
||||
if [ -z "$url" ]; then
|
||||
log "GeoSite.dat skip (url empty)"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$tmp_target" >/dev/null 2>&1
|
||||
if ! download_to "$url" "$tmp_target"; then
|
||||
rm -f "$tmp_target" >/dev/null 2>&1
|
||||
log "GeoSite.dat update failed"
|
||||
return 0
|
||||
fi
|
||||
size=$(wc -c <"$tmp_target" 2>/dev/null)
|
||||
if [ -z "$size" ] || [ "$size" -lt "$GEOSITE_MIN_SIZE" ]; then
|
||||
log "GeoSite.dat download invalid or incomplete (${size:-0} bytes)"
|
||||
rm -f "$tmp_target" >/dev/null 2>&1
|
||||
return 0
|
||||
fi
|
||||
mv -f "$tmp_target" /etc/clashoo/GeoSite.dat >/dev/null 2>&1 || return 1
|
||||
cp -f /etc/clashoo/GeoSite.dat /etc/clashoo/geosite.dat >/dev/null 2>&1 || true
|
||||
chmod 644 /etc/clashoo/GeoSite.dat /etc/clashoo/geosite.dat >/dev/null 2>&1
|
||||
log "GeoSite.dat updated"
|
||||
return 0
|
||||
}
|
||||
|
||||
download_mmdb() {
|
||||
url="$1"
|
||||
tmp_target="${TMP_DIR}/Country.mmdb.tmp"
|
||||
size=0
|
||||
rm -f "$tmp_target" >/dev/null 2>&1
|
||||
if ! download_to "$url" "$tmp_target"; then
|
||||
rm -f "$tmp_target" >/dev/null 2>&1
|
||||
return 1
|
||||
fi
|
||||
size=$(wc -c <"$tmp_target" 2>/dev/null)
|
||||
if [ -z "$size" ] || [ "$size" -lt "$MMDB_MIN_SIZE" ]; then
|
||||
log "Country.mmdb download invalid or incomplete (${size:-0} bytes)"
|
||||
rm -f "$tmp_target" >/dev/null 2>&1
|
||||
return 1
|
||||
fi
|
||||
mv -f "$tmp_target" /etc/clashoo/Country.mmdb >/dev/null 2>&1 || return 1
|
||||
chmod 644 /etc/clashoo/Country.mmdb >/dev/null 2>&1
|
||||
return 0
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
mkdir -p "$TMP_DIR" /etc/clashoo >/dev/null 2>&1
|
||||
|
||||
rm -f /var/run/geoip_down_complete >/dev/null 2>&1
|
||||
: > /var/run/geoip_update
|
||||
: > "$LOG_FILE"
|
||||
log "GeoIP 更新任务启动"
|
||||
|
||||
mmdb_url=""
|
||||
geosite_url=""
|
||||
geoip_dat_url=""
|
||||
geoip_asn_url=""
|
||||
|
||||
case "$geoip_source" in
|
||||
1)
|
||||
if [ -z "$license_key" ]; then
|
||||
log "MaxMind source selected but license key is empty"
|
||||
exit 1
|
||||
fi
|
||||
log "Updating Country.mmdb from MaxMind"
|
||||
if ! download_to "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${license_key}&suffix=tar.gz" "$TMP_DIR/geoip.tar.gz"; then
|
||||
log "MaxMind download failed"
|
||||
exit 1
|
||||
fi
|
||||
if ! tar zxf "$TMP_DIR/geoip.tar.gz" -C "$TMP_DIR" >/dev/null 2>&1; then
|
||||
log "MaxMind archive extract failed"
|
||||
exit 1
|
||||
fi
|
||||
mmdb_file="$(ls "$TMP_DIR"/GeoLite2-Country_*/GeoLite2-Country.mmdb 2>/dev/null | head -n 1)"
|
||||
if [ -z "$mmdb_file" ] || [ ! -f "$mmdb_file" ]; then
|
||||
log "MaxMind Country.mmdb not found in archive"
|
||||
exit 1
|
||||
fi
|
||||
cp -f "$mmdb_file" /etc/clashoo/Country.mmdb
|
||||
chmod 644 /etc/clashoo/Country.mmdb >/dev/null 2>&1
|
||||
log "Country.mmdb updated"
|
||||
;;
|
||||
3)
|
||||
# Loyalsoldier (default)
|
||||
mmdb_url="$LOYALSOLDIER_MMDB_URL"
|
||||
geosite_url="$LOYALSOLDIER_GEOSITE_URL"
|
||||
geoip_dat_url="$LOYALSOLDIER_GEOIP_DAT_URL"
|
||||
geoip_asn_url="$DEFAULT_GEOIP_ASN_URL"
|
||||
;;
|
||||
5)
|
||||
# v2fly (mmdb borrowed from Loyalsoldier)
|
||||
mmdb_url="$LOYALSOLDIER_MMDB_URL"
|
||||
geosite_url="$V2FLY_GEOSITE_URL"
|
||||
geoip_dat_url="$V2FLY_GEOIP_DAT_URL"
|
||||
geoip_asn_url="$DEFAULT_GEOIP_ASN_URL"
|
||||
;;
|
||||
4)
|
||||
# custom — asn falls back to default when left blank
|
||||
mmdb_url="$cfg_mmdb_url"
|
||||
geosite_url="$cfg_geosite_url"
|
||||
geoip_dat_url="$cfg_geoip_dat_url"
|
||||
geoip_asn_url="${cfg_geoip_asn_url:-$DEFAULT_GEOIP_ASN_URL}"
|
||||
;;
|
||||
*)
|
||||
mmdb_url="${cfg_mmdb_url:-$DEFAULT_MMDB_URL}"
|
||||
geosite_url="${cfg_geosite_url:-$DEFAULT_GEOSITE_URL}"
|
||||
geoip_dat_url="${cfg_geoip_dat_url:-$DEFAULT_GEOIP_DAT_URL}"
|
||||
geoip_asn_url="${cfg_geoip_asn_url:-$DEFAULT_GEOIP_ASN_URL}"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$geoip_source" != "1" ]; then
|
||||
log "Updating Country.mmdb"
|
||||
if ! download_mmdb "$mmdb_url"; then
|
||||
log "Country.mmdb download failed"
|
||||
exit 1
|
||||
fi
|
||||
log "Country.mmdb updated"
|
||||
|
||||
if config_needs_geosite; then
|
||||
download_geosite "$geosite_url"
|
||||
else
|
||||
log "GeoSite.dat skip (no geosite reference in config)"
|
||||
fi
|
||||
download_optional "$geoip_dat_url" /etc/clashoo/geoip.dat "geoip.dat"
|
||||
download_optional "$geoip_asn_url" /etc/clashoo/GeoLite2-ASN.mmdb "GeoLite2-ASN.mmdb"
|
||||
fi
|
||||
|
||||
touch /var/run/geoip_down_complete >/dev/null 2>&1
|
||||
|
||||
if pidof mihomo >/dev/null 2>&1 || pidof clash-meta >/dev/null 2>&1; then
|
||||
log "GeoIP update completed, apply on next Clashoo restart"
|
||||
fi
|
||||
|
||||
log "GeoIP update completed"
|
||||
exit 0
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
MODEL_PATH="/etc/clashoo/Model.bin"
|
||||
TMP_PATH="/tmp/clash_Model.bin"
|
||||
LOG_FILE="/tmp/lgbm_update.log"
|
||||
|
||||
log() {
|
||||
echo " $(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
: > "$LOG_FILE"
|
||||
|
||||
DOWNLOAD_URL=$(uci get clashoo.config.smart_lgbm_url 2>/dev/null)
|
||||
[ -z "$DOWNLOAD_URL" ] && DOWNLOAD_URL="https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model.bin"
|
||||
|
||||
log "Start downloading LightGBM model from: $DOWNLOAD_URL"
|
||||
|
||||
mkdir -p /etc/clashoo
|
||||
|
||||
rm -f "$TMP_PATH" 2>/dev/null
|
||||
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
|
||||
dl_rc=$?
|
||||
else
|
||||
log "No curl or wget found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$dl_rc" -ne 0 ] || [ ! -s "$TMP_PATH" ]; then
|
||||
log "Download failed (rc=$dl_rc)"
|
||||
rm -f "$TMP_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$MODEL_PATH" ] && cmp -s "$TMP_PATH" "$MODEL_PATH"; then
|
||||
log "Model unchanged, no update needed"
|
||||
rm -f "$TMP_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mv "$TMP_PATH" "$MODEL_PATH" 2>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
log "Failed to install model to $MODEL_PATH"
|
||||
rm -f "$TMP_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "LightGBM model updated successfully"
|
||||
exit 0
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
STATE_DIR="${CLASHOO_STATE_DIR:-/usr/share/clashbackup}"
|
||||
UPDATE_SCRIPT="${CLASHOO_UPDATE_SCRIPT:-/usr/share/clashoo/update/update_all.sh}"
|
||||
LOG_FILE="${CLASHOO_LOG_FILE:-/usr/share/clashoo/clashoo.txt}"
|
||||
LOCK_DIR="${CLASHOO_LOCK_DIR:-/tmp/clashoo_maintenance.lock}"
|
||||
NOW="${CLASHOO_NOW:-$(date +%s)}"
|
||||
|
||||
stamp() {
|
||||
tmp="$STATE_DIR/$1.tmp.$$"
|
||||
printf 'last_run=%s\n' "$NOW" >"$tmp" && mv -f "$tmp" "$STATE_DIR/$1"
|
||||
}
|
||||
|
||||
due() {
|
||||
hours="$(uci -q get "clashoo.config.$2")"
|
||||
echo "$hours" | grep -Eq '^[0-9]+$' && [ "$hours" -ge 1 ] || hours=12
|
||||
last="$(sed -n 's/^last_run=//p' "$STATE_DIR/$1" 2>/dev/null | head -1)"
|
||||
if ! echo "$last" | grep -Eq '^[0-9]+$' || [ "$last" -gt "$NOW" ]; then
|
||||
stamp "$1"
|
||||
return 1
|
||||
fi
|
||||
[ $((NOW - last)) -ge $((hours * 3600)) ]
|
||||
}
|
||||
|
||||
mkdir -p "$STATE_DIR" || exit 0
|
||||
mkdir "$LOCK_DIR" 2>/dev/null || exit 0
|
||||
trap 'rmdir "$LOCK_DIR" >/dev/null 2>&1' EXIT INT TERM
|
||||
|
||||
if [ "$(uci -q get clashoo.config.auto_update)" = "1" ] && due rule_update.status auto_update_time; then
|
||||
CLASHOO_STATE_DIR="$STATE_DIR" sh "$UPDATE_SCRIPT" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
if [ "$(uci -q get clashoo.config.auto_clear_log)" = "1" ] && due log_cleanup.status clear_time; then
|
||||
: >"$LOG_FILE"
|
||||
stamp log_cleanup.status
|
||||
fi
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
REAL_LOG="/usr/share/clashoo/clashoo_real.txt"
|
||||
panel="$1"
|
||||
lang="$(uci -q get luci.main.lang 2>/dev/null)"
|
||||
STATE_FILE="/tmp/clash_panel_download_state"
|
||||
RUN_FILE="/var/run/panel_downloading"
|
||||
DASHBOARD_LINK="/etc/clashoo/dashboard"
|
||||
|
||||
[ -n "$panel" ] || panel="$(uci -q get clashoo.config.dashboard_panel 2>/dev/null)"
|
||||
[ -n "$panel" ] || panel="zashboard"
|
||||
|
||||
case "$panel" in
|
||||
metacubexd)
|
||||
URLS="https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
|
||||
TARGET_DIR="/etc/clashoo/dashboard-metacubexd"
|
||||
;;
|
||||
yacd)
|
||||
URLS="https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip https://github.com/haishanh/yacd/archive/refs/heads/gh-pages.zip"
|
||||
TARGET_DIR="/etc/clashoo/dashboard-yacd"
|
||||
;;
|
||||
zashboard)
|
||||
URLS="https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip"
|
||||
TARGET_DIR="/etc/clashoo/dashboard-zashboard"
|
||||
;;
|
||||
razord)
|
||||
URLS="https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip https://github.com/ayanamist/clash-dashboard/archive/refs/heads/gh-pages.zip"
|
||||
TARGET_DIR="/etc/clashoo/dashboard-razord"
|
||||
;;
|
||||
*)
|
||||
URLS="https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip"
|
||||
TARGET_DIR="/etc/clashoo/dashboard-zashboard"
|
||||
panel="zashboard"
|
||||
;;
|
||||
esac
|
||||
|
||||
TMP_ROOT="/tmp/clash_panel_${panel}_$$"
|
||||
ZIP_FILE="$TMP_ROOT/panel.zip"
|
||||
UNPACK_DIR="$TMP_ROOT/unpack"
|
||||
|
||||
UPDATE_LOG="/tmp/clash_update.txt"
|
||||
|
||||
update_log() {
|
||||
[ -n "$1" ] || return 0
|
||||
printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$UPDATE_LOG"
|
||||
}
|
||||
|
||||
log_msg() {
|
||||
if [ "$lang" = "en" ]; then
|
||||
echo "$1" >"$REAL_LOG"
|
||||
[ "$3" = "1" ] || update_log "$1"
|
||||
else
|
||||
echo "$2" >"$REAL_LOG"
|
||||
[ "$3" = "1" ] || update_log "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
set_state() {
|
||||
state="$1"
|
||||
msg="$2"
|
||||
echo "${state}:${panel}:${msg}" >"$STATE_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_ROOT" >/dev/null 2>&1
|
||||
rm -f "$RUN_FILE" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Route the panel download through the running core in kernel-only mode
|
||||
# (shared logic in proxy_lib.sh). Normal mode returns empty -> TPROXY.
|
||||
. /usr/share/clashoo/update/proxy_lib.sh
|
||||
detect_proxy() { clashoo_detect_proxy; }
|
||||
|
||||
download_zip() {
|
||||
proxy="$(detect_proxy)"
|
||||
for url in $URLS; do
|
||||
if [ -n "$proxy" ] && command -v curl >/dev/null 2>&1 &&
|
||||
curl -fsSL --connect-timeout 15 --max-time 300 --retry 1 \
|
||||
--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
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
extract_zip() {
|
||||
if command -v unzip >/dev/null 2>&1; then
|
||||
unzip -oq "$ZIP_FILE" -d "$UNPACK_DIR" >/dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v busybox >/dev/null 2>&1 && busybox --list 2>/dev/null | grep -qx "unzip"; then
|
||||
busybox unzip -oq "$ZIP_FILE" -d "$UNPACK_DIR" >/dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v bsdtar >/dev/null 2>&1; then
|
||||
bsdtar -xf "$ZIP_FILE" -C "$UNPACK_DIR" >/dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
return 2
|
||||
}
|
||||
|
||||
find_web_root() {
|
||||
if [ -f "$UNPACK_DIR/index.html" ]; then
|
||||
echo "$UNPACK_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
index_file="$(find "$UNPACK_DIR" -type f -name index.html 2>/dev/null | head -n 1)"
|
||||
if [ -n "$index_file" ]; then
|
||||
dirname "$index_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
activate_panel() {
|
||||
rm -rf "$DASHBOARD_LINK" >/dev/null 2>&1
|
||||
ln -s "$TARGET_DIR" "$DASHBOARD_LINK" >/dev/null 2>&1 || cp -a "$TARGET_DIR" "$DASHBOARD_LINK" >/dev/null 2>&1
|
||||
rm -rf /www/luci-static/yacd >/dev/null 2>&1
|
||||
if [ "$panel" = "yacd" ]; then
|
||||
ln -s "$TARGET_DIR" /www/luci-static/yacd >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
mkdir -p "$UNPACK_DIR" "$TARGET_DIR" >/dev/null 2>&1
|
||||
touch "$RUN_FILE"
|
||||
set_state "downloading" "开始下载"
|
||||
|
||||
log_msg "Downloading dashboard panel..." "正在下载面板..."
|
||||
if ! download_zip; then
|
||||
set_state "error" "下载失败"
|
||||
log_msg "Dashboard panel download failed" "面板下载失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
extract_zip
|
||||
extract_rc=$?
|
||||
if [ "$extract_rc" -ne 0 ]; then
|
||||
if [ "$extract_rc" -eq 2 ]; then
|
||||
set_state "error" "缺少解压工具"
|
||||
log_msg "Panel unzip tool not found" "缺少解压工具,无法安装面板"
|
||||
else
|
||||
set_state "error" "解压失败"
|
||||
log_msg "Dashboard panel unzip failed" "面板解压失败"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SRC_DIR="$(find_web_root)"
|
||||
if [ -z "$SRC_DIR" ] || [ ! -f "$SRC_DIR/index.html" ]; then
|
||||
set_state "error" "文件无效"
|
||||
log_msg "Dashboard panel content invalid" "面板文件无效"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$TARGET_DIR"/* >/dev/null 2>&1
|
||||
cp -a "$SRC_DIR"/. "$TARGET_DIR"/ >/dev/null 2>&1
|
||||
|
||||
activate_panel
|
||||
|
||||
uci set clashoo.config.dashboard_panel="$panel" >/dev/null 2>&1
|
||||
uci commit clashoo >/dev/null 2>&1
|
||||
|
||||
set_state "success" "安装成功"
|
||||
log_msg "Dashboard panel installed" "面板安装完成"
|
||||
sleep 1
|
||||
log_msg "Clashoo" "Clashoo" 1
|
||||
exit 0
|
||||
@@ -1,80 +0,0 @@
|
||||
# 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
|
||||
# downloads, subscriptions) and health checks must reach GitHub through the
|
||||
# running core, otherwise they leak out direct and stall behind the GFW. Route
|
||||
# them through the core's mixed port whenever a core is running — in BOTH modes:
|
||||
# - core-only mode deliberately skips TPROXY to coexist with other plugins;
|
||||
# - normal mode used to rely on transparent redirect, but the router's OWN
|
||||
# output redirect is fragile (e.g. PPPoE/GL.iNet stacks where Redirect to
|
||||
# loopback never completes — issue #25), so don't depend on it. The explicit
|
||||
# mixed port works on every network stack.
|
||||
# When no core is running (e.g. bootstrapping the very first core download),
|
||||
# returns empty so the caller falls back to direct / mirror sources.
|
||||
#
|
||||
# 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 -> 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)
|
||||
# uci mixed_port is the last-resort fallback for both.
|
||||
clashoo_running_core_type() {
|
||||
_cdp_type="$(uci -q get clashoo.config.core_type 2>/dev/null)"
|
||||
case "$_cdp_type" in
|
||||
singbox)
|
||||
_cdp_service="clashoo"
|
||||
_cdp_filter='@.clashoo.instances.singbox.pid'
|
||||
;;
|
||||
*)
|
||||
_cdp_type="mihomo"
|
||||
_cdp_service="clashoo"
|
||||
_cdp_filter='@.clashoo.instances.*.pid'
|
||||
;;
|
||||
esac
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
clashoo_detect_proxy() {
|
||||
_cdp_type="$(clashoo_running_core_type)" || return 0
|
||||
_cdp_port=""
|
||||
if [ "$_cdp_type" = "singbox" ]; then
|
||||
_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
|
||||
# sed would return whichever appears first by line order, so loop by key
|
||||
for _cdp_key in mixed-port port socks-port; do
|
||||
_cdp_port="$(sed -n "s/^[[:space:]]*${_cdp_key}:[[:space:]]*\([0-9][0-9]*\).*/\1/p" \
|
||||
/etc/clashoo/config.yaml 2>/dev/null | head -n 1)"
|
||||
[ -n "$_cdp_port" ] && break
|
||||
done
|
||||
fi
|
||||
[ -n "$_cdp_port" ] || _cdp_port="$(uci -q get clashoo.config.mixed_port 2>/dev/null)"
|
||||
[ -n "$_cdp_port" ] || return 0
|
||||
printf 'http://127.0.0.1:%s' "$_cdp_port"
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
SUB_DIR="${CLASHOO_SUB_DIR:-/usr/share/clashoo/config/sub}"
|
||||
SINGBOX_DIR="${CLASHOO_SINGBOX_DIR:-/usr/share/clashoo/config/singbox}"
|
||||
BACKUP_DIR="${CLASHOO_BACKUP_DIR:-/usr/share/clashbackup}"
|
||||
TEMPLATE_DIR="${CLASHOO_TEMPLATE_DIR:-/usr/share/clashoo/config/custom}"
|
||||
LIST_FILE="${CLASHOO_LIST_FILE:-$BACKUP_DIR/confit_list.conf}"
|
||||
BIND_FILE="${CLASHOO_BIND_FILE:-$BACKUP_DIR/template_bindings.conf}"
|
||||
STATUS_FILE="${CLASHOO_STATUS_FILE:-$BACKUP_DIR/subscription_update.status}"
|
||||
LOCK_DIR="${CLASHOO_LOCK_DIR:-/tmp/clashoo_subscription_update.lock}"
|
||||
UPDATE_LOG="${CLASHOO_UPDATE_LOG:-/tmp/clash_update.txt}"
|
||||
SERVICE_CMD="${CLASHOO_SERVICE_CMD:-/etc/init.d/clashoo}"
|
||||
TMP_DIR="${CLASHOO_TMP_DIR:-/tmp}"
|
||||
DEFAULT_SUB_UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
|
||||
DOWNLOAD_ERROR=""
|
||||
|
||||
# Subscriptions are fetched direct first (many airports are domestic / geo-fence
|
||||
# foreign exit IPs). Only used as a last-resort fallback, see download_to.
|
||||
. /usr/share/clashoo/update/proxy_lib.sh
|
||||
|
||||
updated=0
|
||||
unchanged=0
|
||||
failed=0
|
||||
skipped=0
|
||||
restart_needed=0
|
||||
started_at="$(date +%s)"
|
||||
message=""
|
||||
record_status=0
|
||||
[ "$1" = "--all" ] && record_status=1
|
||||
|
||||
log_update() {
|
||||
printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$UPDATE_LOG"
|
||||
}
|
||||
|
||||
write_status() {
|
||||
local tmp
|
||||
mkdir -p "$(dirname "$STATUS_FILE")" >/dev/null 2>&1
|
||||
tmp="${STATUS_FILE}.tmp.$$"
|
||||
cat >"$tmp" <<EOF
|
||||
running=$1
|
||||
last_run=$started_at
|
||||
finished_at=$2
|
||||
updated=$updated
|
||||
unchanged=$unchanged
|
||||
failed=$failed
|
||||
skipped=$skipped
|
||||
message=$message
|
||||
EOF
|
||||
mv "$tmp" "$STATUS_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$LOCK_DIR" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
if ! mkdir "$LOCK_DIR" >/dev/null 2>&1; then
|
||||
exit 75
|
||||
fi
|
||||
trap cleanup EXIT INT TERM
|
||||
[ "$record_status" = "1" ] && write_status 1 0
|
||||
|
||||
safe_name() {
|
||||
case "$1" in
|
||||
''|*/*|*..*) return 1 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
extract_host() {
|
||||
printf '%s' "$1" | sed -e 's#^[a-zA-Z0-9+.-]*://##' -e 's#/.*$##' -e 's#:.*$##' -e 's#.*@##'
|
||||
}
|
||||
|
||||
resolve_via() {
|
||||
local host dns
|
||||
host="$1"
|
||||
dns="$2"
|
||||
nslookup "$host" "$dns" 2>/dev/null | awk '
|
||||
/^Address/ {
|
||||
ip = $NF
|
||||
if (ip ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ &&
|
||||
ip !~ /^127\./ && ip !~ /^0\./ && ip !~ /^198\.18\./ &&
|
||||
ip != "8.8.8.8" && ip != "1.1.1.1" &&
|
||||
ip != "223.5.5.5" && ip != "119.29.29.29") {
|
||||
print ip
|
||||
exit
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
curl_download() {
|
||||
local url out hdr ua host ip
|
||||
url="$1"
|
||||
out="$2"
|
||||
hdr="$3"
|
||||
ua="$4"
|
||||
host="$5"
|
||||
ip="$6"
|
||||
if [ -n "$host" ] && [ -n "$ip" ]; then
|
||||
curl -sSL --connect-timeout 15 --max-time 60 --speed-time 30 \
|
||||
--speed-limit 1 --retry 2 -A "$ua" -D "$hdr" -o "$out" \
|
||||
--resolve "$host:443:$ip" --resolve "$host:80:$ip" \
|
||||
-w '%{http_code}' "$url" 2>/dev/null
|
||||
else
|
||||
curl -sSL --connect-timeout 15 --max-time 60 --speed-time 30 \
|
||||
--speed-limit 1 --retry 2 -A "$ua" -D "$hdr" -o "$out" \
|
||||
-w '%{http_code}' "$url" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
subscription_ua() {
|
||||
local target ua
|
||||
target="$1"
|
||||
ua=""
|
||||
[ -r "${target}.ua" ] && ua="$(sed -n '1p' "${target}.ua" 2>/dev/null)"
|
||||
[ -n "$ua" ] || ua="$(uci -q get clashoo.config.sub_ua 2>/dev/null)"
|
||||
[ -n "$ua" ] || ua="$DEFAULT_SUB_UA"
|
||||
printf '%s' "$ua"
|
||||
}
|
||||
|
||||
save_subscription_ua() {
|
||||
printf '%s\n' "$2" >"${1}.ua"
|
||||
}
|
||||
|
||||
curl_error() {
|
||||
case "$1" in
|
||||
6) printf '%s' "DNS 解析失败" ;;
|
||||
7) printf '%s' "连接失败" ;;
|
||||
28) printf '%s' "连接超时" ;;
|
||||
35) printf '%s' "TLS 握手失败" ;;
|
||||
60) printf '%s' "证书校验失败" ;;
|
||||
*) printf '网络错误(curl rc=%s)' "${1:-unknown}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
download_to() {
|
||||
local url out hdr ua code rc last_rc host dns ip proxy
|
||||
url="$1"
|
||||
out="$2"
|
||||
hdr="$3"
|
||||
ua="$4"
|
||||
[ -n "$ua" ] || ua="$DEFAULT_SUB_UA"
|
||||
DOWNLOAD_ERROR=""
|
||||
|
||||
rm -f "$out" "$hdr" >/dev/null 2>&1
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
code="$(curl_download "$url" "$out" "$hdr" "$ua" "" "")"
|
||||
rc=$?
|
||||
if [ "$rc" -eq 0 ] && [ "$code" = "200" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$rc" -eq 0 ] && [ -n "$code" ] && [ "$code" != "000" ]; then
|
||||
DOWNLOAD_ERROR="HTTP ${code}"
|
||||
return 1
|
||||
fi
|
||||
last_rc="$rc"
|
||||
host="$(extract_host "$url")"
|
||||
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})"
|
||||
code="$(curl_download "$url" "$out" "$hdr" "$ua" "$host" "$ip")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] && [ "$code" = "200" ] && return 0
|
||||
if [ "$rc" -eq 0 ] && [ -n "$code" ] && [ "$code" != "000" ]; then
|
||||
DOWNLOAD_ERROR="HTTP ${code}"
|
||||
return 1
|
||||
fi
|
||||
last_rc="$rc"
|
||||
done
|
||||
# Last resort: direct + DNS-override both failed, so the source may be
|
||||
# GFW-blocked (e.g. a github-hosted sub). In kernel-only mode try once
|
||||
# through the running core — nothing left to lose, and it won't disturb
|
||||
# any airport that already answered direct.
|
||||
proxy="$(clashoo_detect_proxy)"
|
||||
if [ -n "$proxy" ]; then
|
||||
log_update "代理兜底:通过本地核心重试订阅"
|
||||
code="$(curl -sSL --connect-timeout 15 --max-time 60 --speed-time 30 \
|
||||
--speed-limit 1 --retry 1 -A "$ua" -D "$hdr" -o "$out" \
|
||||
--proxy "$proxy" -w '%{http_code}' "$url" 2>/dev/null)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] && [ "$code" = "200" ] && return 0
|
||||
if [ "$rc" -eq 0 ] && [ -n "$code" ] && [ "$code" != "000" ]; then
|
||||
DOWNLOAD_ERROR="HTTP ${code}"
|
||||
return 1
|
||||
fi
|
||||
last_rc="$rc"
|
||||
fi
|
||||
DOWNLOAD_ERROR="$(curl_error "$last_rc")"
|
||||
return 1
|
||||
fi
|
||||
wget -q --tries=4 --timeout=20 --user-agent="$ua" "$url" -O "$out"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] && return 0
|
||||
DOWNLOAD_ERROR="网络错误(wget rc=${rc})"
|
||||
return 1
|
||||
}
|
||||
|
||||
update_info() {
|
||||
local hdr target info
|
||||
hdr="$1"
|
||||
target="$2"
|
||||
info="$(grep -i 'subscription-userinfo:' "$hdr" 2>/dev/null | head -1 | \
|
||||
sed 's/^[Ss]ubscription-[Uu]serinfo:[[:space:]]*//' | tr -d '\r')"
|
||||
if [ -n "$info" ]; then
|
||||
printf '%s\n' "$info" >"${target}.info"
|
||||
else
|
||||
rm -f "${target}.info" >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
service_running() {
|
||||
[ -n "${CLASHOO_SERVICE_CMD:-}" ] && return 0
|
||||
"$SERVICE_CMD" status >/dev/null 2>&1
|
||||
}
|
||||
|
||||
template_output_name() {
|
||||
local sub tpl
|
||||
sub="$(printf '%s' "$1" | sed -e 's/\.[Yy][Aa][Mm][Ll]$//' -e 's/\.[Yy][Mm][Ll]$//' -e 's/[^A-Za-z0-9._-]/-/g')"
|
||||
tpl="$(printf '%s' "$2" | sed -e 's/\.[Yy][Aa][Mm][Ll]$//' -e 's/\.[Yy][Mm][Ll]$//' -e 's/[^A-Za-z0-9._-]/-/g')"
|
||||
printf '_merged_%s__%s.yaml' "${sub:-sub}" "${tpl:-template}"
|
||||
}
|
||||
|
||||
apply_template() {
|
||||
local name target template merged merged_path use_config
|
||||
name="$1"
|
||||
target="$2"
|
||||
[ -r "$BIND_FILE" ] || return 0
|
||||
[ -x /usr/share/clashoo/update/template_merge.sh ] || return 0
|
||||
template="$(awk -F '#' -v n="$name" '$1==n && ($3=="1" || $3=="true") {print $2; exit}' "$BIND_FILE")"
|
||||
[ -n "$template" ] && [ -r "$TEMPLATE_DIR/$template" ] || return 0
|
||||
merged="$(template_output_name "$name" "$template")"
|
||||
merged_path="$TEMPLATE_DIR/$merged"
|
||||
if sh /usr/share/clashoo/update/template_merge.sh "$target" "$TEMPLATE_DIR/$template" "$merged_path" >/dev/null 2>&1; then
|
||||
use_config="$(uci -q get clashoo.config.use_config 2>/dev/null)"
|
||||
if [ "$use_config" = "$target" ] || [ "$use_config" = "$merged_path" ]; then
|
||||
uci -q set clashoo.config.use_config="$merged_path"
|
||||
uci -q set clashoo.config.config_type='3'
|
||||
uci -q commit clashoo >/dev/null 2>&1
|
||||
restart_needed=1
|
||||
fi
|
||||
else
|
||||
log_update "模板生成失败:${name} <- ${template}"
|
||||
fi
|
||||
}
|
||||
|
||||
update_mihomo() {
|
||||
local name url typ target tmp hdr ua use_config config_type
|
||||
name="$1"
|
||||
url="$2"
|
||||
typ="$3"
|
||||
safe_name "$name" || return 1
|
||||
case "$typ" in clash|meta) ;; *) skipped=$((skipped + 1)); return 0 ;; esac
|
||||
target="$SUB_DIR/$name"
|
||||
[ -f "$target" ] || { skipped=$((skipped + 1)); return 0; }
|
||||
tmp="$TMP_DIR/clashoo_sub_$$.yaml"
|
||||
hdr="$TMP_DIR/clashoo_sub_$$.hdr"
|
||||
ua="$(subscription_ua "$target")"
|
||||
if ! download_to "$url" "$tmp" "$hdr" "$ua"; then
|
||||
failed=$((failed + 1))
|
||||
log_update "更新失败(${DOWNLOAD_ERROR:-下载失败}):$name"
|
||||
rm -f "$tmp" "$hdr"
|
||||
return 1
|
||||
fi
|
||||
if ! grep -Eq '^(proxies|proxy-providers):' "$tmp" 2>/dev/null; then
|
||||
failed=$((failed + 1))
|
||||
log_update "更新失败(无效 Mihomo 配置):$name"
|
||||
rm -f "$tmp" "$hdr"
|
||||
return 1
|
||||
fi
|
||||
if cmp -s "$tmp" "$target"; then
|
||||
update_info "$hdr" "$target"
|
||||
save_subscription_ua "$target" "$ua"
|
||||
unchanged=$((unchanged + 1))
|
||||
rm -f "$tmp" "$hdr"
|
||||
return 0
|
||||
fi
|
||||
if ! mv "$tmp" "$target"; then
|
||||
failed=$((failed + 1))
|
||||
rm -f "$hdr"
|
||||
return 1
|
||||
fi
|
||||
update_info "$hdr" "$target"
|
||||
save_subscription_ua "$target" "$ua"
|
||||
rm -f "$hdr"
|
||||
updated=$((updated + 1))
|
||||
use_config="$(uci -q get clashoo.config.use_config 2>/dev/null)"
|
||||
config_type="$(uci -q get clashoo.config.config_type 2>/dev/null)"
|
||||
[ "$config_type" = "1" ] && [ "$use_config" = "$target" ] && restart_needed=1
|
||||
apply_template "$name" "$target"
|
||||
log_update "更新完成:$name"
|
||||
}
|
||||
|
||||
valid_singbox() {
|
||||
ucode -e 'import { readfile } from "fs"; let c = json(readfile(ARGV[0])); exit(type(c) == "object" && type(c.outbounds) == "array" && length(c.outbounds) > 0 ? 0 : 1);' "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
update_singbox() {
|
||||
local name target url tmp hdr ua active
|
||||
name="$1"
|
||||
safe_name "$name" || return 1
|
||||
target="$SINGBOX_DIR/$name"
|
||||
[ -f "$target" ] && [ -r "$target.url" ] || { skipped=$((skipped + 1)); return 0; }
|
||||
url="$(sed -n '1p' "$target.url")"
|
||||
[ -n "$url" ] || { skipped=$((skipped + 1)); return 0; }
|
||||
tmp="$TMP_DIR/clashoo_sb_$$.json"
|
||||
hdr="$TMP_DIR/clashoo_sb_$$.hdr"
|
||||
ua="$(subscription_ua "$target")"
|
||||
if ! download_to "$url" "$tmp" "$hdr" "$ua"; then
|
||||
failed=$((failed + 1))
|
||||
log_update "更新失败(${DOWNLOAD_ERROR:-下载失败}):$name"
|
||||
rm -f "$tmp" "$hdr"
|
||||
return 1
|
||||
fi
|
||||
if ! valid_singbox "$tmp"; then
|
||||
failed=$((failed + 1))
|
||||
log_update "更新失败(无效 sing-box 配置):$name"
|
||||
rm -f "$tmp" "$hdr"
|
||||
return 1
|
||||
fi
|
||||
if cmp -s "$tmp" "$target"; then
|
||||
update_info "$hdr" "$target"
|
||||
save_subscription_ua "$target" "$ua"
|
||||
unchanged=$((unchanged + 1))
|
||||
rm -f "$tmp" "$hdr"
|
||||
return 0
|
||||
fi
|
||||
if ! mv "$tmp" "$target"; then
|
||||
failed=$((failed + 1))
|
||||
rm -f "$hdr"
|
||||
return 1
|
||||
fi
|
||||
update_info "$hdr" "$target"
|
||||
save_subscription_ua "$target" "$ua"
|
||||
rm -f "$hdr"
|
||||
updated=$((updated + 1))
|
||||
active="$(uci -q get clashoo.config.singbox_active 2>/dev/null)"
|
||||
[ "$active" = "$name" ] && restart_needed=1
|
||||
log_update "更新完成:$name"
|
||||
}
|
||||
|
||||
update_all() {
|
||||
local name url typ url_file
|
||||
if [ -r "$LIST_FILE" ]; then
|
||||
while IFS='#' read -r name url typ _rest; do
|
||||
[ -n "$name" ] && [ -n "$url" ] || continue
|
||||
update_mihomo "$name" "$url" "$typ" || true
|
||||
done <"$LIST_FILE"
|
||||
fi
|
||||
for url_file in "$SINGBOX_DIR"/*.url; do
|
||||
[ -f "$url_file" ] || continue
|
||||
name="$(basename "$url_file" .url)"
|
||||
update_singbox "$name" || true
|
||||
done
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
--all)
|
||||
update_all
|
||||
;;
|
||||
--mihomo)
|
||||
line="$(awk -F '#' -v n="$2" '$1==n {print; exit}' "$LIST_FILE" 2>/dev/null)"
|
||||
[ -n "$line" ] || { failed=1; message="subscription not found"; }
|
||||
if [ -n "$line" ]; then
|
||||
name="$(printf '%s' "$line" | awk -F '#' '{print $1}')"
|
||||
url="$(printf '%s' "$line" | awk -F '#' '{print $2}')"
|
||||
typ="$(printf '%s' "$line" | awk -F '#' '{print $3}')"
|
||||
update_mihomo "$name" "$url" "$typ" || true
|
||||
fi
|
||||
;;
|
||||
--singbox)
|
||||
update_singbox "$2" || true
|
||||
;;
|
||||
*)
|
||||
failed=1
|
||||
message="invalid arguments"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$restart_needed" = "1" ] && service_running; then
|
||||
"$SERVICE_CMD" restart >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
finished_at="$(date +%s)"
|
||||
[ -n "$message" ] || message="updated=$updated unchanged=$unchanged failed=$failed skipped=$skipped"
|
||||
[ "$record_status" = "1" ] && write_status 0 "$finished_at"
|
||||
[ "$failed" -eq 0 ]
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
STATUS_FILE="${CLASHOO_STATUS_FILE:-/usr/share/clashbackup/subscription_update.status}"
|
||||
UPDATER="${CLASHOO_SUBSCRIPTION_UPDATER:-/usr/share/clashoo/update/subscription_update.sh}"
|
||||
SERVICE_CMD="${CLASHOO_SERVICE_CMD:-/etc/init.d/clashoo}"
|
||||
interval="$(uci -q get clashoo.config.subscription_update_interval 2>/dev/null)"
|
||||
now="$(date +%s)"
|
||||
last=0
|
||||
|
||||
[ "$(uci -q get clashoo.config.auto_subscription_update 2>/dev/null)" = "1" ] || exit 0
|
||||
"$SERVICE_CMD" status >/dev/null 2>&1 || exit 0
|
||||
echo "$interval" | grep -Eq '^[0-9]+$' || interval=72
|
||||
[ "$interval" -gt 0 ] || interval=72
|
||||
[ -r "$STATUS_FILE" ] && last="$(sed -n 's/^last_run=//p' "$STATUS_FILE" | head -1)"
|
||||
echo "$last" | grep -Eq '^[0-9]+$' || last=0
|
||||
|
||||
[ $((now - last)) -ge $((interval * 3600)) ] || exit 0
|
||||
sh "$UPDATER" --all
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
SUB_FILE="${1:-}"
|
||||
TEMPLATE_FILE="${2:-}"
|
||||
OUT_FILE="${3:-}"
|
||||
TMP_FILE="/tmp/clash_template_merge_$$.yaml"
|
||||
TMP_FILE2="${TMP_FILE}.norm"
|
||||
LOG_FILE="/tmp/clash_update.txt"
|
||||
|
||||
log() {
|
||||
printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$LOG_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -f "$TMP_FILE" "$TMP_FILE2" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
[ -n "$SUB_FILE" ] || { log "模板复写失败:缺少订阅文件参数"; exit 1; }
|
||||
[ -n "$TEMPLATE_FILE" ] || { log "模板复写失败:缺少模板文件参数"; exit 1; }
|
||||
[ -n "$OUT_FILE" ] || { log "模板复写失败:缺少输出文件参数"; exit 1; }
|
||||
|
||||
[ -f "$SUB_FILE" ] || { log "模板复写失败:订阅文件不存在"; exit 1; }
|
||||
[ -f "$TEMPLATE_FILE" ] || { log "模板复写失败:模板文件不存在"; exit 1; }
|
||||
|
||||
if ! command -v yq >/dev/null 2>&1; then
|
||||
log "模板复写失败:缺少 yq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
core_bin=""
|
||||
for b in mihomo clash-meta clash; do
|
||||
if command -v "$b" >/dev/null 2>&1; then
|
||||
core_bin="$b"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$(dirname "$OUT_FILE")" >/dev/null 2>&1
|
||||
|
||||
log "开始模板复写:$(basename "$SUB_FILE") <- $(basename "$TEMPLATE_FILE")"
|
||||
export SUB_FILE TEMPLATE_FILE
|
||||
|
||||
# template is base; load() forces single-doc; explode anchors first (yq 4.53+ breaks "<<")
|
||||
yq -n '
|
||||
(load(strenv(SUB_FILE)) | explode(.)) as $sub |
|
||||
(load(strenv(TEMPLATE_FILE)) | explode(.)) as $tpl |
|
||||
(
|
||||
($tpl * {
|
||||
"proxies": (((($tpl.proxies // []) + ($sub.proxies // [])) | unique_by(.name))),
|
||||
"proxy-providers": (($tpl."proxy-providers" // {}) * ($sub."proxy-providers" // {}))
|
||||
})
|
||||
| .dns = ((.dns // {}) * {
|
||||
"proxy-server-nameserver": (
|
||||
.dns."proxy-server-nameserver"
|
||||
// $sub.dns."proxy-server-nameserver"
|
||||
// $sub.dns.nameserver
|
||||
// ["223.5.5.5", "119.29.29.29"]
|
||||
)
|
||||
})
|
||||
)
|
||||
' >"$TMP_FILE" 2>/dev/null || {
|
||||
log "模板复写失败:YAML 合并错误"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 兼容第三方模板中使用“直连”字样,统一转换为 Clash 内置 DIRECT。
|
||||
yq e '
|
||||
(.. | select(tag == "!!str")) |= sub("^直连$", "DIRECT")
|
||||
| .rules = ((.rules // []) | map(sub(",直连,", ",DIRECT,") | sub(",直连$", ",DIRECT")))
|
||||
' "$TMP_FILE" >"$TMP_FILE2" 2>/dev/null || {
|
||||
log "模板复写失败:直连别名标准化失败"
|
||||
exit 1
|
||||
}
|
||||
mv -f "$TMP_FILE2" "$TMP_FILE" >/dev/null 2>&1 || {
|
||||
log "模板复写失败:中间文件写入失败"
|
||||
exit 1
|
||||
}
|
||||
|
||||
yq e '.' "$TMP_FILE" >/dev/null 2>&1 || {
|
||||
log "模板复写失败:合并结果 YAML 无效"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -n "$core_bin" ]; then
|
||||
"$core_bin" -t -f "$TMP_FILE" >/dev/null 2>&1 || {
|
||||
log "模板复写失败:内核校验不通过"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
mv -f "$TMP_FILE" "$OUT_FILE" >/dev/null 2>&1 || {
|
||||
log "模板复写失败:写入输出文件失败"
|
||||
exit 1
|
||||
}
|
||||
|
||||
chmod 644 "$OUT_FILE" >/dev/null 2>&1 || true
|
||||
log "模板复写完成:$(basename "$OUT_FILE")"
|
||||
exit 0
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
config_name="${1:-$(uci -q get clashoo.config.config_update_name 2>/dev/null)}"
|
||||
[ -n "$config_name" ] || exit 1
|
||||
exec sh /usr/share/clashoo/update/subscription_update.sh --mihomo "$config_name"
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
UPDATE_LOG="/tmp/clash_update.txt"
|
||||
STATE_DIR="${CLASHOO_STATE_DIR:-/usr/share/clashbackup}"
|
||||
|
||||
log_update() {
|
||||
printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$UPDATE_LOG"
|
||||
}
|
||||
|
||||
log_update "更新大陆白名单"
|
||||
sh /usr/share/clashoo/update/update_china_ip.sh >> "$UPDATE_LOG" 2>&1
|
||||
|
||||
log_update "更新 GeoIP / GeoSite"
|
||||
sh /usr/share/clashoo/update/geoip.sh >/dev/null 2>&1
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
tmp="$STATE_DIR/rule_update.status.tmp.$$"
|
||||
printf 'last_run=%s\n' "$(date +%s)" >"$tmp" && mv -f "$tmp" "$STATE_DIR/rule_update.status"
|
||||
|
||||
exit 0
|
||||
@@ -1,204 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
LOG_FILE="${LOG_FILE:-/tmp/clash_update.txt}"
|
||||
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}"
|
||||
PROC_ROOT="${PROC_ROOT:-/proc}"
|
||||
SINGBOX_BIN_DIR="${SINGBOX_BIN_DIR:-/usr/share/clashoo/bin}"
|
||||
TMP_V4="/tmp/china_ip.txt.$$"
|
||||
TMP_V6="/tmp/china_ipv6.txt.$$"
|
||||
OUT_V4="/tmp/geoip_cn.nft.$$"
|
||||
OUT_V6="/tmp/geoip6_cn.nft.$$"
|
||||
|
||||
log() {
|
||||
printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
bool_enabled() {
|
||||
case "$1" in
|
||||
1|true|TRUE|yes|on) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
download_with_fallback() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
local ip
|
||||
|
||||
if curl -fsSL "$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
|
||||
log "DNS 异常,已使用 --resolve(${ip}) 回源下载"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -f "$TMP_V4" "$TMP_V6" "$OUT_V4" "$OUT_V6"
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
render_nft_set() {
|
||||
local source_file="$1"
|
||||
local output_file="$2"
|
||||
local set_name="$3"
|
||||
local set_type="$4"
|
||||
|
||||
awk -v set_name="$set_name" -v set_type="$set_type" '
|
||||
BEGIN {
|
||||
print "set " set_name " {"
|
||||
print "\ttype " set_type ";"
|
||||
print "\tflags interval;"
|
||||
print "\tauto-merge;"
|
||||
print "\telements = {"
|
||||
first = 1
|
||||
}
|
||||
!/^[[:space:]]*$/ && !/^[[:space:]]*#/ {
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0)
|
||||
if ($0 == "")
|
||||
next
|
||||
if (!first)
|
||||
print ","
|
||||
printf "\t\t%s", $0
|
||||
first = 0
|
||||
}
|
||||
END {
|
||||
if (!first)
|
||||
print ""
|
||||
print "\t}"
|
||||
print "}"
|
||||
}
|
||||
' "$source_file" > "$output_file"
|
||||
}
|
||||
|
||||
url4="$(uci -q get clashoo.config.china_ip_url 2>/dev/null || true)"
|
||||
url6="$(uci -q get clashoo.config.china_ipv6_url 2>/dev/null || true)"
|
||||
bypass_china="$(uci -q get clashoo.config.bypass_china 2>/dev/null || true)"
|
||||
bypass_china_ipv6="$(uci -q get clashoo.config.bypass_china_ipv6 2>/dev/null || true)"
|
||||
[ -n "$bypass_china_ipv6" ] || bypass_china_ipv6="$bypass_china"
|
||||
|
||||
[ -n "$url4" ] || url4='https://ispip.clang.cn/all_cn.txt'
|
||||
[ -n "$url6" ] || url6='https://ispip.clang.cn/all_cn_ipv6.txt'
|
||||
|
||||
mkdir -p "$NFT_DIR"
|
||||
|
||||
log '开始更新大陆白名单'
|
||||
|
||||
download_with_fallback "$url4" "$TMP_V4"
|
||||
[ -s "$TMP_V4" ] || {
|
||||
log '大陆 IPv4 白名单下载失败:返回为空'
|
||||
exit 1
|
||||
}
|
||||
changed=0
|
||||
|
||||
render_nft_set "$TMP_V4" "$OUT_V4" clashoo_china ipv4_addr
|
||||
if cmp -s "$OUT_V4" "$TARGET_V4"; then
|
||||
log '大陆 IPv4 白名单内容无变化'
|
||||
else
|
||||
mv "$OUT_V4" "$TARGET_V4"
|
||||
chmod 644 "$TARGET_V4" >/dev/null 2>&1 || true
|
||||
changed=1
|
||||
log '大陆 IPv4 白名单更新完成'
|
||||
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 cmp -s "$OUT_V6" "$TARGET_V6"; then
|
||||
log '大陆 IPv6 白名单内容无变化'
|
||||
else
|
||||
mv "$OUT_V6" "$TARGET_V6"
|
||||
chmod 600 "$TARGET_V6" >/dev/null 2>&1 || true
|
||||
changed=1
|
||||
log '大陆 IPv6 白名单更新完成'
|
||||
fi
|
||||
else
|
||||
log '大陆 IPv6 白名单下载为空,保留原文件'
|
||||
fi
|
||||
else
|
||||
log '大陆 IPv6 白名单下载失败,保留原文件'
|
||||
fi
|
||||
|
||||
instance_pid() {
|
||||
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" ]
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
name="clashoo"
|
||||
pid="$(instance_pid "$name" singbox)"
|
||||
singbox_pid_owns_private_binary "$pid"
|
||||
return $?
|
||||
else
|
||||
name="clashoo"
|
||||
fi
|
||||
pid="$(instance_pid "$name")"
|
||||
pid_running "$pid"
|
||||
}
|
||||
|
||||
firewall_managed() {
|
||||
bool_enabled "$(uci -q get clashoo.config.enable 2>/dev/null)" || return 1
|
||||
[ "$(uci -q get clashoo.config.core_only 2>/dev/null)" = "1" ] && return 1
|
||||
selected_core_running
|
||||
}
|
||||
|
||||
if [ "$changed" -eq 0 ]; then
|
||||
log '白名单内容未变,跳过防火墙重载'
|
||||
elif ! firewall_managed; then
|
||||
log '未接管防火墙(服务未运行或仅内核模式),仅更新白名单文件'
|
||||
elif bool_enabled "$bypass_china" || bool_enabled "$bypass_china_ipv6"; then
|
||||
if "$FW4_SCRIPT" apply >/dev/null 2>&1; then
|
||||
log "大陆白名单规则已重载(IPv4=${bypass_china:-0},IPv6=${bypass_china_ipv6:-0})"
|
||||
else
|
||||
log '大陆白名单规则重载失败'
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log '大陆 IPv4/IPv6 绕过均未启用,仅更新白名单文件'
|
||||
fi
|
||||
|
||||
log '大陆白名单更新流程完成'
|
||||
@@ -1,91 +0,0 @@
|
||||
--- a/dns/middleware.go
|
||||
+++ b/dns/middleware.go
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
icontext "github.com/metacubex/mihomo/context"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
+ M "github.com/metacubex/sing/common/metadata"
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -152,6 +153,9 @@ func withFakeIP(skipper *fakeip.Skipper,
|
||||
q := r.Question[0]
|
||||
|
||||
host := strings.TrimRight(q.Name, ".")
|
||||
+ if !M.IsDomainName(host) {
|
||||
+ return next(ctx, r)
|
||||
+ }
|
||||
if skipper.ShouldSkipped(host) {
|
||||
return next(ctx, r)
|
||||
}
|
||||
--- /dev/null
|
||||
+++ b/dns/middleware_test.go
|
||||
@@ -0,0 +1,68 @@
|
||||
+package dns
|
||||
+
|
||||
+import (
|
||||
+ "context"
|
||||
+ "net/netip"
|
||||
+ "testing"
|
||||
+
|
||||
+ "github.com/metacubex/mihomo/component/fakeip"
|
||||
+ icontext "github.com/metacubex/mihomo/context"
|
||||
+
|
||||
+ D "github.com/miekg/dns"
|
||||
+ "github.com/stretchr/testify/require"
|
||||
+)
|
||||
+
|
||||
+func TestWithFakeIPSkipsInvalidDomain(t *testing.T) {
|
||||
+ pool, err := fakeip.New(fakeip.Options{
|
||||
+ IPNet: netip.MustParsePrefix("198.18.0.1/16"),
|
||||
+ Size: 10,
|
||||
+ })
|
||||
+ require.NoError(t, err)
|
||||
+
|
||||
+ downstreamCalled := false
|
||||
+ next := func(_ *icontext.DNSContext, request *D.Msg) (*D.Msg, error) {
|
||||
+ downstreamCalled = true
|
||||
+ response := new(D.Msg)
|
||||
+ response.SetReply(request)
|
||||
+ return response, nil
|
||||
+ }
|
||||
+
|
||||
+ request := new(D.Msg)
|
||||
+ request.SetQuestion("n-relay-ipc-txc-nj-00.tplinkcloud.com.cn\\152.", D.TypeA)
|
||||
+ response, err := withFakeIP(&fakeip.Skipper{}, pool, nil, 1)(next)(icontext.NewDNSContext(context.Background()), request)
|
||||
+
|
||||
+ require.NoError(t, err)
|
||||
+ require.True(t, downstreamCalled)
|
||||
+ require.Empty(t, response.Answer)
|
||||
+ _, mapped := pool.LookBack(netip.MustParseAddr("198.18.0.4"))
|
||||
+ require.False(t, mapped)
|
||||
+}
|
||||
+
|
||||
+func TestWithFakeIPKeepsValidDomain(t *testing.T) {
|
||||
+ pool, err := fakeip.New(fakeip.Options{
|
||||
+ IPNet: netip.MustParsePrefix("198.18.0.1/16"),
|
||||
+ Size: 10,
|
||||
+ })
|
||||
+ require.NoError(t, err)
|
||||
+
|
||||
+ downstreamCalled := false
|
||||
+ next := func(_ *icontext.DNSContext, request *D.Msg) (*D.Msg, error) {
|
||||
+ downstreamCalled = true
|
||||
+ response := new(D.Msg)
|
||||
+ response.SetReply(request)
|
||||
+ return response, nil
|
||||
+ }
|
||||
+
|
||||
+ request := new(D.Msg)
|
||||
+ request.SetQuestion("n-relay-ipc-txc-nj-00.tplinkcloud.com.cn.", D.TypeA)
|
||||
+ dnsCtx := icontext.NewDNSContext(context.Background())
|
||||
+ response, err := withFakeIP(&fakeip.Skipper{}, pool, nil, 1)(next)(dnsCtx, request)
|
||||
+
|
||||
+ require.NoError(t, err)
|
||||
+ require.False(t, downstreamCalled)
|
||||
+ require.Len(t, response.Answer, 1)
|
||||
+ require.Equal(t, icontext.DNSTypeFakeIP, dnsCtx.Type())
|
||||
+ host, mapped := pool.LookBack(netip.MustParseAddr("198.18.0.4"))
|
||||
+ require.True(t, mapped)
|
||||
+ require.Equal(t, "n-relay-ipc-txc-nj-00.tplinkcloud.com.cn", host)
|
||||
+}
|
||||
Loaded 100 of 4914 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user