update 2026-07-05 01:02:55

This commit is contained in:
action committed 2026-07-05 01:02:55 +08:00
1 parent 37125b3bbc
commit 76aa39b679
46 files changed
+667 -2545

No files matched your search

+31 -17
View File
@@ -1,22 +1,24 @@
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
# SPDX-License-Identifier: GPL-2.0-only
include $(TOPDIR)/rules.mk
PKG_NAME:=adguardhome
PKG_VERSION:=0.107.51
PKG_VERSION:=0.107.77
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:=dea9685282e55940925ff7211f7d72072de5b98134c8f01d5569c04284681650
PKG_HASH:=1db8e53238c3ba8aecfbe0b68ba6197435dae19fd3512c70c68d90b7239b7464
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:=3c455340365e4dc3babedfc65e52ab93334a40568e8a7b84eb4aa1cd1a6b3a71
PKG_LICENSE:=GPL-3.0-only
PKG_LICENSE_FILES:=LICENSE.txt
PKG_MAINTAINER:=Dobroslaw Kijowski <dobo90@gmail.com>
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
@@ -25,16 +27,15 @@ PKG_BUILD_FLAGS:=no-mips16
GO_PKG:=github.com/AdguardTeam/AdGuardHome
GO_PKG_BUILD_PKG:=$(GO_PKG)
AGH_BUILD_TIME:=$(shell date -d @$(SOURCE_DATE_EPOCH) +%FT%TZ%z)
GO_PKG_LDFLAGS_X:= \
$(GO_PKG)/internal/version.channel=release \
$(GO_PKG)/internal/version.version=v$(PKG_VERSION) \
$(GO_PKG)/internal/version.buildtime=$(AGH_BUILD_TIME) \
$(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 ../../lang/golang/golang-package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/adguardhome
SECTION:=net
@@ -42,10 +43,11 @@ define Package/adguardhome
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.yaml
/etc/adguardhome/adguardhome.yaml
/etc/config/adguardhome
endef
@@ -53,25 +55,37 @@ define Package/adguardhome/description
Free and open source, powerful network-wide ads and trackers blocking DNS server.
endef
FRONTEND_FILE:=$(PKG_NAME)-frontend-$(PKG_VERSION).tar.gz
define Download/adguardhome-frontend
URL:=https://github.com/AdguardTeam/AdGuardHome/releases/download/v$(PKG_VERSION)/
URL:=$(FRONTEND_URL)
URL_FILE:=AdGuardHome_frontend.tar.gz
FILE:=$(FRONTEND_FILE)
HASH:=253d05185865840e14e841e4245c3fdb0fdcca28649902303d3869e0bd07cfd7
FILE:=$(FRONTEND_DEST)
HASH:=$(FRONTEND_HASH)
endef
define Build/Prepare
$(call Build/Prepare/Default)
gzip -dc $(DL_DIR)/$(FRONTEND_FILE) | $(HOST_TAR) -C $(PKG_BUILD_DIR)/ $(TAR_OPTIONS)
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))
+30
View File
@@ -0,0 +1,30 @@
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'
+109
View File
@@ -0,0 +1,109 @@
#!/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
+111
View File
@@ -0,0 +1,111 @@
#!/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
}
+22
View File
@@ -0,0 +1,22 @@
{
"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"
]
}
+3
View File
@@ -0,0 +1,3 @@
# quic-go expects larger UDP send/receive buffers
net.core.rmem_max = 7500000
net.core.wmem_max = 7500000
@@ -1,11 +0,0 @@
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -571,7 +571,7 @@
"filters_interval": "过滤器更新间隔",
"disabled": "已禁用",
"username_label": "用户名",
- "username_placeholder": "输入用户名",
+ "username_placeholder": "默认用户名密码都是root",
"password_label": "密码",
"password_placeholder": "输入密码",
"sign_in": "登入",
-9
View File
@@ -1,9 +0,0 @@
--- a/client/src/helpers/version.ts
+++ b/client/src/helpers/version.ts
@@ -13,5 +13,5 @@ export const areEqualVersions = (left: any, right: any) => {
const leftVersion = left.replace(/^v/, '');
const rightVersion = right.replace(/^v/, '');
- return leftVersion === rightVersion;
+ return leftVersion >= rightVersion;
};
+19 -41
View File
@@ -1,38 +1,36 @@
# 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_LIBC:=musl
ifeq ($(ARCH),arm)
PKG_LIBC:=musleabi
PKG_BUILD_DEPENDS:=rust/host
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
ifneq ($(filter $(ARM_CPU_FEATURES),vfp vfpv2),)
PKG_LIBC:=musleabihf
endif
endif
PKG_ARCH=$(ARCH)
ifeq ($(ARCH),i386)
PKG_ARCH:=i686
endif
PKG_SOURCE:=aliyundrive-webdav-v$(PKG_VERSION).$(PKG_ARCH)-unknown-linux-$(PKG_LIBC).tar.gz
PKG_SOURCE_URL:=https://github.com/messense/aliyundrive-webdav/releases/download/v$(PKG_VERSION)/
PKG_HASH:=skip
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
@@ -43,34 +41,14 @@ define Package/aliyundrive-webdav/conffiles
/etc/config/aliyundrive-webdav
endef
define Download/sha256sum
FILE:=$(PKG_SOURCE).sha256
URL_FILE:=$(FILE)
URL:=$(PKG_SOURCE_URL)
HASH:=skip
endef
$(eval $(call Download,sha256sum))
define Build/Prepare
mv $(DL_DIR)/$(PKG_SOURCE).sha256 .
cp $(DL_DIR)/$(PKG_SOURCE) .
shasum -a 256 -c $(PKG_SOURCE).sha256
rm $(PKG_SOURCE).sha256 $(PKG_SOURCE)
tar -C $(PKG_BUILD_DIR)/ -zxf $(DL_DIR)/$(PKG_SOURCE)
endef
define Build/Compile
echo "aliyundrive-webdav using precompiled binary."
endef
define Package/aliyundrive-webdav/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/aliyundrive-webdav $(1)/usr/bin/aliyundrive-webdav
$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET_ARCH)/release/aliyundrive-webdav $(1)/usr/bin/
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/aliyundrive-webdav.init $(1)/etc/init.d/aliyundrive-webdav
$(INSTALL_BIN) $(CURDIR)/files/aliyundrive-webdav.init $(1)/etc/init.d/aliyundrive-webdav
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/aliyundrive-webdav.config $(1)/etc/config/aliyundrive-webdav
$(INSTALL_CONF) $(CURDIR)/files/aliyundrive-webdav.config $(1)/etc/config/aliyundrive-webdav
endef
$(eval $(call BuildPackage,aliyundrive-webdav))
+14 -16
View File
@@ -6,32 +6,27 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=CloudDrive2
PKG_VERSION:=0.8.16
PKG_NAME:=clouddrive2
PKG_VERSION:=1.0.4
PKG_RELEASE=1
ifeq ($(ARCH),x86_64)
PKG_ARCH:=x86_64
endif
ifeq ($(ARCH),aarch64)
PKG_ARCH:=aarch64
endif
ifeq ($(ARCH),arm)
PKG_HASH:=dc9088789e75a6b17f55ea7756d841c7e660951f1195cd858c04d3d5b33ea7e0
else ifeq ($(ARCH),arm)
PKG_ARCH:=armv7
PKG_HASH:=26e15f9a97bba7344f39c06e54e42232b1518293d71570f3a2a01187044cb7b5
else ifeq ($(ARCH),x86_64)
PKG_ARCH:=x86_64
PKG_HASH:=84385139ff87a038677e016d78df43669f4a07c616f0e26d90c2c9df66467af4
endif
PKG_SOURCE:=clouddrive-2-linux-$(PKG_ARCH)-$(PKG_VERSION).tgz
PKG_SOURCE_URL:=https://github.com/cloud-fs/cloud-fs.github.io/releases/download/v$(PKG_VERSION)
PKG_HASH:=skip
include $(INCLUDE_DIR)/package.mk
define Package/clouddrive2
SECTION:=net
CATEGORY:=Network
DEPENDS:=@(arm||aarch64||x86_64) +fuse3-utils
DEPENDS:=@(arm||aarch64||x86_64) +fuse-utils
TITLE:=CloudDrive2
endef
@@ -39,6 +34,9 @@ define Package/clouddrive2/description
CloudDrive2 is a cloud storage mounting tool for OpenWrt.
endef
PKG_SOURCE:=clouddrive-2-linux-$(PKG_ARCH)-$(PKG_VERSION).tgz
PKG_SOURCE_URL:=https://github.com/cloud-fs/cloud-fs.github.io/releases/download/v$(PKG_VERSION)
define Build/Prepare
$(call Build/Prepare/Default)
tar -xzvf $(DL_DIR)/clouddrive-2-linux-$(PKG_ARCH)-$(PKG_VERSION).tgz -C $(PKG_BUILD_DIR)/ --strip-components=1
@@ -49,9 +47,9 @@ endef
define Package/clouddrive2/install
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/clouddrive2.init $(1)/etc/init.d/clouddrive2
$(INSTALL_BIN) $(CURDIR)/files/clouddrive2.init $(1)/etc/init.d/clouddrive2
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/clouddrive2.config $(1)/etc/config/clouddrive2
$(INSTALL_CONF) $(CURDIR)/files/clouddrive2.config $(1)/etc/config/clouddrive2
$(INSTALL_DIR) $(1)/usr/share/clouddrive2
$(INSTALL_BIN) $(PKG_BUILD_DIR)/clouddrive-2-linux-$(PKG_ARCH)-$(PKG_VERSION)/clouddrive $(1)/usr/share/clouddrive2/
cp -rf $(PKG_BUILD_DIR)/clouddrive-2-linux-$(PKG_ARCH)-$(PKG_VERSION)/wwwroot $(1)/usr/share/clouddrive2/
+1 -1
View File
@@ -37,5 +37,5 @@ stop_service() {
load_config
echo "Stopping $SERVICE"
killall -q clouddrive
[ -d "$mount_point" ] && fusermount3 -u "$mount_point"
[ -d "$mount_point" ] && fusermount -u "$mount_point"
}
+15 -18
View File
@@ -1,7 +1,9 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com>
# Copyright (C) 2021-2023 sirpdboy <herboy2008@gmail.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
@@ -15,46 +17,41 @@ PKG_HASH:=05ae7ef07241113ee2cd861be530524f06856fc7368b84846db5137a477277ea
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16
GO_PKG:=github.com/jeessy2/ddns-go/v6
GO_PKG_LDFLAGS_X:=main.Version=$(PKG_VERSION)
GO_PKG_LDFLAGS:=$(GO_PKG_LDFLAGS_X)
GO_PKG_LDFLAGS_X:=main.version=$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/ddns-go
TITLE:=A Linux web GUI client of ddns-go
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=Support IPV4 and IPV6 simple and easy-to-use IP binding domain name tool
URL:=https://github.com/jeessy2/ddns-go
SUBMENU:=Web Servers/Proxies
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
URL:=https://github.com/jeessy2/ddns-go
USERID:=ddns-go:ddns-go
endef
define Package/ddns-go/description
A simple and easy-to-use IP binding domain name tool that supports IPV4 and IPV6.
ddns-go is a automatically obtain your public IPv4 or IPv6 address and resolve it to the corresponding domain name service,
support Alidns Dnspod Cloudflare Hicloud Callback Baiducloud porkbun GoDaddy Google Domains.
endef
define Package/ddns-go/conffiles
/etc/config/ddns-go
/etc/ddns-go/ddns-go-config.yaml
endef
define Package/ddns-go/install
$(call GoPackage/Package/Install/Bin,$(1))
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_BIN) $(CURDIR)/files/ddns-go.init $(1)/etc/init.d/ddns-go
$(INSTALL_CONF) $(CURDIR)/files/ddns-go.conf $(1)/etc/config/ddns-go
$(INSTALL_BIN) $(CURDIR)/file/ddns-go.init $(1)/etc/init.d/ddns-go
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) $(CURDIR)/file/luci-ddns-go.uci-default $(1)/etc/uci-defaults/luci-ddns-go
endef
$(eval $(call GoBinPackage,ddns-go))
+46
View File
@@ -0,0 +1,46 @@
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2021-2023 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-ddns-go
#
# This file is part of ddns-go .
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
START=99
USE_PROCD=1
PROG=/usr/bin/ddns-go
CONFDIR=/etc/ddns-go
CONF=$CONFDIR/ddns-go-config.yaml
get_config() {
config_get_bool enabled $1 enabled 1
config_get_bool logger $1 logger 1
config_get port $1 port 9876
config_get time $1 time 300
}
init_yaml(){
[ -d $CONFDIR ] || mkdir -p $CONFDIR 2>/dev/null
cat /usr/share/ddns-go/ddns-go-default.yaml > $CONF
}
start_service() {
config_load ddns-go
config_foreach get_config basic
[ x$enabled == x1 ] || return 1
[ -s ${CONF} ] || init_yaml
logger -t ddns-go -p warn "ddns-go is start."
echo "ddns-go is start."
procd_open_instance
procd_set_param command $PROG -l :$port -f $time -c "$CONF"
[ "x$logger" == x1 ] && procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
}
service_triggers() {
procd_add_reload_trigger "ddns-go"
}
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
[ -s "/etc/ddns-go/localtime" ] && mv -f /etc/ddns-go/localtime /etc/localtime
/etc/init.d/ddns-go enable
/etc/init.d/ddns-go start
rm -f /tmp/luci*
exit 0
-9
View File
@@ -1,9 +0,0 @@
config basic 'config'
option enabled '0'
option logger '1'
option port '9876'
option time '300'
option ctimes '5'
option skipverify '0'
option delay '0'
option dns '223.5.5.5'
-84
View File
@@ -1,84 +0,0 @@
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com>
#
# This file is part of ddns-go .
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
START=99
USE_PROCD=1
NAME=ddns-go
PROG=/usr/bin/ddns-go
CONFDIR=/etc/ddns-go
CONF=$CONFDIR/ddns-go-config.yaml
init_yaml() {
[ -d "$CONFDIR" ] || mkdir -p "$CONFDIR"
chown -R ddns-go:ddns-go "$CONFDIR"
chmod 755 "$CONFDIR"
[ -f "$CONF" ] && chmod 644 "$CONF"
}
build_args() {
local cfg="$1"
local args="-c $CONF"
config_get port "$cfg" port '9876'
args="$args -l :$port"
config_get time "$cfg" time '300'
[ -n "$time" ] && args="$args -f $time"
config_get ctimes "$cfg" ctimes '5'
[ -n "$ctimes" ] && args="$args -cacheTimes $ctimes"
config_get dns "$cfg" dns '223.5.5.5'
[ -n "$dns" ] && args="$args -dns $dns"
config_get_bool noweb "$cfg" noweb 0
[ "$noweb" -eq 1 ] && args="$args -noweb"
config_get_bool skipverify "$cfg" skipverify 0
[ "$skipverify" -eq 1 ] && args="$args -skipVerify"
echo "$args"
}
start_instance() {
local cfg="$1"
config_get_bool enabled "$cfg" enabled 0
[ "$enabled" -eq 0 ] && return 0
config_get delay "$cfg" delay 0
if [ "$delay" -gt 0 ]; then
local uptime=$(awk -F. '{print $1}' /proc/uptime)
[ "$uptime" -lt 120 ] && sleep "$delay"
fi
init_yaml
local args=$(build_args "$cfg")
procd_open_instance
procd_set_param command $PROG $args
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param log_level 7
procd_set_param user ddns-go
procd_set_param respawn
procd_close_instance
}
start_service() {
config_load "$NAME"
config_foreach start_instance 'basic'
}
service_triggers() {
procd_add_reload_trigger "$NAME"
}
+45 -40
View File
@@ -1,63 +1,68 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2017-2024
#
# This is free software, licensed under the GNU General Public License v2.
#
# Copyright (C) 2024 op.dllkids.xyz
include $(TOPDIR)/rules.mk
PKG_NAME:=filebrowser
PKG_VERSION:=0.8.7-beta
PKG_RELEASE=1
PKG_VERSION:=2.63.18
PKG_RELEASE:=1
ifeq ($(ARCH),aarch64)
PKG_ARCH:=arm64
PKG_HASH:=skip
else ifeq ($(ARCH),arm)
PKG_ARCH:=armv7
PKG_HASH:=skip
else ifeq ($(ARCH),x86_64)
PKG_ARCH:=amd64
PKG_HASH:=skip
endif
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/filebrowser/filebrowser/tar.gz/v${PKG_VERSION}?
PKG_HASH:=b665942fa4adb882498e89f09ef90f4690de4f22de043453c28e201c812060c5
PKG_LICENSE:=Apache-2.0
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=kenzo <kenzok8@gmail.com>
PKG_BUILD_DEPENDS:=golang/host node/host
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
PKG_BUILD_FLAGS:=no-mips16
GO_PKG:=github.com/filebrowser/filebrowser
GO_PKG_LDFLAGS_X:= \
$(GO_PKG)/v2/version.CommitSHA=$(PKG_SOURCE_VERSION) \
$(GO_PKG)/v2/version.Version=v$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/filebrowser
SECTION:=net
CATEGORY:=Network
DEPENDS:=@(arm||aarch64||x86_64)
TITLE:=FileBrowser Quantum
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Web File Browser
URL:=https://github.com/filebrowser/filebrowser
DEPENDS:=$(GO_ARCH_DEPENDS)
endef
define Package/filebrowser/description
The best free self-hosted web-based file manager.
endef
PKG_SOURCE:=linux-$(PKG_ARCH)-filebrowser
PKG_SOURCE_URL:=https://github.com/gtsteffaniak/filebrowser/releases/download/v$(PKG_VERSION)
define Build/Prepare
$(call Build/Prepare/Default)
filebrowser provides a file managing interface within a specified directory
and it can be used to upload, delete, preview, rename and edit your files.
It allows the creation of multiple users and each user can have its own directory.
It can be used as a standalone app or as a middleware.
endef
define Build/Compile
endef
define Package/filebrowser/conffiles
/etc/filebrowser/
/etc/config/filebrowser
( \
pushd "$(PKG_BUILD_DIR)/frontend" ; \
npm install -g pnpm ; \
pnpm install --frozen-lockfile ; \
pnpm run build ; \
popd ; \
$(call GoPackage/Build/Compile) ; \
)
endef
define Package/filebrowser/install
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) $(CURDIR)/files/filebrowser.init $(1)/etc/init.d/filebrowser
$(call GoPackage/Package/Install/Bin,$(1))
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) $(CURDIR)/files/filebrowser.config $(1)/etc/config/filebrowser
$(INSTALL_DIR) $(1)/etc/filebrowser
$(INSTALL_CONF) $(CURDIR)/files/config.yaml $(1)/etc/filebrowser/config.yaml
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_BIN) $(DL_DIR)/linux-$(PKG_ARCH)-filebrowser $(1)/usr/bin/filebrowser
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) $(CURDIR)/files/filebrowser.init $(1)/etc/init.d/filebrowser
endef
$(eval $(call BuildPackage,filebrowser))
$(eval $(call GoBinPackage,filebrowser))
$(eval $(call BuildPackage,filebrowser))
-156
View File
@@ -1,156 +0,0 @@
server:
minSearchLength: 3 # minimum length of search query to begin searching (default: 3)
disableUpdateCheck: false # disables backend update check service
numImageProcessors: 4 # number of concurrent image processing jobs used to create previews, default is number of cpu cores available.
socket: "" # socket to listen on
tlsKey: "" # path to TLS key
tlsCert: "" # path to TLS cert
disablePreviews: false # disable all previews thumbnails, simple icons will be used
disablePreviewResize: false # disable resizing of previews for faster loading over slow connections
disableTypeDetectionByHeader: false # disable type detection by header, useful if filesystem is slow.
port: 8989
baseURL: "/"
logging:
- levels: "" # separated list of log levels to enable. (eg. "info|warning|error|debug")
apiLevels: "" # separated list of log levels to enable for the API. (eg. "info|warning|error")
output: "stdout" # output location. (eg. "stdout" or "path/to/file.log")
noColors: false # disable colors in the output
json: false # output in json format
utc: false # use UTC time in the output instead of local time
debugMedia: false # output ffmpeg stdout for media integration -- careful can produces lots of output!
database: "/etc/filebrowser/database.db" # path to the database file
sources: # validate:required,dive
- path: "/"
name: "Root" # display name
config:
denyByDefault: false # deny access unless an "allow" access rule was specifically created.
private: false # designate as source as private -- currently just means no sharing permitted.
disabled: false # disable the source, this is useful so you don't need to remove it from the config file
indexingIntervalMinutes: 0 # optional manual overide interval in minutes to re-index the source
disableIndexing: false # disable the indexing of this source
maxWatchers: 0 # number of concurrent watchers to use for this source, currently not supported
neverWatchPaths: []
exclude: # exclude files and folders from indexing, if include is not set
hidden: false # exclude hidden files and folders.
ignoreZeroSizeFolders: false # ignore folders with 0 size
filePaths: []
folderPaths: []
fileNames: []
folderNames: []
fileEndsWith: []
folderEndsWith: []
fileStartsWith: []
folderStartsWith: []
include: # include files and folders from indexing, if exclude is not set
rootFolders: []
rootFiles: []
defaultUserScope: "/" # default "/" should match folders under path
defaultEnabled: false # should be added as a default source for new users?
createUserDir: false # create a user directory for each user
externalUrl: "" # used by share links if set (eg. http://mydomain.com)
internalUrl: "" # used by integrations if set, this is the base domain that an integration service will use to communicate with filebrowser (eg. http://localhost:8080)
cacheDir: "tmp" # path to the cache directory, used for thumbnails and other cached files
maxArchiveSize: 50 # max pre-archive combined size of files/folder that are allowed to be archived (in GB)
auth:
tokenExpirationHours: 2 # time in hours each web UI session token is valid for. Default is 2 hours.
methods:
proxy: # validate:omitempty
enabled: false
createUser: false # create user if not exists
header: "" # required header to use for authentication. Security Warning: FileBrowser blindly accepts the header value as username.
logoutRedirectUrl: "" # if provider logout url is provided, filebrowser will also redirect to logout url. Custom logout query params are respected.
noauth: false # if set to true, overrides all other auth methods and disables authentication
password: # validate:omitempty
enabled: true
minLength: 3 # minimum pasword length required, default is 5. validate:omitempty
signup: false # allow signups on login page if enabled -- not secure. validate:omitempty
recaptcha: # recaptcha config, only used if signup is enabled validate:omitempty
host: "" # validate:required
key: "" # validate:required
secret: "" # validate:required
enforcedOtp: false # if set to true, TOTP is enforced for all password users users. Otherwise, users can choose to enable TOTP.
oidc: # validate:omitempty
enabled: false # whether to enable OIDC authentication
clientId: "**hidden**" # secret: client id of the OIDC application
clientSecret: "**hidden**" # secret: client secret of the OIDC application
issuerUrl: "" # authorization URL of the OIDC provider
scopes: "openid email profile" # scopes to request from the OIDC provider
userIdentifier: "preferred_username" # the field value to use as the username. Default is "preferred_username", can also be "email" or "username", or "phone"
disableVerifyTLS: false # disable TLS verification for the OIDC provider. This is insecure and should only be used for testing.
logoutRedirectUrl: "" # if provider logout url is provided, filebrowser will also redirect to logout url. Custom logout query params are respected.
createUser: false # create user if not exists
adminGroup: "" # if set, users in this group will be granted admin privileges.
groupsClaim: "groups" # the JSON field name to read groups from. Default is "groups"
key: "**hidden**" # secret: the key used to sign the JWT tokens. If not set, a random key will be generated.
adminUsername: "admin" # secret: the username of the admin user. If not set, the default is "admin".
adminPassword: "admin" # secret: the password of the admin user. If not set, the default is "admin".
totpSecret: "**hidden**" # secret: secret used to encrypt TOTP secrets
frontend:
name: "FileBrowser Quantum" # display name
disableDefaultLinks: false # disable default links in the sidebar
disableUsedPercentage: false # disable used percentage for the sources in the sidebar
externalLinks:
- text: "Help" # the text to display on the link validate:required
title: "" # the title to display on hover
url: "https://github.com/gtsteffaniak/filebrowser" # the url to link to validate:required
disableNavButtons: false # disable the nav buttons in the sidebar
styling:
customCSS: "" # if a valid path to a css file is provided, it will be applied for all users. (eg. "reduce-rounded-corners.css")
lightBackground: "#f5f5f5" # specify a valid CSS color property value to use as the background color in light mode
darkBackground: "#141D24" # Specify a valid CSS color property value to use as the background color in dark mode
customThemes: # A list of custom css files that each user can select to override the default styling. if "default" is key name then it will be the default option.
default:
description: "The default theme" # The description of the theme to display in the UI.
css: "" # The css file path and filename to use for the theme.
alternative:
description: "Reduce rounded corners" # The description of the theme to display in the UI.
css: "reduce-rounded-corners.css" # The css file path and filename to use for the theme.
favicon: "" # path to a favicon to use for the frontend
description: "FileBrowser Quantum is a file manager for the web which can be used to manage files on your server" # description that shows up in html head meta description
userDefaults:
editorQuickSave: false # show quick save button in editor
hideSidebarFileActions: false # hide the file actions in the sidebar
disableQuickToggles: false # disable the quick toggles in the sidebar
disableSearchOptions: false # disable the search options in the search bar
stickySidebar: true # keep sidebar open when navigating
darkMode: true # should dark mode be enabled
locale: "zhCN" # language to use: eg. de, en, or fr
viewMode: "normal" # view mode to use: eg. normal, list, grid, or compact
singleClick: false # open directory on single click, also enables middle click to open in new tab
showHidden: false # show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files
dateFormat: false # when false, the date is relative, when true, the date is an exact timestamp
gallerySize: 3 # 0-9 - the size of the gallery thumbnails
themeColor: "var(--blue)" # theme color to use: eg. #ff0000, or var(--red), var(--purple), etc
quickDownload: false # show icon to download in one click
disablePreviewExt: "" # space separated list of file extensions to disable preview for
disableViewingExt: "" # space separated list of file extensions to disable viewing for
lockPassword: false # disable the user from changing their password
disableSettings: false # disable the user from viewing the settings page
preview:
disableHideSidebar: false
highQuality: false
image: false
video: false
motionVideoPreview: false
office: false
popup: false
autoplayMedia: false
defaultMediaPlayer: false
folder: false
permissions:
api: false
admin: false
modify: false
share: false
realtime: false
loginMethod: "password" # login method to use: eg. password, proxy, oidc
disableUpdateNotifications: false # disable update notifications banner for admin users
deleteWithoutConfirming: false # delete files without confirmation
fileLoading: # upload and download settings
maxConcurrentUpload: 10
uploadChunkSizeMb: 10
clearAll: false
disableOnlyOfficeExt: "" # list of file extensions to disable onlyoffice editor for
customTheme: "" # Name of theme to use chosen from custom themes config.
showSelectMultiple: false # show select multiple files on desktop
debugOffice: false # debug onlyoffice editor
+6 -4
View File
@@ -1,7 +1,9 @@
config filebrowser 'config'
option addr_type 'lan'
option db_dir '/etc'
option db_name 'filebrowser.db'
option enabled '0'
option listen_port '8989'
option root_path '/'
option base_url '/'
option disable_exec '1'
option port '8989'
option root_dir '/'
+24 -21
View File
@@ -1,31 +1,34 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2021 ImmortalWrt
START=99
START=90
STOP=10
CONF="filebrowser"
PROG="/usr/bin/filebrowser"
CONF_PATH="/etc/filebrowser/config.yaml"
PID_FILE="/var/run/filebrowser.pid"
addr_type="$(uci get filebrowser.config.addr_type)"
db_dir="$(uci get filebrowser.config.db_dir)"
[ "${db_dir}" == "/" ] || db_dir="${db_dir%*/}"
db_name="$(uci get filebrowser.config.db_name| sed 's#/##g')"
enabled="$(uci get filebrowser.config.enabled)"
port="$(uci get filebrowser.config.port)"
root_dir="$(uci get filebrowser.config.root_dir)"
if [ "${addr_type}" == "local" ];then
addr="127.0.0.1"
elif [ "${addr_type}" == "lan" ];then
addr="$(uci get network.lan.ipaddr)"
elif [ "${addr_type}" == "wan" ];then
addr="0.0.0.0"
fi
start() {
config_load "$CONF"
local enabled
config_get_bool enabled "config" "enabled" "0"
[ "$enabled" -eq "1" ] || return 1
local listen_port root_path base_url
config_get listen_port "config" "listen_port" "8989"
config_get root_path "config" "root_path" "/"
config_get base_url "config" "base_url" "/"
echo "Starting filebrowser..."
start-stop-daemon -S -q -b -m -p "$PID_FILE" -x "$PROG" -- -c "$CONF_PATH"
stop
[ "$enabled" == "1" ] || exit 0
mkdir -p "${root_dir}"
mkdir -p "${db_dir}"
filebrowser -a "${addr}" -d "${db_dir}/${db_name}" -p "${port}" -r "${root_dir}" >/dev/null 2>&1 &
}
stop() {
kill -9 `pidof filebrowser | sed "s/$$//g"` 2>/dev/null
rm -f "$PID_FILE"
echo "filebrowser stopped"
echo "${db_dir}/${db_name}" > "/lib/upgrade/keep.d/filebrowser"
killall -3 filebrowser >/dev/null 2>&1
}
+9
View File
@@ -0,0 +1,9 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for UUgamebooster
LUCI_DEPENDS:=+uugamebooster
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
@@ -0,0 +1,74 @@
'use strict';
'require form';
'require view';
'require poll';
'require ui';
'require rpc';
var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});
function getServiceStatus() {
return L.resolveDefault(callServiceList('uugamebooster'), {}).then(function(res) {
var instances = res['uugamebooster'] && res['uugamebooster']['instances'];
if (instances) {
for (var key in instances) {
if (instances[key] && instances[key].running)
return true;
}
}
return false;
});
}
return view.extend({
render: function() {
var m, s, o;
m = new form.Map('uugamebooster', _('UU Game Accelerator'), _('A Paid Game Acceleration service'));
s = m.section(form.GridSection, '_status', _('Status'));
s.render = function() {
return E('div', { class: 'cbi-section' }, [
E('p', { id: 'uugamebooster_status' }, _('Collecting data...'))
]);
};
s = m.section(form.TypedSection, 'uugamebooster', _('Settings'));
s.anonymous = true;
s.addremove = false;
o = s.option(form.Flag, 'enabled', _('Enable'));
o.default = '0';
o.rmempty = false;
s = m.section(form.GridSection, '_qrcode', _('QR Code'));
s.render = function() {
return E('div', { class: 'cbi-section' }, [
E('p', { id: 'uugamebooster_qcode' }, [
E('img', { src: '/uugamebooster/uuios.png', height: '300' }),
E('img', { src: '/uugamebooster/uuandriod.png', height: '300' })
])
]);
};
poll.add(function() {
return getServiceStatus().then(function(running) {
var node = document.getElementById('uugamebooster_status');
if (node) {
if (running) {
node.innerHTML = '<em><b><font color="green">UU GameAcc ' + _('RUNNING') + '</font></b></em>';
} else {
node.innerHTML = '<em><b><font color="red">UU GameAcc ' + _('NOT RUNNING') + '</font></b></em>';
}
}
});
}, 3);
return m.render();
}
});
@@ -0,0 +1,46 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"Language: zh_Hans\n"
#: htdocs/luci-static/resources/view/uugamebooster.js:24
msgid "Enable"
msgstr "启用"
#: root/usr/share/luci/menu.d/luci-app-uugamebooster.json:3
msgid "UU GameAcc"
msgstr "UU游戏加速器"
#: htdocs/luci-static/resources/view/uugamebooster.js:11
msgid "UU Game Accelerator"
msgstr "UU游戏加速器"
#: htdocs/luci-static/resources/view/uugamebooster.js:11
msgid "A Paid Game Acceleration service"
msgstr "一个付费主机游戏加速器 (开启服务后,使用手机APP绑定路由器并指定加速主机)"
#: htdocs/luci-static/resources/view/uugamebooster.js:13
msgid "Status"
msgstr "状态"
#: htdocs/luci-static/resources/view/uugamebooster.js:16
msgid "Collecting data..."
msgstr "正在收集数据..."
#: htdocs/luci-static/resources/view/uugamebooster.js:20
msgid "Settings"
msgstr "设置"
#: htdocs/luci-static/resources/view/uugamebooster.js:28
msgid "QR Code"
msgstr "二维码"
#: htdocs/luci-static/resources/view/uugamebooster.js:44
msgid "RUNNING"
msgstr "运行中"
#: htdocs/luci-static/resources/view/uugamebooster.js:46
msgid "NOT RUNNING"
msgstr "未运行"
@@ -0,0 +1,13 @@
{
"admin/services/uugamebooster": {
"title": "UU GameAcc",
"action": {
"type": "view",
"path": "uugamebooster"
},
"depends": {
"acl": [ "luci-app-uugamebooster" ],
"uci": { "uugamebooster": true }
}
}
}
@@ -0,0 +1,14 @@
{
"luci-app-uugamebooster": {
"description": "Grant UCI access for luci-app-uugamebooster",
"read": {
"uci": [ "uugamebooster" ],
"ubus": {
"service": [ "list" ]
}
},
"write": {
"uci": [ "uugamebooster" ]
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

+6 -20
View File
@@ -1,10 +1,9 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021-2025 sirpdboy <herboy2008@gmail.com>
# Copyright (C) 2021-2022 sirpdboy <herboy2008@gmail.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
#
include $(TOPDIR)/rules.mk
@@ -26,12 +25,8 @@ ifeq ($(ARCH),x86_64)
LUCKY_ARCH:=x86_64
endif
ifeq ($(ARCH),arm)
ifeq ($(BOARD),bcm53xx)
LUCKY_ARCH:=armv6
else
LUCKY_ARCH:=armv7
endif
endif
ifeq ($(BOARD),bcm53xx)
LUCKY_ARCH:=armv6
ifeq ($(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE)))),)
@@ -50,42 +45,33 @@ PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=GDY666 <gdy666@foxmail.com>
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip
PKG_HASH:=0216c9a833724875f4a004aa5fc5e6e1a2d9f92f99e933905f76ebf7876b413c
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
TITLE:=Lucky gdy
TITLE:=Lucky dynamic domain name ddns-go service, socat,frp
DEPENDS:=@(i386||x86_64||arm||aarch64||mipsel||mips)
URL:=https://github.com/gdy666/lucky
endef
define Package/$(PKG_NAME)/description
Main functions of Lucky: ipv4/ipv6 portforward,ddns,IOT wake on lan ,reverse proxy and more...
Main functions of Lucky: dynamic domain name ddns-go service, socat,reverse proxy ,wake on lan
endef
define Build/Prepare
[ ! -f $(PKG_BUILD_DIR)/$(PKG_NAME)_$(PKG_VERSION)_Linux_$(LUCKY_ARCH).tar.gz ] && wget https://github.com/gdy666/lucky/releases/download/v$(PKG_VERSION)/$(PKG_NAME)_$(PKG_VERSION)_Linux_$(LUCKY_ARCH).tar.gz -O $(PKG_BUILD_DIR)/$(PKG_NAME)_$(PKG_VERSION)_Linux_$(LUCKY_ARCH).tar.gz
tar -xzvf $(PKG_BUILD_DIR)/$(PKG_NAME)_$(PKG_VERSION)_Linux_$(LUCKY_ARCH).tar.gz -C $(PKG_BUILD_DIR) || exit 1
tar -xzvf $(PKG_BUILD_DIR)/$(PKG_NAME)_$(PKG_VERSION)_Linux_$(LUCKY_ARCH).tar.gz -C $(PKG_BUILD_DIR)
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/lucky
/etc/lucky/*
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_DIR) $(1)/etc/init.d/
$(INSTALL_DIR) $(1)/etc/config/
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/lucky $(1)/usr/bin/lucky
$(INSTALL_BIN) $(CURDIR)/files/luckyarch.bin $(1)/usr/bin/luckyarch
$(INSTALL_BIN) ./files/lucky.init $(1)/etc/init.d/lucky
$(INSTALL_CONF) $(CURDIR)/files/lucky.config $(1)/etc/config/lucky
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
-5
View File
@@ -1,5 +0,0 @@
config lucky 'lucky'
option logger '1'
option port '16601'
option configdir '/etc/lucky'
option enabled '0'
-81
View File
@@ -1,81 +0,0 @@
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2021-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-lucky
# This file is part of lucky .
#
# This is free software, licensed under the Apache License, Version 2.0 .
START=99
STOP=15
USE_PROCD=1
CONF=lucky
PROG=/usr/bin/lucky
CONFDIR=/etc/lucky
get_config() {
config_get_bool enabled $1 enabled 0
config_get_bool logger $1 logger 1
config_get port $1 port 16601
config_get SafeURL $1 safe
config_get delay $1 delay 0
}
init_config(){
config_load "$CONF"
config_foreach get_config "$CONF"
}
init_confdir(){
[ -d $CONFDIR ] || mkdir -p $CONFDIR 2>/dev/null
}
LOG(){
echo "$1"
logger -t lucky -p warn "$1"
}
start_instance() {
enabled=$(uci -q get $CONF.$CONF.enabled ) || enabled="0"
logger=$(uci -q get $CONF.$CONF.logger ) || logger="1"
port=$(uci -q get $CONF.$CONF.port ) || port="16601"
SafeURL=$(uci -q get $CONF.$CONF.safe ) || SafeURL=" "
delay=$(uci -q get $CONF.$CONF.delay ) || delay="5"
SafeURL="${SafeURL##*( )}"
SafeURL="${SafeURL%%*( )}"
init_confdir
[ x$enabled = x1 ] || return 1
[ $(awk -F. '{print $1}' /proc/uptime) -lt "120" ] && sleep $delay
$(which lucky) -setconf -key AdminWebListenPort -value $port -cd $CONFDIR
if [ -z "$SafeURL" ] ; then
$(which lucky) -rCancelSafeURL
else
$(which lucky) -setconf -key SafeURL -value "$SafeURL" -cd $CONFDIR
fi
procd_open_instance
procd_set_param command $PROG
procd_append_param command -cd $CONFDIR
procd_set_param respawn
procd_set_param stderr 1
procd_close_instance
LOG "lucky is start."
}
start_service() {
pgrep -f $PROG | xargs kill -9 >/dev/null 2>&1
start_instance
}
stop_service() {
pgrep -f $PROG | xargs kill -9 >/dev/null 2>&1
LOG "lucky is stop."
}
service_triggers() {
procd_add_reload_trigger lucky
}
-14
View File
@@ -1,14 +0,0 @@
#!/bin/sh
cputype=$(uname -ms | tr ' ' '_' | tr '[A-Z]' '[a-z]')
[ -n "$(echo $cputype | grep -E "linux.*armv.*")" ] && cpucore="armv5"
[ -n "$(echo $cputype | grep -E "linux.*armv7.*")" ] && [ -n "$(cat /proc/cpuinfo | grep vfp)" ] && [ ! -d /jffs/clash ] && cpucore="armv7"
[ -n "$(echo $cputype | grep -E "linux.*aarch64.*|linux.*armv8.*")" ] && cpucore="arm64"
[ -n "$(echo $cputype | grep -E "linux.*86.*")" ] && cpucore="i386"
[ -n "$(echo $cputype | grep -E "linux.*86_64.*")" ] && cpucore="x86_64"
if [ -n "$(echo $cputype | grep -E "linux.*mips.*")" ];then
mipstype=$(echo -n I | hexdump -o 2>/dev/null | awk '{ print substr($2,6,1); exit}') #通过判断大小端判断mips或mipsle
[ "$mipstype" = "0" ] && cpucore="mips_softfloat" || cpucore="mipsle_softfloat"
fi
echo $cpucore
+3 -2
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=mosdns
PKG_VERSION:=5.3.4
PKG_RELEASE:=6
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/IrineSistiana/mosdns/tar.gz/v$(PKG_VERSION)?
@@ -18,10 +18,11 @@ PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
PKG_BUILD_FLAGS:=no-mips16
GO_PKG:=github.com/IrineSistiana/mosdns
GO_PKG_LDFLAGS_X:=main.version=v$(PKG_VERSION)-$(PKG_RELEASE)
GO_PKG_LDFLAGS_X:=main.version=v$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
@@ -1,238 +0,0 @@
From 201408484de552cb927f6a50b76a34d65c8ac8c6 Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Fri, 20 Mar 2026 10:39:22 +0800
Subject: [PATCH] mosdns: update dependencies
Signed-off-by: sbwml <admin@cooluc.com>
---
.github/workflows/release.yml | 2 +-
.github/workflows/test.yml | 2 +-
go.mod | 36 +++++++++----------
go.sum | 68 +++++++++++++++++------------------
pkg/query_context/context.go | 1 -
5 files changed, 54 insertions(+), 55 deletions(-)
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,7 +14,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
- go-version: "1.25"
+ go-version: "1.26"
check-latest: true
cache: true
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -12,7 +12,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
- go-version: "1.25"
+ go-version: "1.26"
check-latest: true
cache: true
--- a/go.mod
+++ b/go.mod
@@ -1,30 +1,30 @@
module github.com/IrineSistiana/mosdns/v5
-go 1.24.9
+go 1.25.0
require (
github.com/IrineSistiana/go-bytes-pool v0.0.0-20230918115058-c72bd9761c57
- github.com/go-chi/chi/v5 v5.2.3
- github.com/go-viper/mapstructure/v2 v2.4.0
+ github.com/go-chi/chi/v5 v5.2.5
+ github.com/go-viper/mapstructure/v2 v2.5.0
github.com/google/nftables v0.3.0
github.com/kardianos/service v1.2.4
- github.com/klauspost/compress v1.18.2
- github.com/miekg/dns v1.1.70
+ github.com/klauspost/compress v1.18.4
+ github.com/miekg/dns v1.1.72
github.com/mitchellh/mapstructure v1.5.0
github.com/nadoo/ipset v0.5.0
github.com/prometheus/client_golang v1.23.2
- github.com/quic-go/quic-go v0.58.1
+ github.com/quic-go/quic-go v0.59.0
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/vishvananda/netlink v1.3.0
go.uber.org/zap v1.27.1
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
- golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
- golang.org/x/net v0.48.0
- golang.org/x/sync v0.19.0
- golang.org/x/sys v0.40.0
- golang.org/x/time v0.14.0
+ golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
+ golang.org/x/net v0.52.0
+ golang.org/x/sync v0.20.0
+ golang.org/x/sys v0.42.0
+ golang.org/x/time v0.15.0
google.golang.org/protobuf v1.36.11
)
@@ -37,14 +37,14 @@ require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/mdlayher/netlink v1.8.0 // indirect
+ github.com/mdlayher/netlink v1.9.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
- github.com/prometheus/procfs v0.19.2 // indirect
+ github.com/prometheus/procfs v0.20.1 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
@@ -55,11 +55,11 @@ require (
github.com/vishvananda/netns v0.0.4 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
- go.yaml.in/yaml/v2 v2.4.3 // indirect
+ go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
- golang.org/x/crypto v0.46.0 // indirect
- golang.org/x/mod v0.32.0 // indirect
- golang.org/x/text v0.33.0 // indirect
- golang.org/x/tools v0.40.0 // indirect
+ golang.org/x/crypto v0.49.0 // indirect
+ golang.org/x/mod v0.34.0 // indirect
+ golang.org/x/text v0.35.0 // indirect
+ golang.org/x/tools v0.43.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
--- a/go.sum
+++ b/go.sum
@@ -13,10 +13,10 @@ github.com/frankban/quicktest v1.14.6 h1
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
-github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
-github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
-github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
+github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
+github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
+github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=
@@ -25,20 +25,20 @@ github.com/inconshreveable/mousetrap v1.
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kardianos/service v1.2.4 h1:XNlGtZOYNx2u91urOdg/Kfmc+gfmuIo1Dd3rEi2OgBk=
github.com/kardianos/service v1.2.4/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc=
-github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
-github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
+github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
+github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/mdlayher/netlink v1.8.0 h1:e7XNIYJKD7hUct3Px04RuIGJbBxy1/c4nX7D5YyvvlM=
-github.com/mdlayher/netlink v1.8.0/go.mod h1:UhgKXUlDQhzb09DrCl2GuRNEglHmhYoWAHid9HK3594=
+github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
+github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
-github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
-github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
+github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
+github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -53,12 +53,12 @@ github.com/prometheus/client_model v0.6.
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
-github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
-github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
+github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
+github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
-github.com/quic-go/quic-go v0.58.1 h1:J0s55TVauDbnCEY5tU2B8e7nYb3gnKMSez5Fwi1dl2s=
-github.com/quic-go/quic-go v0.58.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
+github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
+github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -91,32 +91,32 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSx
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
-go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
-go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
-golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
-golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
-golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
-golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
-golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
-golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
-golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
-golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
-golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
-golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
+golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
+golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
-golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
-golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
-golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
-golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
-golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
-golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
--- a/pkg/query_context/context.go
+++ b/pkg/query_context/context.go
@@ -108,7 +108,6 @@ func (ctx *Context) QQuestion() dns.Ques
// It's a helper func for searching opt in Q() manually.
func (ctx *Context) QOpt() *dns.OPT {
opt := findOpt(ctx.query)
- ctx.query.IsEdns0()
if opt == nil {
panic("query opt is missing")
}
@@ -9,7 +9,7 @@ Subject: [PATCH 3/5] add response for bad request in ServeHTTP handler
--- a/pkg/server/http_handler.go
+++ b/pkg/server/http_handler.go
@@ -91,6 +91,7 @@ func (h *HttpHandler) ServeHTTP(w http.R
@@ -93,6 +93,7 @@ func (h *HttpHandler) ServeHTTP(w http.R
if err != nil {
h.warnErr(req, "invalid request", err)
w.WriteHeader(http.StatusBadRequest)
@@ -1,697 +0,0 @@
From 9554c5b29aff4107504bdf45c35582e6d9743a93 Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Mon, 20 Apr 2026 23:06:17 +0800
Subject: [PATCH] add auto-reload rule files on modification
Signed-off-by: sbwml <admin@cooluc.com>
---
pkg/utils/file_watcher.go | 73 +++++++++++++++
plugin/data_provider/domain_set/domain_set.go | 71 +++++++++++---
plugin/data_provider/ip_set/ip_set.go | 75 +++++++++++----
plugin/executable/arbitrary/arbitrary.go | 81 +++++++++++-----
plugin/executable/hosts/hosts.go | 87 ++++++++++++-----
plugin/executable/redirect/redirect.go | 93 +++++++++++++------
6 files changed, 374 insertions(+), 106 deletions(-)
create mode 100644 pkg/utils/file_watcher.go
--- /dev/null
+++ b/pkg/utils/file_watcher.go
@@ -0,0 +1,73 @@
+package utils
+
+import (
+ "os"
+ "time"
+)
+
+// FileWatcher periodically checks the modification time of the specified files.
+type FileWatcher struct {
+ done chan struct{}
+}
+
+// StartFileWatcher starts a new FileWatcher that checks files every 'interval'.
+// If any file modification time changes or a file is missing, onChange is triggered.
+// Note: onChange must be safe to run concurrently with other components.
+func StartFileWatcher(files []string, interval time.Duration, onChange func(changedFiles []string)) *FileWatcher {
+ if len(files) == 0 {
+ return nil
+ }
+ fw := &FileWatcher{
+ done: make(chan struct{}),
+ }
+ go func() {
+ modTimes := make(map[string]time.Time)
+ for _, f := range files {
+ stat, err := os.Stat(f)
+ if err == nil {
+ modTimes[f] = stat.ModTime()
+ }
+ }
+ ticker := time.NewTicker(interval)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-fw.done:
+ return
+ case <-ticker.C:
+ var changedFiles []string
+ for _, f := range files {
+ stat, err := os.Stat(f)
+ if err != nil {
+ if os.IsNotExist(err) {
+ // File is missing or deleted.
+ if mt, ok := modTimes[f]; ok && !mt.IsZero() {
+ modTimes[f] = time.Time{} // mark as missing
+ changedFiles = append(changedFiles, f)
+ }
+ }
+ continue
+ }
+
+ if mt, ok := modTimes[f]; !ok || mt != stat.ModTime() {
+ modTimes[f] = stat.ModTime()
+ changedFiles = append(changedFiles, f)
+ }
+ }
+ if len(changedFiles) > 0 {
+ onChange(changedFiles)
+ }
+ }
+ }
+ }()
+ return fw
+}
+
+// Close stops the FileWatcher.
+func (fw *FileWatcher) Close() error {
+ if fw != nil {
+ close(fw.done)
+ }
+ return nil
+}
--- a/plugin/data_provider/domain_set/domain_set.go
+++ b/plugin/data_provider/domain_set/domain_set.go
@@ -22,10 +22,15 @@ package domain_set
import (
"bytes"
"fmt"
+ "os"
+ "sync/atomic"
+ "time"
+
"github.com/IrineSistiana/mosdns/v5/coremain"
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
"github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
- "os"
+ "go.uber.org/zap"
)
const PluginType = "domain_set"
@@ -51,33 +56,69 @@ type Args struct {
var _ data_provider.DomainMatcherProvider = (*DomainSet)(nil)
type DomainSet struct {
- mg []domain.Matcher[struct{}]
+ mg atomic.Pointer[MatcherGroup]
+ fw *utils.FileWatcher
}
func (d *DomainSet) GetDomainMatcher() domain.Matcher[struct{}] {
- return MatcherGroup(d.mg)
+ return d
+}
+
+func (d *DomainSet) Match(s string) (struct{}, bool) {
+ if mg := d.mg.Load(); mg != nil {
+ return mg.Match(s)
+ }
+ return struct{}{}, false
+}
+
+func (d *DomainSet) Close() error {
+ if d.fw != nil {
+ return d.fw.Close()
+ }
+ return nil
}
// NewDomainSet inits a DomainSet from given args.
func NewDomainSet(bp *coremain.BP, args *Args) (*DomainSet, error) {
ds := &DomainSet{}
- m := domain.NewDomainMixMatcher()
- if err := LoadExpsAndFiles(args.Exps, args.Files, m); err != nil {
- return nil, err
+ loadInner := func() (*MatcherGroup, error) {
+ var mg MatcherGroup
+ m := domain.NewDomainMixMatcher()
+ if err := LoadExpsAndFiles(args.Exps, args.Files, m); err != nil {
+ return nil, err
+ }
+ if m.Len() > 0 {
+ mg = append(mg, m)
+ }
+
+ for _, tag := range args.Sets {
+ provider, _ := bp.M().GetPlugin(tag).(data_provider.DomainMatcherProvider)
+ if provider == nil {
+ return nil, fmt.Errorf("%s is not a DomainMatcherProvider", tag)
+ }
+ m := provider.GetDomainMatcher()
+ mg = append(mg, m)
+ }
+ return &mg, nil
}
- if m.Len() > 0 {
- ds.mg = append(ds.mg, m)
+
+ mg, err := loadInner()
+ if err != nil {
+ return nil, err
}
+ ds.mg.Store(mg)
- for _, tag := range args.Sets {
- provider, _ := bp.M().GetPlugin(tag).(data_provider.DomainMatcherProvider)
- if provider == nil {
- return nil, fmt.Errorf("%s is not a DomainMatcherProvider", tag)
- }
- m := provider.GetDomainMatcher()
- ds.mg = append(ds.mg, m)
+ if len(args.Files) > 0 {
+ ds.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
+ newMg, err := loadInner()
+ if err == nil {
+ ds.mg.Store(newMg)
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
+ }
+ })
}
+
return ds, nil
}
--- a/plugin/data_provider/ip_set/ip_set.go
+++ b/plugin/data_provider/ip_set/ip_set.go
@@ -22,12 +22,17 @@ package ip_set
import (
"bytes"
"fmt"
- "github.com/IrineSistiana/mosdns/v5/coremain"
- "github.com/IrineSistiana/mosdns/v5/pkg/matcher/netlist"
- "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
"net/netip"
"os"
"strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/IrineSistiana/mosdns/v5/coremain"
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/netlist"
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
+ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
+ "go.uber.org/zap"
)
const PluginType = "ip_set"
@@ -49,31 +54,67 @@ type Args struct {
var _ data_provider.IPMatcherProvider = (*IPSet)(nil)
type IPSet struct {
- mg []netlist.Matcher
+ mg atomic.Pointer[MatcherGroup]
+ fw *utils.FileWatcher
}
func (d *IPSet) GetIPMatcher() netlist.Matcher {
- return MatcherGroup(d.mg)
+ return d
+}
+
+func (d *IPSet) Match(addr netip.Addr) bool {
+ if mg := d.mg.Load(); mg != nil {
+ return mg.Match(addr)
+ }
+ return false
+}
+
+func (d *IPSet) Close() error {
+ if d.fw != nil {
+ return d.fw.Close()
+ }
+ return nil
}
func NewIPSet(bp *coremain.BP, args *Args) (*IPSet, error) {
p := &IPSet{}
- l := netlist.NewList()
- if err := LoadFromIPsAndFiles(args.IPs, args.Files, l); err != nil {
- return nil, err
+ loadInner := func() (*MatcherGroup, error) {
+ var mg MatcherGroup
+ l := netlist.NewList()
+ if err := LoadFromIPsAndFiles(args.IPs, args.Files, l); err != nil {
+ return nil, err
+ }
+ l.Sort()
+ if l.Len() > 0 {
+ mg = append(mg, l)
+ }
+ for _, tag := range args.Sets {
+ provider, _ := bp.M().GetPlugin(tag).(data_provider.IPMatcherProvider)
+ if provider == nil {
+ return nil, fmt.Errorf("%s is not an IPMatcherProvider", tag)
+ }
+ mg = append(mg, provider.GetIPMatcher())
+ }
+ return &mg, nil
}
- l.Sort()
- if l.Len() > 0 {
- p.mg = append(p.mg, l)
+
+ mg, err := loadInner()
+ if err != nil {
+ return nil, err
}
- for _, tag := range args.Sets {
- provider, _ := bp.M().GetPlugin(tag).(data_provider.IPMatcherProvider)
- if provider == nil {
- return nil, fmt.Errorf("%s is not an IPMatcherProvider", tag)
- }
- p.mg = append(p.mg, provider.GetIPMatcher())
+ p.mg.Store(mg)
+
+ if len(args.Files) > 0 {
+ p.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
+ newMg, err := loadInner()
+ if err == nil {
+ p.mg.Store(newMg)
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
+ }
+ })
}
+
return p, nil
}
--- a/plugin/executable/arbitrary/arbitrary.go
+++ b/plugin/executable/arbitrary/arbitrary.go
@@ -23,12 +23,17 @@ import (
"bytes"
"context"
"fmt"
+ "os"
+ "strings"
+ "sync/atomic"
+ "time"
+
"github.com/IrineSistiana/mosdns/v5/coremain"
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
"github.com/IrineSistiana/mosdns/v5/pkg/zone_file"
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
- "os"
- "strings"
+ "go.uber.org/zap"
)
const PluginType = "arbitrary"
@@ -45,38 +50,68 @@ type Args struct {
var _ sequence.Executable = (*Arbitrary)(nil)
type Arbitrary struct {
- m *zone_file.Matcher
+ m atomic.Pointer[zone_file.Matcher]
+ fw *utils.FileWatcher
}
-func NewArbitrary(args *Args) (*Arbitrary, error) {
- m := new(zone_file.Matcher)
- for i, s := range args.Rules {
- if err := m.Load(strings.NewReader(s)); err != nil {
- return nil, fmt.Errorf("failed to load rr #%d [%s], %w", i, s, err)
- }
- }
- for i, file := range args.Files {
- b, err := os.ReadFile(file)
- if err != nil {
- return nil, fmt.Errorf("failed to read file #%d [%s], %w", i, file, err)
+func NewArbitrary(bp *coremain.BP, args *Args) (*Arbitrary, error) {
+ a := &Arbitrary{}
+
+ loadInner := func() (*zone_file.Matcher, error) {
+ m := new(zone_file.Matcher)
+ for i, s := range args.Rules {
+ if err := m.Load(strings.NewReader(s)); err != nil {
+ return nil, fmt.Errorf("failed to load rr #%d [%s], %w", i, s, err)
+ }
}
- if err := m.Load(bytes.NewReader(b)); err != nil {
- return nil, fmt.Errorf("failed to load rr file #%d [%s], %w", i, file, err)
+ for i, file := range args.Files {
+ b, err := os.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read file #%d [%s], %w", i, file, err)
+ }
+ if err := m.Load(bytes.NewReader(b)); err != nil {
+ return nil, fmt.Errorf("failed to load rr file #%d [%s], %w", i, file, err)
+ }
}
+ return m, nil
+ }
+
+ inner, err := loadInner()
+ if err != nil {
+ return nil, err
+ }
+ a.m.Store(inner)
+
+ if len(args.Files) > 0 {
+ a.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
+ newInner, err := loadInner()
+ if err == nil {
+ a.m.Store(newInner)
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
+ }
+ })
}
- return &Arbitrary{
- m: m,
- }, nil
+
+ return a, nil
}
func (a *Arbitrary) Exec(_ context.Context, qCtx *query_context.Context) error {
- if r := a.m.Reply(qCtx.Q()); r != nil {
- qCtx.SetResponse(r)
+ if inner := a.m.Load(); inner != nil {
+ if r := inner.Reply(qCtx.Q()); r != nil {
+ qCtx.SetResponse(r)
+ }
+ }
+ return nil
+}
+
+func (a *Arbitrary) Close() error {
+ if a.fw != nil {
+ return a.fw.Close()
}
return nil
}
-func Init(_ *coremain.BP, v any) (any, error) {
+func Init(bp *coremain.BP, v any) (any, error) {
args := v.(*Args)
- return NewArbitrary(args)
+ return NewArbitrary(bp, args)
}
--- a/plugin/executable/hosts/hosts.go
+++ b/plugin/executable/hosts/hosts.go
@@ -23,13 +23,18 @@ import (
"bytes"
"context"
"fmt"
+ "os"
+ "sync/atomic"
+ "time"
+
"github.com/IrineSistiana/mosdns/v5/coremain"
"github.com/IrineSistiana/mosdns/v5/pkg/hosts"
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
"github.com/miekg/dns"
- "os"
+ "go.uber.org/zap"
)
const PluginType = "hosts"
@@ -46,44 +51,76 @@ type Args struct {
}
type Hosts struct {
- h *hosts.Hosts
+ h atomic.Pointer[hosts.Hosts]
+ fw *utils.FileWatcher
}
-func Init(_ *coremain.BP, args any) (any, error) {
- return NewHosts(args.(*Args))
+func Init(bp *coremain.BP, args any) (any, error) {
+ return NewHosts(bp, args.(*Args))
}
-func NewHosts(args *Args) (*Hosts, error) {
- m := domain.NewMixMatcher[*hosts.IPs]()
- m.SetDefaultMatcher(domain.MatcherFull)
- for i, entry := range args.Entries {
- if err := domain.Load[*hosts.IPs](m, entry, hosts.ParseIPs); err != nil {
- return nil, fmt.Errorf("failed to load entry #%d %s, %w", i, entry, err)
- }
- }
- for i, file := range args.Files {
- b, err := os.ReadFile(file)
- if err != nil {
- return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
+func NewHosts(bp *coremain.BP, args *Args) (*Hosts, error) {
+ h := &Hosts{}
+
+ loadInner := func() (*hosts.Hosts, error) {
+ m := domain.NewMixMatcher[*hosts.IPs]()
+ m.SetDefaultMatcher(domain.MatcherFull)
+ for i, entry := range args.Entries {
+ if err := domain.Load[*hosts.IPs](m, entry, hosts.ParseIPs); err != nil {
+ return nil, fmt.Errorf("failed to load entry #%d %s, %w", i, entry, err)
+ }
}
- if err := domain.LoadFromTextReader[*hosts.IPs](m, bytes.NewReader(b), hosts.ParseIPs); err != nil {
- return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
+ for i, file := range args.Files {
+ b, err := os.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
+ }
+ if err := domain.LoadFromTextReader[*hosts.IPs](m, bytes.NewReader(b), hosts.ParseIPs); err != nil {
+ return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
+ }
}
+ return hosts.NewHosts(m), nil
+ }
+
+ inner, err := loadInner()
+ if err != nil {
+ return nil, err
}
+ h.h.Store(inner)
- return &Hosts{
- h: hosts.NewHosts(m),
- }, nil
+ if len(args.Files) > 0 {
+ h.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
+ newInner, err := loadInner()
+ if err == nil {
+ h.h.Store(newInner)
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
+ }
+ })
+ }
+
+ return h, nil
}
func (h *Hosts) Response(q *dns.Msg) *dns.Msg {
- return h.h.LookupMsg(q)
+ if inner := h.h.Load(); inner != nil {
+ return inner.LookupMsg(q)
+ }
+ return nil
}
func (h *Hosts) Exec(_ context.Context, qCtx *query_context.Context) error {
- r := h.h.LookupMsg(qCtx.Q())
- if r != nil {
- qCtx.SetResponse(r)
+ if inner := h.h.Load(); inner != nil {
+ r := inner.LookupMsg(qCtx.Q())
+ if r != nil {
+ qCtx.SetResponse(r)
+ }
+ }
+ return nil
+}
+
+func (h *Hosts) Close() error {
+ if h.fw != nil {
+ return h.fw.Close()
}
return nil
}
--- a/plugin/executable/redirect/redirect.go
+++ b/plugin/executable/redirect/redirect.go
@@ -25,10 +25,13 @@ import (
"fmt"
"os"
"strings"
+ "sync/atomic"
+ "time"
"github.com/IrineSistiana/mosdns/v5/coremain"
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
"github.com/miekg/dns"
"go.uber.org/zap"
@@ -48,11 +51,12 @@ type Args struct {
}
type Redirect struct {
- m *domain.MixMatcher[string]
+ m atomic.Pointer[domain.MixMatcher[string]]
+ fw *utils.FileWatcher
}
func Init(bp *coremain.BP, args any) (any, error) {
- r, err := NewRedirect(args.(*Args))
+ r, err := NewRedirect(bp, args.(*Args))
if err != nil {
return nil, err
}
@@ -60,7 +64,8 @@ func Init(bp *coremain.BP, args any) (an
return r, nil
}
-func NewRedirect(args *Args) (*Redirect, error) {
+func NewRedirect(bp *coremain.BP, args *Args) (*Redirect, error) {
+ r := &Redirect{}
parseFunc := func(s string) (p, v string, err error) {
f := strings.Fields(s)
if len(f) != 2 {
@@ -68,23 +73,44 @@ func NewRedirect(args *Args) (*Redirect,
}
return f[0], dns.Fqdn(f[1]), nil
}
- m := domain.NewMixMatcher[string]()
- m.SetDefaultMatcher(domain.MatcherFull)
- for i, rule := range args.Rules {
- if err := domain.Load[string](m, rule, parseFunc); err != nil {
- return nil, fmt.Errorf("failed to load rule #%d %s, %w", i, rule, err)
- }
- }
- for i, file := range args.Files {
- b, err := os.ReadFile(file)
- if err != nil {
- return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
+
+ loadInner := func() (*domain.MixMatcher[string], error) {
+ m := domain.NewMixMatcher[string]()
+ m.SetDefaultMatcher(domain.MatcherFull)
+ for i, rule := range args.Rules {
+ if err := domain.Load[string](m, rule, parseFunc); err != nil {
+ return nil, fmt.Errorf("failed to load rule #%d %s, %w", i, rule, err)
+ }
}
- if err := domain.LoadFromTextReader[string](m, bytes.NewReader(b), parseFunc); err != nil {
- return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
+ for i, file := range args.Files {
+ b, err := os.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
+ }
+ if err := domain.LoadFromTextReader[string](m, bytes.NewReader(b), parseFunc); err != nil {
+ return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
+ }
}
+ return m, nil
+ }
+
+ inner, err := loadInner()
+ if err != nil {
+ return nil, err
+ }
+ r.m.Store(inner)
+
+ if len(args.Files) > 0 {
+ r.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
+ newInner, err := loadInner()
+ if err == nil {
+ r.m.Store(newInner)
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles), zap.Int("length", newInner.Len()))
+ }
+ })
}
- return &Redirect{m: m}, nil
+
+ return r, nil
}
func (r *Redirect) Exec(ctx context.Context, qCtx *query_context.Context, next sequence.ChainWalker) error {
@@ -94,7 +120,12 @@ func (r *Redirect) Exec(ctx context.Cont
}
orgQName := q.Question[0].Name
- redirectTarget, ok := r.m.Match(orgQName)
+ inner := r.m.Load()
+ if inner == nil {
+ return next.ExecNext(ctx, qCtx)
+ }
+
+ redirectTarget, ok := inner.Match(orgQName)
if !ok {
return next.ExecNext(ctx, qCtx)
}
@@ -104,16 +135,16 @@ func (r *Redirect) Exec(ctx context.Cont
q.Question[0].Name = orgQName
}()
err := next.ExecNext(ctx, qCtx)
- if r := qCtx.R(); r != nil {
+ if rResp := qCtx.R(); rResp != nil {
// Restore original query name.
- for i := range r.Question {
- if r.Question[i].Name == redirectTarget {
- r.Question[i].Name = orgQName
+ for i := range rResp.Question {
+ if rResp.Question[i].Name == redirectTarget {
+ rResp.Question[i].Name = orgQName
}
}
// Insert a CNAME record.
- newAns := make([]dns.RR, 1, len(r.Answer)+1)
+ newAns := make([]dns.RR, 1, len(rResp.Answer)+1)
newAns[0] = &dns.CNAME{
Hdr: dns.RR_Header{
Name: orgQName,
@@ -123,12 +154,22 @@ func (r *Redirect) Exec(ctx context.Cont
},
Target: redirectTarget,
}
- newAns = append(newAns, r.Answer...)
- r.Answer = newAns
+ newAns = append(newAns, rResp.Answer...)
+ rResp.Answer = newAns
}
return err
}
func (r *Redirect) Len() int {
- return r.m.Len()
+ if inner := r.m.Load(); inner != nil {
+ return inner.Len()
+ }
+ return 0
+}
+
+func (r *Redirect) Close() error {
+ if r.fw != nil {
+ return r.fw.Close()
+ }
+ return nil
}
@@ -1,427 +0,0 @@
From 64614017e674960638bac87b4a2f9c0aea0065e6 Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Fri, 1 May 2026 01:35:59 +0800
Subject: [PATCH] add adblock_set database plugin
Signed-off-by: sbwml <admin@cooluc.com>
---
.../data_provider/adblock_set/adblock_set.go | 122 ++++++++++++++++++
.../adblock_set/adblock_set_test.go | 107 +++++++++++++++
plugin/data_provider/adblock_set/matcher.go | 43 ++++++
plugin/data_provider/adblock_set/parser.go | 115 +++++++++++++++++
plugin/enabled_plugins.go | 1 +
5 files changed, 388 insertions(+)
create mode 100644 plugin/data_provider/adblock_set/adblock_set.go
create mode 100644 plugin/data_provider/adblock_set/adblock_set_test.go
create mode 100644 plugin/data_provider/adblock_set/matcher.go
create mode 100644 plugin/data_provider/adblock_set/parser.go
--- /dev/null
+++ b/plugin/data_provider/adblock_set/adblock_set.go
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2025, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package adblock_set
+
+import (
+ "fmt"
+ "os"
+ "sync/atomic"
+ "time"
+
+ "github.com/IrineSistiana/mosdns/v5/coremain"
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
+ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
+ "go.uber.org/zap"
+)
+
+const PluginType = "adblock_set"
+
+func init() {
+ coremain.RegNewPluginFunc(PluginType, Init, func() any { return new(Args) })
+}
+
+func Init(bp *coremain.BP, args any) (any, error) {
+ m, err := NewAdblockSet(bp, args.(*Args))
+ if err != nil {
+ return nil, err
+ }
+ return m, nil
+}
+
+type Args struct {
+ Files []string `yaml:"files"`
+}
+
+var _ data_provider.DomainMatcherProvider = (*AdblockSet)(nil)
+
+type AdblockSet struct {
+ matcher atomic.Pointer[AdblockMatcher]
+ fw *utils.FileWatcher
+}
+
+func (d *AdblockSet) GetDomainMatcher() domain.Matcher[struct{}] {
+ return d
+}
+
+func (d *AdblockSet) Match(s string) (struct{}, bool) {
+ if m := d.matcher.Load(); m != nil {
+ return m.Match(s)
+ }
+ return struct{}{}, false
+}
+
+func (d *AdblockSet) Close() error {
+ if d.fw != nil {
+ return d.fw.Close()
+ }
+ return nil
+}
+
+func NewAdblockSet(bp *coremain.BP, args *Args) (*AdblockSet, error) {
+ ds := &AdblockSet{}
+
+ loadInner := func() (*AdblockMatcher, error) {
+ m := &AdblockMatcher{
+ blacklist: domain.NewMixMatcher[struct{}](),
+ whitelist: domain.NewMixMatcher[struct{}](),
+ }
+ for _, f := range args.Files {
+ if len(f) == 0 {
+ continue
+ }
+ file, err := os.Open(f)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open file %s: %w", f, err)
+ }
+ if err := ParseRules(file, m.blacklist, m.whitelist); err != nil {
+ file.Close()
+ return nil, fmt.Errorf("failed to parse rules from %s: %w", f, err)
+ }
+ file.Close()
+ }
+ return m, nil
+ }
+
+ m, err := loadInner()
+ if err != nil {
+ return nil, err
+ }
+ ds.matcher.Store(m)
+
+ if len(args.Files) > 0 {
+ ds.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
+ newM, err := loadInner()
+ if err == nil {
+ ds.matcher.Store(newM)
+ bp.L().Info("reloaded adblock files", zap.Strings("files", changedFiles))
+ } else {
+ bp.L().Error("failed to reload adblock files", zap.Error(err))
+ }
+ })
+ }
+
+ return ds, nil
+}
--- /dev/null
+++ b/plugin/data_provider/adblock_set/adblock_set_test.go
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2025, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package adblock_set
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseAndMatch(t *testing.T) {
+ rules := `
+! Comment
+# Another comment
+||example.com^
+@@||whitelist.com^
+|exact.com|
+@@|white-exact.com|
+/.*regex.*/
+@@/.*white-regex.*/
+*keyword*
+@@*white-keyword*
+blocked.com
+domain:ntp.org
+full:metrics.icloud.com
+`
+ blacklist := domain.NewMixMatcher[struct{}]()
+ whitelist := domain.NewMixMatcher[struct{}]()
+
+ err := ParseRules(strings.NewReader(rules), blacklist, whitelist)
+ assert.NoError(t, err)
+
+ matcher := &AdblockMatcher{
+ blacklist: blacklist,
+ whitelist: whitelist,
+ }
+
+ tests := []struct {
+ domain string
+ want bool
+ }{
+ {"example.com", true},
+ {"sub.example.com", true},
+ {"whitelist.com", false},
+ {"sub.whitelist.com", false},
+ {"exact.com", true},
+ {"sub.exact.com", false}, // |exact.com| is exact match
+ {"white-exact.com", false},
+ {"myregex123", true},
+ {"mywhite-regex123", false},
+ {"somekeywordhere", true},
+ {"some-white-keyword-here", false},
+ {"blocked.com", true},
+ {"other.com", false},
+ {"ntp.org", true}, // matched by domain:ntp.org
+ {"sub.ntp.org", true}, // matched by domain:ntp.org
+ {"metrics.icloud.com", true}, // matched by full:metrics.icloud.com
+ {"sub.metrics.icloud.com", false}, // full match only
+ }
+
+ for _, tt := range tests {
+ _, ok := matcher.Match(tt.domain)
+ assert.Equal(t, tt.want, ok, "Match(%s)", tt.domain)
+ }
+}
+
+func TestStripOptions(t *testing.T) {
+ rules := `
+||example.com^$important,third-party
+@@||whitelist.com^$dnstype=A
+`
+ blacklist := domain.NewMixMatcher[struct{}]()
+ whitelist := domain.NewMixMatcher[struct{}]()
+
+ err := ParseRules(strings.NewReader(rules), blacklist, whitelist)
+ assert.NoError(t, err)
+
+ matcher := &AdblockMatcher{
+ blacklist: blacklist,
+ whitelist: whitelist,
+ }
+
+ _, ok := matcher.Match("example.com")
+ assert.True(t, ok)
+
+ _, ok = matcher.Match("whitelist.com")
+ assert.False(t, ok)
+}
--- /dev/null
+++ b/plugin/data_provider/adblock_set/matcher.go
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package adblock_set
+
+import (
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+)
+
+type AdblockMatcher struct {
+ blacklist *domain.MixMatcher[struct{}]
+ whitelist *domain.MixMatcher[struct{}]
+}
+
+func (m *AdblockMatcher) Match(s string) (struct{}, bool) {
+ // 1. Check whitelist. If matched, it's NOT a hit for the adblock set.
+ if _, ok := m.whitelist.Match(s); ok {
+ return struct{}{}, false
+ }
+
+ // 2. Check blacklist.
+ if _, ok := m.blacklist.Match(s); ok {
+ return struct{}{}, true
+ }
+
+ return struct{}{}, false
+}
--- /dev/null
+++ b/plugin/data_provider/adblock_set/parser.go
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2025, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package adblock_set
+
+import (
+ "bufio"
+ "io"
+ "strings"
+
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+)
+
+// ParseRules reads AdBlock rules from r and adds them to blacklist and whitelist matchers.
+func ParseRules(r io.Reader, blacklist, whitelist *domain.MixMatcher[struct{}]) error {
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" || line[0] == '!' || line[0] == '#' {
+ continue
+ }
+
+ // Handle whitelists
+ isWhitelist := false
+ if strings.HasPrefix(line, "@@") {
+ isWhitelist = true
+ line = line[2:]
+ }
+
+ // Handle comments in rules (e.g., rule # comment)
+ // But in AdBlock, # is usually for element hiding (##) or CSS selectors.
+ // For DNS rules, we ignore anything starting with #.
+ if strings.Contains(line, "##") || strings.Contains(line, "#?#") || strings.Contains(line, "#$#") {
+ continue
+ }
+
+ // Strip options (e.g., $important, $third-party)
+ if idx := strings.LastIndex(line, "$"); idx != -1 {
+ // Check if it's a regex or something else.
+ // In ABP, $ marks the start of options.
+ line = line[:idx]
+ }
+
+ if line == "" {
+ continue
+ }
+
+ target := blacklist
+ if isWhitelist {
+ target = whitelist
+ }
+
+ if err := parseAndAddRule(line, target); err != nil {
+ // Skip invalid rules but maybe we should log them?
+ continue
+ }
+ }
+ return scanner.Err()
+}
+
+func parseAndAddRule(rule string, m *domain.MixMatcher[struct{}]) error {
+ // 0. Native MosDNS rules: type:pattern
+ if strings.Contains(rule, ":") {
+ typ, _, _ := strings.Cut(rule, ":")
+ switch typ {
+ case "full", "domain", "regexp", "keyword":
+ return m.Add(rule, struct{}{})
+ }
+ }
+
+ // 1. Regex: /regexp/
+ if strings.HasPrefix(rule, "/") && strings.HasSuffix(rule, "/") && len(rule) > 2 {
+ return m.Add("regexp:"+rule[1:len(rule)-1], struct{}{})
+ }
+
+ // 2. Subdomain: ||example.com^
+ if strings.HasPrefix(rule, "||") {
+ domainStr := rule[2:]
+ if strings.HasSuffix(domainStr, "^") {
+ domainStr = domainStr[:len(domainStr)-1]
+ }
+ return m.Add("domain:"+domainStr, struct{}{})
+ }
+
+ // 3. Exact match: |example.com|
+ if strings.HasPrefix(rule, "|") && strings.HasSuffix(rule, "|") && len(rule) > 2 {
+ return m.Add("full:"+rule[1:len(rule)-1], struct{}{})
+ }
+
+ // 4. Keyword: *keyword*
+ if strings.HasPrefix(rule, "*") && strings.HasSuffix(rule, "*") && len(rule) > 2 {
+ return m.Add("keyword:"+rule[1:len(rule)-1], struct{}{})
+ }
+
+ // 5. Default: treat as exact match if it looks like a domain
+ // Many lists are just a list of domains.
+ // AdGuard Home treats rules without || or ^ as exact matches.
+ return m.Add("full:"+rule, struct{}{})
+}
--- a/plugin/enabled_plugins.go
+++ b/plugin/enabled_plugins.go
@@ -22,6 +22,7 @@ package plugin
// data providers
import (
// data provider
+ _ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/adblock_set"
_ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/domain_set"
_ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/ip_set"
@@ -1,64 +0,0 @@
From 8f9c6ca1ac8e9b5e10a5c45f3f338546424c95f1 Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Sat, 2 May 2026 13:43:31 +0800
Subject: [PATCH] feat(adblock_set): support hosts file format blocking rules
Parse lines like "0.0.0.0 domain" and "127.0.0.1 domain" as exact-match
blocking rules, compatible with AdGuard Home hosts-style lists.
Signed-off-by: sbwml <admin@cooluc.com>
---
plugin/data_provider/adblock_set/adblock_set_test.go | 6 ++++++
plugin/data_provider/adblock_set/parser.go | 12 ++++++++++++
2 files changed, 18 insertions(+)
--- a/plugin/data_provider/adblock_set/adblock_set_test.go
+++ b/plugin/data_provider/adblock_set/adblock_set_test.go
@@ -42,6 +42,8 @@ func TestParseAndMatch(t *testing.T) {
blocked.com
domain:ntp.org
full:metrics.icloud.com
+0.0.0.0 singlehotmilf.online
+127.0.0.1 deezer.deliveryengine.adswizz.com
`
blacklist := domain.NewMixMatcher[struct{}]()
whitelist := domain.NewMixMatcher[struct{}]()
@@ -75,6 +77,10 @@ full:metrics.icloud.com
{"sub.ntp.org", true}, // matched by domain:ntp.org
{"metrics.icloud.com", true}, // matched by full:metrics.icloud.com
{"sub.metrics.icloud.com", false}, // full match only
+ {"singlehotmilf.online", true}, // hosts: 0.0.0.0
+ {"sub.singlehotmilf.online", false}, // hosts: exact match only
+ {"deezer.deliveryengine.adswizz.com", true}, // hosts: 127.0.0.1
+ {"sub.deezer.deliveryengine.adswizz.com", false}, // hosts: exact match only
}
for _, tt := range tests {
--- a/plugin/data_provider/adblock_set/parser.go
+++ b/plugin/data_provider/adblock_set/parser.go
@@ -22,6 +22,7 @@ package adblock_set
import (
"bufio"
"io"
+ "net"
"strings"
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
@@ -36,6 +37,17 @@ func ParseRules(r io.Reader, blacklist,
continue
}
+ // Handle hosts file format: "0.0.0.0 domain" or "127.0.0.1 domain"
+ if fields := strings.Fields(line); len(fields) >= 2 {
+ if ip := net.ParseIP(fields[0]); ip != nil {
+ host := fields[1]
+ if host != "" && host != "localhost" {
+ _ = blacklist.Add("full:"+host, struct{}{})
+ }
+ continue
+ }
+ }
+
// Handle whitelists
isWhitelist := false
if strings.HasPrefix(line, "@@") {
@@ -1,280 +0,0 @@
From e2a50186add36064f78015b163d725f1fe9821d0 Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Thu, 2 Jul 2026 22:37:40 +0800
Subject: [PATCH] feat: add size and backups config for log file rotation
- `log.size`: limits active log file size, supports K/M/G/T (default 1M)
- `log.backups`: limits max retained backup files (default 3)
Signed-off-by: sbwml <admin@cooluc.com>
---
mlog/logger.go | 25 +++++-
mlog/rotator.go | 219 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 241 insertions(+), 3 deletions(-)
create mode 100644 mlog/rotator.go
--- a/mlog/logger.go
+++ b/mlog/logger.go
@@ -38,6 +38,14 @@ type LogConfig struct {
// Production enables json output.
Production bool `yaml:"production"`
+
+ // Size of log file limit. Default is 1M.
+ // Supported units: K, M, G, T. If no unit, defaults to M.
+ Size string `yaml:"size"`
+
+ // Max number of old log files to retain. Default is 3.
+ // Set to a negative number to retain all files.
+ Backups int `yaml:"backups"`
}
var (
@@ -57,11 +65,22 @@ func NewLogger(lc LogConfig) (*zap.Logge
var out zapcore.WriteSyncer
if lf := lc.File; len(lf) > 0 {
- f, _, err := zap.Open(lf)
+ maxSize, err := ParseSize(lc.Size)
if err != nil {
- return nil, fmt.Errorf("open log file: %w", err)
+ return nil, fmt.Errorf("invalid log size limit: %w", err)
+ }
+
+ backups := lc.Backups
+ if backups == 0 {
+ backups = 3
+ }
+
+ rotator := &Rotator{
+ Filename: lf,
+ MaxSize: maxSize,
+ MaxBackups: backups,
}
- out = zapcore.Lock(f)
+ out = zapcore.AddSync(rotator)
} else {
out = stderr
}
--- /dev/null
+++ b/mlog/rotator.go
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2020-2022, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package mlog
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+type Rotator struct {
+ Filename string
+ MaxSize int64
+ MaxBackups int
+
+ size int64
+ f *os.File
+ mu sync.Mutex
+}
+
+func (r *Rotator) Write(p []byte) (n int, err error) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ if r.f == nil {
+ if err := r.open(); err != nil {
+ return 0, err
+ }
+ }
+
+ writeLen := int64(len(p))
+ if r.MaxSize > 0 && r.size+writeLen > r.MaxSize {
+ if err := r.rotate(); err != nil {
+ return 0, err
+ }
+ }
+
+ n, err = r.f.Write(p)
+ r.size += int64(n)
+ return n, err
+}
+
+func (r *Rotator) open() error {
+ info, err := os.Stat(r.Filename)
+ if os.IsNotExist(err) {
+ return r.openNew()
+ }
+ if err != nil {
+ return err
+ }
+ r.size = info.Size()
+ f, err := os.OpenFile(r.Filename, os.O_APPEND|os.O_WRONLY, 0644)
+ if err != nil {
+ return r.openNew()
+ }
+ r.f = f
+ return nil
+}
+
+func (r *Rotator) openNew() error {
+ err := os.MkdirAll(filepath.Dir(r.Filename), 0755)
+ if err != nil {
+ return err
+ }
+ f, err := os.OpenFile(r.Filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+ if err != nil {
+ return err
+ }
+ r.f = f
+ r.size = 0
+ return nil
+}
+
+func (r *Rotator) rotate() error {
+ if r.f != nil {
+ r.f.Close()
+ r.f = nil
+ }
+ _, err := os.Stat(r.Filename)
+ if err == nil {
+ ext := filepath.Ext(r.Filename)
+ name := strings.TrimSuffix(r.Filename, ext)
+ // Windows doesn't allow colons in filenames, so use dashes for time
+ newName := fmt.Sprintf("%s-%s%s", name, time.Now().Format("2006-01-02T15-04-05.000"), ext)
+ os.Rename(r.Filename, newName)
+ }
+ err = r.openNew()
+ if err == nil {
+ r.cleanupBackups()
+ }
+ return err
+}
+
+func (r *Rotator) cleanupBackups() {
+ if r.MaxBackups < 0 {
+ return // < 0 means keep all
+ }
+
+ dir := filepath.Dir(r.Filename)
+ ext := filepath.Ext(r.Filename)
+ name := strings.TrimSuffix(filepath.Base(r.Filename), ext)
+
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return
+ }
+
+ var backups []string
+ prefix := name + "-"
+ for _, entry := range entries {
+ if entry.IsDir() {
+ continue
+ }
+ n := entry.Name()
+ if strings.HasPrefix(n, prefix) && strings.HasSuffix(n, ext) {
+ // Basic validation of the timestamp length
+ timestamp := n[len(prefix) : len(n)-len(ext)]
+ if len(timestamp) == 23 {
+ backups = append(backups, filepath.Join(dir, n))
+ }
+ }
+ }
+
+ if len(backups) <= r.MaxBackups {
+ return
+ }
+
+ // Sort alphabetically, which sorts by time since format is 2006-01-02T15-04-05.000
+ sort.Strings(backups)
+
+ toDelete := len(backups) - r.MaxBackups
+ for i := 0; i < toDelete; i++ {
+ os.Remove(backups[i])
+ }
+}
+
+func (r *Rotator) Sync() error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.f != nil {
+ return r.f.Sync()
+ }
+ return nil
+}
+
+func (r *Rotator) Close() error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.f != nil {
+ err := r.f.Close()
+ r.f = nil
+ return err
+ }
+ return nil
+}
+
+// ParseSize parses the size string and returns bytes.
+// Supports K, M, G, T units. If no unit, defaults to M.
+func ParseSize(s string) (int64, error) {
+ if s == "" {
+ return 1024 * 1024, nil // Default 1M
+ }
+ s = strings.ToUpper(strings.TrimSpace(s))
+
+ var unit string
+ var valStr string
+ for i, c := range s {
+ if c >= 'A' && c <= 'Z' {
+ valStr = s[:i]
+ unit = s[i:]
+ break
+ }
+ }
+ if unit == "" {
+ valStr = s
+ unit = "M" // default to M
+ }
+ valStr = strings.TrimSpace(valStr)
+
+ val, err := strconv.ParseInt(valStr, 10, 64)
+ if err != nil {
+ return 0, err
+ }
+
+ switch unit {
+ case "K", "KB":
+ return val * 1024, nil
+ case "M", "MB":
+ return val * 1024 * 1024, nil
+ case "G", "GB":
+ return val * 1024 * 1024 * 1024, nil
+ case "T", "TB":
+ return val * 1024 * 1024 * 1024 * 1024, nil
+ default:
+ return 0, fmt.Errorf("invalid size unit: %s", unit)
+ }
+}
+7 -6
View File
@@ -5,22 +5,23 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=uugamebooster
PKG_VERSION:=12.1.4
PKG_VERSION:=14.0.6
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(ARCH).tar.gz
PKG_SOURCE_URL:=https://uurouter.gdl.netease.com/uuplugin/openwrt-$(ARCH)/v$(PKG_VERSION)/uu.tar.gz?
ifeq ($(ARCH),aarch64)
PKG_HASH:=f140dc99af5a67367e63c811ba5b555c748dea32c3b11c6b5bde6afc25ac960c
PKG_HASH:=4fe6fcad525c525ad530af69f102d90c1189330e4e3c3183b577ddde27731e2a
else ifeq ($(ARCH),arm)
PKG_HASH:=d531ff8ccb8b14abbac2d46d8cf1697114c981b059b9fb88adc19322be1ec2af
PKG_HASH:=e8693a284d0a9445e73f8d2e9c3d330364cc6371d63c19da03f8e8c47f076caf
else ifeq ($(ARCH),mipsel)
PKG_HASH:=c9003945872a106fd46207afb130a6741ee832bdc3273e2257f35a092cfd966a
PKG_HASH:=6fd2497bfdcf228de050119f7f593547a0da3eca9c50baa6bf8d5da6e366c575
else ifeq ($(ARCH),x86_64)
PKG_HASH:=1dd419ac166b124f8d83e4075a65b6547ba589f976894065ce551a66313a6e84
PKG_HASH:=1becd5ef81d257b22002f208cd303b04e421c7da4231523b7d82da2c7193b57f
endif
PKG_LICENSE:=Proprietary
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
include $(INCLUDE_DIR)/package.mk
@@ -61,4 +62,4 @@ define Package/uugamebooster/install
$(INSTALL_BIN) $(CURDIR)/files/uugamebooster.init $(1)/etc/init.d/uugamebooster
endef
$(eval $(call BuildPackage,uugamebooster))
$(eval $(call BuildPackage,uugamebooster))
-1
View File
@@ -1,4 +1,3 @@
config uugamebooster 'config'
option enabled '0'
+1 -1
View File
@@ -34,4 +34,4 @@ reload_service() {
service_triggers() {
procd_add_reload_trigger "$CONF"
}
}
Executable → Regular
+2 -2
View File
@@ -19,7 +19,7 @@ for ARCH in "aarch64" "arm" "mipsel" "x86_64"; do
echo -e "Version mismatch. Expected version: $VERSION, got $FILE_VER."
exit 1
else
curl -fsSL "http://uurouter.gdl.netease.com/uuplugin/openwrt-$ARCH/v$VERSION/uu.tar.gz" -o "$CURDIR/uu-$ARCH.tar.gz"
curl -fsSL "http://uurouter.gdl04.netease.com/uuplugin/openwrt-$ARCH/v$VERSION/uu.tar.gz" -o "$CURDIR/uu-$ARCH.tar.gz"
ACTUAL_MD5="$(md5sum "$CURDIR/uu-$ARCH.tar.gz" | awk '{print $1}')"
if [ "$ACTUAL_MD5" != "$FILE_MD5" ]; then
echo -e "HASH mismatch. Expected md5: $FILE_MD5, got $ACTUAL_MD5."
@@ -32,4 +32,4 @@ for ARCH in "aarch64" "arm" "mipsel" "x86_64"; do
fi
done
sed -i "s,PKG_VERSION:=.*,PKG_VERSION:=$VERSION,g" "$CURDIR/Makefile"
sed -i "s,PKG_VERSION:=.*,PKG_VERSION:=$VERSION,g" "$CURDIR/Makefile"
+8 -9
View File
@@ -7,14 +7,13 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=v2dat
PKG_SOURCE_DATE:=2022-12-15
PKG_SOURCE_VERSION:=47b8ee51fb528e11e1a83453b7e767a18d20d1f7
PKG_RELEASE:=4
PKG_VERSION:=0.1.5
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_SOURCE_DATE).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/urlesistiana/v2dat/tar.gz/$(PKG_SOURCE_VERSION)?
PKG_HASH:=dca45a31006aca3dba5f543f6990ca755ffb2bde8e533cc2bbe6bac9ec12f157
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_SOURCE_VERSION)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/kenzok8/v2dat/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=149319fa274a7d0b054d848f782ad103f653321380785cd1734642e434f99176
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
PKG_LICENSE:=GPL-3.0
PKG_LICENSE_FILE:=LICENSE
@@ -25,7 +24,7 @@ PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
PKG_BUILD_FLAGS:=no-mips16
GO_PKG:=github.com/urlesistiana/v2dat
GO_PKG:=github.com/kenzok8/v2dat
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
@@ -34,7 +33,7 @@ define Package/v2dat
SECTION:=utils
CATEGORY:=Utilities
TITLE:=V2ray geo/ip data unpack tools
URL:=https://github.com/urlesistiana/v2dat
URL:=https://github.com/kenzok8/v2dat
DEPENDS:=$(GO_ARCH_DEPENDS)
endef
-42
View File
@@ -1,42 +0,0 @@
From 226cb45cc97c52fc0383a5b5172a131f65921f4a Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Wed, 20 Sep 2023 20:52:27 +0800
Subject: [PATCH 1/3] format logtime
---
mlog/logger.go | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
--- a/mlog/logger.go
+++ b/mlog/logger.go
@@ -1,17 +1,28 @@
package mlog
-import "go.uber.org/zap"
+import (
+ "time"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
var logger = mustInitLogger()
func mustInitLogger() *zap.Logger {
- l, err := zap.NewDevelopment(zap.WithCaller(false))
+ config := zap.NewDevelopmentConfig()
+ config.EncoderConfig.EncodeTime = customTimeEncoder
+ l, err := config.Build(zap.WithCaller(false))
if err != nil {
panic("failed to init mlog:" + err.Error())
}
return l
}
+func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+ enc.AppendString(t.Format("2006-01-02 15:04:05"))
+}
+
func L() *zap.Logger {
return logger
}
@@ -1,100 +0,0 @@
From 0e465de6bc63d71dd7371209bbaaf9350e2fc567 Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Fri, 20 Mar 2026 09:45:03 +0800
Subject: [PATCH 2/3] v2dat: update dependencies
Signed-off-by: sbwml <admin@cooluc.com>
---
go.mod | 15 ++++++---------
go.sum | 47 +++++++++++++++++------------------------------
2 files changed, 23 insertions(+), 39 deletions(-)
--- a/go.mod
+++ b/go.mod
@@ -1,18 +1,15 @@
module github.com/urlesistiana/v2dat
-go 1.19
+go 1.23
require (
- github.com/spf13/cobra v1.6.1
- go.uber.org/zap v1.24.0
- google.golang.org/protobuf v1.28.1
+ github.com/spf13/cobra v1.10.2
+ go.uber.org/zap v1.27.1
+ google.golang.org/protobuf v1.36.11
)
require (
- github.com/google/go-cmp v0.5.9 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/spf13/pflag v1.0.5 // indirect
- github.com/stretchr/testify v1.8.1 // indirect
- go.uber.org/atomic v1.10.0 // indirect
- go.uber.org/multierr v1.9.0 // indirect
+ github.com/spf13/pflag v1.0.10 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
)
--- a/go.sum
+++ b/go.sum
@@ -1,42 +1,29 @@
-github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
-github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
+github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
+github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
-go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
-go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
-go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
-go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
-go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
-go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
-google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -1,128 +0,0 @@
From 0c6f9a36049423283052dfaf6eb9e211afe95c89 Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Fri, 20 Mar 2026 10:11:24 +0800
Subject: [PATCH 3/3] perf(unpack): Use memory mapping to reduce memory usage
Replaced `os.ReadFile` with `mmap` to avoid loading the entire geoip.dat
or geosite.dat file into memory during unpacking. This fixes
out-of-memory errors on low-memory devices.
Signed-off-by: sbwml <admin@cooluc.com>
---
cmd/unpack/geoip.go | 18 ++++++++++++++----
cmd/unpack/geosite.go | 11 ++++++++++-
go.mod | 4 +++-
go.sum | 4 ++++
4 files changed, 31 insertions(+), 6 deletions(-)
--- a/cmd/unpack/geoip.go
+++ b/cmd/unpack/geoip.go
@@ -3,14 +3,16 @@ package unpack
import (
"bufio"
"fmt"
- "github.com/spf13/cobra"
- "github.com/urlesistiana/v2dat/v2data"
- "go.uber.org/zap"
"io"
"net/netip"
"os"
"path/filepath"
"strings"
+
+ "github.com/edsrzf/mmap-go"
+ "github.com/spf13/cobra"
+ "github.com/urlesistiana/v2dat/v2data"
+ "go.uber.org/zap"
)
func newGeoIPCmd() *cobra.Command {
@@ -34,10 +36,18 @@ func newGeoIPCmd() *cobra.Command {
func unpackGeoIP(args *unpackArgs) error {
filePath, wantTags, ourDir := args.file, args.filters, args.outDir
- b, err := os.ReadFile(filePath)
+
+ file, err := os.Open(filePath)
if err != nil {
return err
}
+ defer file.Close()
+
+ b, err := mmap.Map(file, mmap.RDONLY, 0)
+ if err != nil {
+ return fmt.Errorf("error mapping file: %w", err)
+ }
+ defer b.Unmap()
geoIPList, err := v2data.LoadGeoIPListFromDAT(b)
if err != nil {
return err
--- a/cmd/unpack/geosite.go
+++ b/cmd/unpack/geosite.go
@@ -3,6 +3,7 @@ package unpack
import (
"bufio"
"fmt"
+ "github.com/edsrzf/mmap-go"
"github.com/spf13/cobra"
"github.com/urlesistiana/v2dat/v2data"
"go.uber.org/zap"
@@ -39,10 +40,18 @@ func newGeoSiteCmd() *cobra.Command {
func unpackGeoSite(args *unpackArgs) error {
filePath, suffixes, outDir := args.file, args.filters, args.outDir
- b, err := os.ReadFile(filePath)
+
+ file, err := os.Open(filePath)
if err != nil {
return err
}
+ defer file.Close()
+
+ b, err := mmap.Map(file, mmap.RDONLY, 0)
+ if err != nil {
+ return fmt.Errorf("error mapping file: %w", err)
+ }
+ defer b.Unmap()
geoSiteList, err := v2data.LoadGeoSiteList(b)
if err != nil {
return err
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,9 @@
module github.com/urlesistiana/v2dat
-go 1.23
+go 1.25.0
require (
+ github.com/edsrzf/mmap-go v1.2.0
github.com/spf13/cobra v1.10.2
go.uber.org/zap v1.27.1
google.golang.org/protobuf v1.36.11
@@ -12,4 +13,5 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
go.uber.org/multierr v1.11.0 // indirect
+ golang.org/x/sys v0.42.0 // indirect
)
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,8 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
+github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -22,6 +24,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:2
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=