From 76aa39b6798c4c1f52a141a45aaac1d59589c284 Mon Sep 17 00:00:00 2001 From: action Date: Sun, 5 Jul 2026 01:02:55 +0800 Subject: [PATCH] update 2026-07-05 01:02:55 --- adguardhome/Makefile | 48 +- adguardhome/files/adguardhome.conf | 30 + adguardhome/files/adguardhome.defaults | 109 +++ adguardhome/files/adguardhome.init | 111 +++ adguardhome/files/adguardhome.json | 22 + adguardhome/files/adguardhome.sysctl | 3 + adguardhome/patches/default_username.patch | 11 - adguardhome/patches/version.patch | 9 - aliyundrive-webdav/Makefile | 60 +- clouddrive2/Makefile | 30 +- clouddrive2/files/clouddrive2.init | 2 +- ddns-go/Makefile | 33 +- ddns-go/file/ddns-go.init | 46 ++ ddns-go/file/luci-ddns-go.uci-default | 7 + ddns-go/files/ddns-go.conf | 9 - ddns-go/files/ddns-go.init | 84 --- filebrowser/Makefile | 85 ++- filebrowser/files/config.yaml | 156 ---- filebrowser/files/filebrowser.config | 10 +- filebrowser/files/filebrowser.init | 45 +- luci-app-uugamebooster/Makefile | 9 + .../resources/view/uugamebooster.js | 74 ++ .../po/zh_Hans/uugamebooster.po | 46 ++ .../luci/menu.d/luci-app-uugamebooster.json | 13 + .../rpcd/acl.d/luci-app-uugamebooster.json | 14 + .../root/www/uugamebooster/uuandriod.png | Bin 0 -> 77225 bytes .../root/www/uugamebooster/uuios.png | Bin 0 -> 7292 bytes lucky/Makefile | 26 +- lucky/files/lucky.config | 5 - lucky/files/lucky.init | 81 -- lucky/files/luckyarch.bin | 14 - mosdns/Makefile | 5 +- .../100-mosdns-update-dependencies.patch | 238 ------ ...for-bad-request-in-ServeHTTP-handler.patch | 2 +- ...to-reload-rule-files-on-modification.patch | 697 ------------------ .../207-add-adblock_set-database-plugin.patch | 427 ----------- ...-support-hosts-file-format-blocking-.patch | 64 -- ...d-backups-config-for-log-file-rotati.patch | 280 ------- uugamebooster/Makefile | 13 +- uugamebooster/files/uugamebooster.config | 1 - uugamebooster/files/uugamebooster.init | 2 +- uugamebooster/update.sh | 4 +- v2dat/Makefile | 17 +- v2dat/patches/100-format-logtime.patch | 42 -- .../101-v2dat-update-dependencies.patch | 100 --- ...memory-mapping-to-reduce-memory-usag.patch | 128 ---- 46 files changed, 667 insertions(+), 2545 deletions(-) create mode 100644 adguardhome/files/adguardhome.conf create mode 100644 adguardhome/files/adguardhome.defaults create mode 100644 adguardhome/files/adguardhome.init create mode 100644 adguardhome/files/adguardhome.json create mode 100644 adguardhome/files/adguardhome.sysctl delete mode 100644 adguardhome/patches/default_username.patch delete mode 100644 adguardhome/patches/version.patch create mode 100644 ddns-go/file/ddns-go.init create mode 100644 ddns-go/file/luci-ddns-go.uci-default delete mode 100644 ddns-go/files/ddns-go.conf delete mode 100644 ddns-go/files/ddns-go.init delete mode 100644 filebrowser/files/config.yaml mode change 100644 => 100755 filebrowser/files/filebrowser.init create mode 100644 luci-app-uugamebooster/Makefile create mode 100644 luci-app-uugamebooster/htdocs/luci-static/resources/view/uugamebooster.js create mode 100644 luci-app-uugamebooster/po/zh_Hans/uugamebooster.po create mode 100644 luci-app-uugamebooster/root/usr/share/luci/menu.d/luci-app-uugamebooster.json create mode 100644 luci-app-uugamebooster/root/usr/share/rpcd/acl.d/luci-app-uugamebooster.json create mode 100644 luci-app-uugamebooster/root/www/uugamebooster/uuandriod.png create mode 100644 luci-app-uugamebooster/root/www/uugamebooster/uuios.png delete mode 100644 lucky/files/lucky.config delete mode 100644 lucky/files/lucky.init delete mode 100644 lucky/files/luckyarch.bin delete mode 100644 mosdns/patches/100-mosdns-update-dependencies.patch delete mode 100644 mosdns/patches/206-add-auto-reload-rule-files-on-modification.patch delete mode 100644 mosdns/patches/207-add-adblock_set-database-plugin.patch delete mode 100644 mosdns/patches/208-feat-adblock_set-support-hosts-file-format-blocking-.patch delete mode 100644 mosdns/patches/209-feat-add-size-and-backups-config-for-log-file-rotati.patch mode change 100755 => 100644 uugamebooster/files/uugamebooster.init mode change 100755 => 100644 uugamebooster/update.sh delete mode 100644 v2dat/patches/100-format-logtime.patch delete mode 100644 v2dat/patches/101-v2dat-update-dependencies.patch delete mode 100644 v2dat/patches/102-perf-unpack-Use-memory-mapping-to-reduce-memory-usag.patch diff --git a/adguardhome/Makefile b/adguardhome/Makefile index d157edc5..ff1c67e9 100644 --- a/adguardhome/Makefile +++ b/adguardhome/Makefile @@ -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 +PKG_CPE_ID:=cpe:/a:adguard:adguardhome +PKG_MAINTAINER:=Dobroslaw Kijowski , George Sapkin 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)) - diff --git a/adguardhome/files/adguardhome.conf b/adguardhome/files/adguardhome.conf new file mode 100644 index 00000000..41bb3644 --- /dev/null +++ b/adguardhome/files/adguardhome.conf @@ -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' diff --git a/adguardhome/files/adguardhome.defaults b/adguardhome/files/adguardhome.defaults new file mode 100644 index 00000000..d19d531e --- /dev/null +++ b/adguardhome/files/adguardhome.defaults @@ -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 diff --git a/adguardhome/files/adguardhome.init b/adguardhome/files/adguardhome.init new file mode 100644 index 00000000..43db94bb --- /dev/null +++ b/adguardhome/files/adguardhome.init @@ -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 +} diff --git a/adguardhome/files/adguardhome.json b/adguardhome/files/adguardhome.json new file mode 100644 index 00000000..3586a4ae --- /dev/null +++ b/adguardhome/files/adguardhome.json @@ -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" + ] +} diff --git a/adguardhome/files/adguardhome.sysctl b/adguardhome/files/adguardhome.sysctl new file mode 100644 index 00000000..dfc1698a --- /dev/null +++ b/adguardhome/files/adguardhome.sysctl @@ -0,0 +1,3 @@ +# quic-go expects larger UDP send/receive buffers +net.core.rmem_max = 7500000 +net.core.wmem_max = 7500000 diff --git a/adguardhome/patches/default_username.patch b/adguardhome/patches/default_username.patch deleted file mode 100644 index 11038f83..00000000 --- a/adguardhome/patches/default_username.patch +++ /dev/null @@ -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": "登入", diff --git a/adguardhome/patches/version.patch b/adguardhome/patches/version.patch deleted file mode 100644 index b4ec521a..00000000 --- a/adguardhome/patches/version.patch +++ /dev/null @@ -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; - }; diff --git a/aliyundrive-webdav/Makefile b/aliyundrive-webdav/Makefile index 097495ce..9186e765 100644 --- a/aliyundrive-webdav/Makefile +++ b/aliyundrive-webdav/Makefile @@ -1,38 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2017-2020 Yousong Zhou +# 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 -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)) diff --git a/clouddrive2/Makefile b/clouddrive2/Makefile index 0e3fd559..a9305e57 100644 --- a/clouddrive2/Makefile +++ b/clouddrive2/Makefile @@ -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/ diff --git a/clouddrive2/files/clouddrive2.init b/clouddrive2/files/clouddrive2.init index dc8d22a2..54bcdd5b 100644 --- a/clouddrive2/files/clouddrive2.init +++ b/clouddrive2/files/clouddrive2.init @@ -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" } diff --git a/ddns-go/Makefile b/ddns-go/Makefile index 173aa1e2..456a2a3a 100644 --- a/ddns-go/Makefile +++ b/ddns-go/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-3.0-only # -# Copyright (C) 2021-2026 sirpdboy +# Copyright (C) 2021-2023 sirpdboy # +# 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 +PKG_MAINTAINER:=Tianling Shen 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)) diff --git a/ddns-go/file/ddns-go.init b/ddns-go/file/ddns-go.init new file mode 100644 index 00000000..d845dc55 --- /dev/null +++ b/ddns-go/file/ddns-go.init @@ -0,0 +1,46 @@ +#!/bin/sh /etc/rc.common +# +# Copyright (C) 2021-2023 sirpdboy 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" +} diff --git a/ddns-go/file/luci-ddns-go.uci-default b/ddns-go/file/luci-ddns-go.uci-default new file mode 100644 index 00000000..0c01cacc --- /dev/null +++ b/ddns-go/file/luci-ddns-go.uci-default @@ -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 diff --git a/ddns-go/files/ddns-go.conf b/ddns-go/files/ddns-go.conf deleted file mode 100644 index e13a09f6..00000000 --- a/ddns-go/files/ddns-go.conf +++ /dev/null @@ -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' diff --git a/ddns-go/files/ddns-go.init b/ddns-go/files/ddns-go.init deleted file mode 100644 index c478656e..00000000 --- a/ddns-go/files/ddns-go.init +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/sh /etc/rc.common -# -# Copyright (C) 2021-2026 sirpdboy -# -# 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" -} \ No newline at end of file diff --git a/filebrowser/Makefile b/filebrowser/Makefile index d121281f..7362a1a6 100644 --- a/filebrowser/Makefile +++ b/filebrowser/Makefile @@ -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 + +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)) \ No newline at end of file diff --git a/filebrowser/files/config.yaml b/filebrowser/files/config.yaml deleted file mode 100644 index d5b46ba7..00000000 --- a/filebrowser/files/config.yaml +++ /dev/null @@ -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 diff --git a/filebrowser/files/filebrowser.config b/filebrowser/files/filebrowser.config index ba72197d..85eb6ac4 100644 --- a/filebrowser/files/filebrowser.config +++ b/filebrowser/files/filebrowser.config @@ -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 '/' diff --git a/filebrowser/files/filebrowser.init b/filebrowser/files/filebrowser.init old mode 100644 new mode 100755 index 230401d5..dda40463 --- a/filebrowser/files/filebrowser.init +++ b/filebrowser/files/filebrowser.init @@ -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 } diff --git a/luci-app-uugamebooster/Makefile b/luci-app-uugamebooster/Makefile new file mode 100644 index 00000000..4a132d5c --- /dev/null +++ b/luci-app-uugamebooster/Makefile @@ -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 diff --git a/luci-app-uugamebooster/htdocs/luci-static/resources/view/uugamebooster.js b/luci-app-uugamebooster/htdocs/luci-static/resources/view/uugamebooster.js new file mode 100644 index 00000000..da7092cc --- /dev/null +++ b/luci-app-uugamebooster/htdocs/luci-static/resources/view/uugamebooster.js @@ -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 = 'UU GameAcc ' + _('RUNNING') + ''; + } else { + node.innerHTML = 'UU GameAcc ' + _('NOT RUNNING') + ''; + } + } + }); + }, 3); + + return m.render(); + } +}); diff --git a/luci-app-uugamebooster/po/zh_Hans/uugamebooster.po b/luci-app-uugamebooster/po/zh_Hans/uugamebooster.po new file mode 100644 index 00000000..84464967 --- /dev/null +++ b/luci-app-uugamebooster/po/zh_Hans/uugamebooster.po @@ -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 "未运行" diff --git a/luci-app-uugamebooster/root/usr/share/luci/menu.d/luci-app-uugamebooster.json b/luci-app-uugamebooster/root/usr/share/luci/menu.d/luci-app-uugamebooster.json new file mode 100644 index 00000000..e66370b9 --- /dev/null +++ b/luci-app-uugamebooster/root/usr/share/luci/menu.d/luci-app-uugamebooster.json @@ -0,0 +1,13 @@ +{ + "admin/services/uugamebooster": { + "title": "UU GameAcc", + "action": { + "type": "view", + "path": "uugamebooster" + }, + "depends": { + "acl": [ "luci-app-uugamebooster" ], + "uci": { "uugamebooster": true } + } + } +} diff --git a/luci-app-uugamebooster/root/usr/share/rpcd/acl.d/luci-app-uugamebooster.json b/luci-app-uugamebooster/root/usr/share/rpcd/acl.d/luci-app-uugamebooster.json new file mode 100644 index 00000000..0c8766a9 --- /dev/null +++ b/luci-app-uugamebooster/root/usr/share/rpcd/acl.d/luci-app-uugamebooster.json @@ -0,0 +1,14 @@ +{ + "luci-app-uugamebooster": { + "description": "Grant UCI access for luci-app-uugamebooster", + "read": { + "uci": [ "uugamebooster" ], + "ubus": { + "service": [ "list" ] + } + }, + "write": { + "uci": [ "uugamebooster" ] + } + } +} diff --git a/luci-app-uugamebooster/root/www/uugamebooster/uuandriod.png b/luci-app-uugamebooster/root/www/uugamebooster/uuandriod.png new file mode 100644 index 0000000000000000000000000000000000000000..279b9f7071c9f4e3e41910973cbb1343a26a4086 GIT binary patch literal 77225 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 literal 0 HcmV?d00001 diff --git a/luci-app-uugamebooster/root/www/uugamebooster/uuios.png b/luci-app-uugamebooster/root/www/uugamebooster/uuios.png new file mode 100644 index 0000000000000000000000000000000000000000..bce47ab9849bbe0792a5aed876c766af8d562fe3 GIT binary patch literal 7292 zcmaiZWl$ST*EV;NB5mnoOMT%R1Vj;Lg3xr~&Nbwda?pE9qAQUIKLn$pza4%3K zxI5(Ke!lsBz4OewGy7w&vuF35YtGr3ohw3HQ{^ckEg=>b)>BoGA{YznpMPf&KK27b zgMW*{!otSVegjd$U@#BJ{{B9Yfdc>lTwPr=GrzvOyK8Q4#>K^TbaYHgN?KiARZ>#I z$H%|8xL^Q?-Q3(9?H@BTNEmAB02v~NFqrL9+uoi&W_B3@0uszXge?O{Moc`=vW$sU z8naopjlnQ7iUS#JhrV(Ezm8z;E~+*$wxv7N)HE2(sOat#Gnf4F@i7mN;*%#&ME7Uf zT7Ohoa$qn!LJS;93>=AxiO-%=l97>BRaLpVy0Y*pGBPqI4PgBJ{S_1xfDHVctg=l_ zP0T{7^mMPfI{O3!1kTUT&(6+PR#teUqK%A<>g(&BoSd|@w4|h@hKGh?W8*eAH-$zz zh5DLV<#dm(FiT6zt*z~tx^h88N1e>lko4N_BxQ0Iq$0Y`{dXMS9 zz(Ao;76Fy>dyEhAxTmM540R%UJXc!!ozv7Fq^c$&EMmgIp~=9(&dx5fI&!}VkB^TB z%7(5Y-Wr0$n|Co$-RB~U{asyMccXdyxtTm)LMHAo858%S8z|wa-u+)0K&ez9_t(SP zk3dFEAcNwcGYsZv*3;9A)9wS1!K@g<-TD_(wT3xaoKj1g66{3srKj;FCLFZ|Jq#-= ztD#U3(vGKh{eJA|4s+HI4D1%DD}$F5r+63x8AacTa5`~wAQtcUcK5b-cY6DmW@cu3 zx(6@2gSfo|fO?;RJa%<~S`!IMDG)(^UY-~jAYbGrkto;u$`X@m_61@>W9n$RN+qK*yH z78%IsBza?>`3mp8SaW@S5)C!=SlJ-tjw(CPTv#^a$QUa2a| zK|amxPRKp&l*Lz)3&P&E@z}jKn3%wRoRLl1xw0Pi&?f&G0j0zJ@5^<^_W!PH3Y(<2 ztQyw|8C)pmMg<3KqZgs?%*~N)UB3a-^W{Qj%CtJ=%fdq8gNVKWb+GDY5(IU{lHx9{ z%9^e9|Cj!=)wvTPUpQjdNXgjJ1^P{uwOOM0n#T-1xEpshxf;*$ft^C9c#CJY*;JYA z$AVAHJeWET9DaJ=-;glkqb{*IL2JY?h8UUnAmMvi!v7Pvego^b?J6tyZCho8*yw^W z&tEw%=iRLpZxJA*in=yTuBV8M_sOPX^@*;YLX|ytyDPU~t9za~c#?BD@wR9NLjjhG z#8kEqG{z~3$+mByslSA??U$ZK$yEtI#3FZn0MoAv1$l2so4D~xMi&L_8tdb%!e&8s1Mtc6Q~-C$gFkI8Ioa8|{E z`))LU8fLtJ#0Puh`Zy!SVYqJ;O97EM$rwZV892ERAXnjLlW5$uir_s zHdjgp9jiIQU_0er3*M*99fUaL)HY@IPyfC}I0lxd8#l4+`6oMe8T!@of6F# z+Y)Y`4ldMNn}Na;#$MWRrTlq>{-NtA*(^>v&RDjk zn$i_dmcB!GioQQJ;%9p)v3<^lbJ?~p+#DUIFELlikZOu}LP_=O3CY>8&bW?_T~HT@ z8{R#YPMf*q9*wNxN+~fK_$3DyER#)5O{=`1{7q}NDfL8C744UOo@b1><9PvTey5e} zuh3+5w`nbTUSc<6|GFlgNYEbM-08O|ZisO{}-1JlDl&_zh~U*r>PI#-q;>Jr|Wlo+|_jhuAJaLQ}cpAn}|D< z(^6Qg>)4v?DJCxAoAr$$|D1oDna6|s<-BPuPF|v%Dpu;Or|YK_c2pbkeqi=rPnmjf zwf2rnFyl^-U6dQcckUe0NhFr>f-fW7T26!p(fJy$!<{xXPEV$W5jZ_q>$*NqzBr>z zAJB`cE@L)rZ{bSK)4>LX5;!1*N6T8R$iI6p%)8f1n_~0$o_3y};Ib*l=%qdI>jTHU z;j9xLyi~W>dL+yHln8ts-9@EXQ(ltqiu!p(B)H7f>+%*{D))Sgw)5sH%)2xHJR<$O zj7ip z=>&?e>u~P*J=%QtS2*S-FbAlEuKRB9oT`?lMobqo`sFZ#FlIo8L^;T#+w| zXE5@}&Jv(pvkx`f)s>uvgtzHS!M}5_QW-SoY`bBb#pG!{sasYf`}|Z`#-I|Nd#SvB zW$?o#U!6BEdbP`m)vMM=ZPTRx+Z@CGDl1avEj|3gVf}`S3I`CUch^xu5ydahY0f28 z2F4@%{eww4iH?YlfLWFKZBmSq0aritl?MEK9n}9UmjezH8~*)wiW*mn*rt*ynihwu zlzmAKjeC-~G8Ea>*){`$ z*jwsQ1M3UPMWzbIV|sVY+)9KC_@6#ziG_StNsMiIZEslygr=!&8btQ8Yd&tB{+*r) z3xP!pxK$cpfP8;=%qVa3-SlDiG^JLgyJ8#w=k<@<$TlUs=4knLbn^; zjTA?vjX2Dc{0ywpvbdY>NVX2b`g$z@XlkT)w}}%sUwA6N`wUteM?y-EFEefAPr5B5 z6bAla!OsPE*N+Mgem%*~Qc8?^O(Yfv&MGF|<`z0M-nVnvh$)~wV|^6kCRwex`@j5qnoVq2{a;0ZY~bNFJZw#{TnyQzF^{N zxWf)dU?#X081qds&nf8ii;G}eAT&itM+IC;uTBH~D5FHW_il^I=)JYj8I%sjp8_=G ztO=XYD$`upwFzTrWI9`Q?$h*>Nan^;hdle_U8Oa_;eX(I60uCT5S@4v2jMFI^~` z$PKJ5($wnS0WJ)gpCu3TeagztMDa`5gj5?GoTUq`3WvYuKi={CD|TnTsPGj9Z37o8 zhg0cslHj?&SZy6IK+eM_mVdj_jxvP6(QuRkr$?Z6`;Ij`4PG_<6FPBEFY&vs)Y{XR zYAdw#367wqTtRD_>0VJQ#0Yv+iKrODx9>0MbZC6mL$%;YN?&Um9}4tqGbv13ZHpRcwi<+B}@s$iO67-T~R? zKh681eo7f%v<>DIKaNb0Qw!@34u+N_psd zlzVKc`#_?Qww6A0itqD5WM&;iq7qz%+fNus4(qk1kN(lu&284EeR!BkQ@IQwJu#p& zrk+W5fDc-`8NbAfHgb4-w$kn%cpo%ZtxL#Ckm!+}JIzWkf0(U1@5O_a1(kg#Fv_fp zNS~CkJKs#pk_u3>>mc8UHaWVfI3&G7kMjA7*rHnxE|86CBB+uYgdvwyILg=H;I{kW z^HxvdSWr&jSxqm>8dcz~C;dIA)O~0-EYM78v%%_QVxaPZWm?nujTljn6!o4N_b0Dv zOAXiQ-8SN1H4A59+colHe*FAWpVg!$(_G5VJu^^s2DUP{8fP2tiGL_rms_sGOD?{y z*dY51(4GlfqOPZKr_e(xw-yTDn{u|C+ z2q>TPvCo8e`m4CD+!2v7n+v+BEK$#gWDbOd$>}1P5D`#&He{^MA=d3lCDXfB+63DJ zFTh^po^!&)58=5pGwksBVJNNV3)yH9Bbb>#6D#U4p}E$&maNyK*!=E08}agQ347?b zJmO`_#5Na34MHQ81nH8eYrcM9A%6BTkp-h|d%(#H?^S2Jj+d`01*WA3v=%>9*gDg;9g==q{1!enX^ zBCB72>Db(pP&FNHTdU45hs3MF|E_SOKC<$F%vQ}zMlq1Ro@GIcf`vxgqQJNYP2qcR z$G9^+&iqM^w(_M(+6C$xKbqvB3__7_ARBtKG`0s($|fFlUfEL$+c7)Gm00Q;gVSyn zjM>j18=?ESwg(2Z!m~G@&^KKQ+r%k!Pqh&fKm5E+6O4oB_S7Zv>O{^Zi%V5ba>A2A zHxU!O9+u4h9^d3)&%uL}%agx&bbO*`Zvqd0X}O!Pg7#miSjwD2Jb;0Wr-y7=(+#6$e!EJw;5p#yHSX=VoT(U5onIAE;o!%v%A9@DZi2gRz`U#eC- zk^vS2f`Pg8KiC5%=s-XtW!``H<6_-N|`2Qk$*0f z57oOrb{Ei*b1*o(n$Mk>6M*PKvon|?&$7%HsPqV~g znBZ$aqZGgcNA0L8jcKu!F7iD_JoJZB|Lr&F;O6)H%3`g`HS}O*O+_JdNpaO_r*GG< zIn|^Qb6+yBE00E309a{?={d)9xaf2w5?Ndoe0z2RT5C}6dS7dpAs^2)J`4QK2V%Y( zeHZ<6fzKTFJnv;baT4^vku6Z_MaHMSPp}!w5}r(k>@CB7=DTaA=6u2QNv>~|+56<6 zzVOppL8NNrSRrx1BEtX^1=(Zt;B#E#iy_N>;~K{e9@WV&6Q?v&c`sK$>$RC;!YRMM z&h^ct_o0rf^Wv$4nA-i;_=uhz3B`p27lyV77SY~Bse=xBRqrHk!eHYY1WUX^bP%{4o)0uaJjd{<aS>Buzvx^8#vgxvFbs$Dx4mxF}~RTaxh znr&*aW?)z3HjSNiO>cXKVE`us)u9X=!+jvPN;QTUVrWdql7L@@J|Wj@u0Qsr@IrI?8>MUXgmT0|4z7 zKV!neGQPQ1-NnA8gNY`{a>W~4JVMAi_!Gb$AKnw6svm_0fwGIZZ z9ncMIkVrDMp~{opVrag;Pds{6=*8aG;CpoFtKRDr4si~N^kasx12QC93z^Jn`phJh zVQz^V+n$P%{C_pQpT-w`W;>f(P!A?-k4idwWLZ; z&v66Qu`*}eZhQ8gzP2Xk_9N&AwJTacbZ7CPb<71}2TYsuQUah^=5}IU^`|b6&sH=| zZC#eWh^`){ENB*Kv6}W7UcB)waEh0ZBj4117p{;VJFg@L`=xF0tO-K3@XP|wpmArJ zi$;M5z|fMHS7g>!MFjr)wcv7r`>;eD65{zh?o?oDxjBAeRz1Nww`jn{Yy~tb?Bml7 zhZRQ=_DBL8gOejNe}(F?y8?0};GhJBQfMu$m5F5-(AFpgcL9E^Y}k^4aNkj#L9CkW z1__T-aMrw3?5~VE49$dt`B4XzRm)>M-VqoqvvU3R8>%UysFiq`*W}En;lAW`|FGf1m z$rh8Q1UplIl-a)-6)--PYB5sR92GG0i+UQLDE3&r;rVn1Gpqg+lpC2ewvBA5(wHW$ zDJ|F|UAmd?{%0MFG^(I`9r3s&rNWpTHrry8fKow#(6;lWd1213z0!b+uZ061yl&eb zfy6#9rxU3`XD_~K{@qC)P5k;3tWenx`WvZfz8jO*b2=Zn;0kusa=^X46Mv7Yd@hU6>*-X)} zd|{w8^6kw~+=g!oLOz(#TVmQe7;K?3hds1p4>NKv{TRcD!iF8ap+oMUD5~8pIK#hi zs?}MQnD-TG?Kf9P{D>m@hu2DgS5&_FEsduP07}?AfhC{k$bmX+wd7YaZX>syQtiqN z@N}z`c8FgJmXhEyu*&=K_EZ4ty&~S3-J;NHrbuO(*Yoau=wuoN5fKOO8^bt8wQf~ zF5E7`2H(!#-)@Hyc7`gt2#a)|-WTR9`{iq?vo$S^t*n@k*%JEjfTHG*0*&=8A4jPm z3%J`rl^1$7)yTxrqNa!>NjrC#pZcSb?Gtc<1h&q+_;>muc=jQP?oqHb=fwy9fahwF zav67xu}k8|p&_!$?+u7xs1zlDIbx9igO6omKr!em8K{r*%c>3ove>0_Q7*AlU9sXL)o?RmSo~Ov;{Pcj ae=6zKcAR|8lc!7m1yq$Z6)WT|gZ>9PM_1zj literal 0 HcmV?d00001 diff --git a/lucky/Makefile b/lucky/Makefile index 92af5e78..d1ff2f48 100644 --- a/lucky/Makefile +++ b/lucky/Makefile @@ -1,10 +1,9 @@ # SPDX-License-Identifier: GPL-3.0-only # -# Copyright (C) 2021-2025 sirpdboy +# Copyright (C) 2021-2022 sirpdboy # # 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 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))) diff --git a/lucky/files/lucky.config b/lucky/files/lucky.config deleted file mode 100644 index 03c96239..00000000 --- a/lucky/files/lucky.config +++ /dev/null @@ -1,5 +0,0 @@ -config lucky 'lucky' - option logger '1' - option port '16601' - option configdir '/etc/lucky' - option enabled '0' diff --git a/lucky/files/lucky.init b/lucky/files/lucky.init deleted file mode 100644 index d68f15c9..00000000 --- a/lucky/files/lucky.init +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/sh /etc/rc.common -# -# Copyright (C) 2021-2025 sirpdboy 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 -} diff --git a/lucky/files/luckyarch.bin b/lucky/files/luckyarch.bin deleted file mode 100644 index cb7a8e0c..00000000 --- a/lucky/files/luckyarch.bin +++ /dev/null @@ -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 diff --git a/mosdns/Makefile b/mosdns/Makefile index 2d88d15b..66699daa 100644 --- a/mosdns/Makefile +++ b/mosdns/Makefile @@ -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 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 diff --git a/mosdns/patches/100-mosdns-update-dependencies.patch b/mosdns/patches/100-mosdns-update-dependencies.patch deleted file mode 100644 index 15fbbdee..00000000 --- a/mosdns/patches/100-mosdns-update-dependencies.patch +++ /dev/null @@ -1,238 +0,0 @@ -From 201408484de552cb927f6a50b76a34d65c8ac8c6 Mon Sep 17 00:00:00 2001 -From: sbwml -Date: Fri, 20 Mar 2026 10:39:22 +0800 -Subject: [PATCH] mosdns: update dependencies - -Signed-off-by: sbwml ---- - .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") - } diff --git a/mosdns/patches/203-add-response-for-bad-request-in-ServeHTTP-handler.patch b/mosdns/patches/203-add-response-for-bad-request-in-ServeHTTP-handler.patch index fcc0da79..38b7974b 100644 --- a/mosdns/patches/203-add-response-for-bad-request-in-ServeHTTP-handler.patch +++ b/mosdns/patches/203-add-response-for-bad-request-in-ServeHTTP-handler.patch @@ -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) diff --git a/mosdns/patches/206-add-auto-reload-rule-files-on-modification.patch b/mosdns/patches/206-add-auto-reload-rule-files-on-modification.patch deleted file mode 100644 index 403e714c..00000000 --- a/mosdns/patches/206-add-auto-reload-rule-files-on-modification.patch +++ /dev/null @@ -1,697 +0,0 @@ -From 9554c5b29aff4107504bdf45c35582e6d9743a93 Mon Sep 17 00:00:00 2001 -From: sbwml -Date: Mon, 20 Apr 2026 23:06:17 +0800 -Subject: [PATCH] add auto-reload rule files on modification - -Signed-off-by: sbwml ---- - 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 - } diff --git a/mosdns/patches/207-add-adblock_set-database-plugin.patch b/mosdns/patches/207-add-adblock_set-database-plugin.patch deleted file mode 100644 index 1f1f8e51..00000000 --- a/mosdns/patches/207-add-adblock_set-database-plugin.patch +++ /dev/null @@ -1,427 +0,0 @@ -From 64614017e674960638bac87b4a2f9c0aea0065e6 Mon Sep 17 00:00:00 2001 -From: sbwml -Date: Fri, 1 May 2026 01:35:59 +0800 -Subject: [PATCH] add adblock_set database plugin - -Signed-off-by: sbwml ---- - .../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 . -+ */ -+ -+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 . -+ */ -+ -+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 . -+ */ -+ -+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 . -+ */ -+ -+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" - diff --git a/mosdns/patches/208-feat-adblock_set-support-hosts-file-format-blocking-.patch b/mosdns/patches/208-feat-adblock_set-support-hosts-file-format-blocking-.patch deleted file mode 100644 index cc3215b1..00000000 --- a/mosdns/patches/208-feat-adblock_set-support-hosts-file-format-blocking-.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 8f9c6ca1ac8e9b5e10a5c45f3f338546424c95f1 Mon Sep 17 00:00:00 2001 -From: sbwml -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 ---- - 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, "@@") { diff --git a/mosdns/patches/209-feat-add-size-and-backups-config-for-log-file-rotati.patch b/mosdns/patches/209-feat-add-size-and-backups-config-for-log-file-rotati.patch deleted file mode 100644 index f2629e73..00000000 --- a/mosdns/patches/209-feat-add-size-and-backups-config-for-log-file-rotati.patch +++ /dev/null @@ -1,280 +0,0 @@ -From e2a50186add36064f78015b163d725f1fe9821d0 Mon Sep 17 00:00:00 2001 -From: sbwml -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 ---- - 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 . -+ */ -+ -+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) -+ } -+} diff --git a/uugamebooster/Makefile b/uugamebooster/Makefile index 4076942d..b501e8be 100644 --- a/uugamebooster/Makefile +++ b/uugamebooster/Makefile @@ -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 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)) \ No newline at end of file diff --git a/uugamebooster/files/uugamebooster.config b/uugamebooster/files/uugamebooster.config index 09016777..b7343751 100644 --- a/uugamebooster/files/uugamebooster.config +++ b/uugamebooster/files/uugamebooster.config @@ -1,4 +1,3 @@ config uugamebooster 'config' option enabled '0' - diff --git a/uugamebooster/files/uugamebooster.init b/uugamebooster/files/uugamebooster.init old mode 100755 new mode 100644 index ed517955..704bac35 --- a/uugamebooster/files/uugamebooster.init +++ b/uugamebooster/files/uugamebooster.init @@ -34,4 +34,4 @@ reload_service() { service_triggers() { procd_add_reload_trigger "$CONF" -} +} \ No newline at end of file diff --git a/uugamebooster/update.sh b/uugamebooster/update.sh old mode 100755 new mode 100644 index b668479b..eb78790d --- a/uugamebooster/update.sh +++ b/uugamebooster/update.sh @@ -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" \ No newline at end of file diff --git a/v2dat/Makefile b/v2dat/Makefile index ff794138..296f7814 100644 --- a/v2dat/Makefile +++ b/v2dat/Makefile @@ -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 diff --git a/v2dat/patches/100-format-logtime.patch b/v2dat/patches/100-format-logtime.patch deleted file mode 100644 index 4ccc1ed6..00000000 --- a/v2dat/patches/100-format-logtime.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 226cb45cc97c52fc0383a5b5172a131f65921f4a Mon Sep 17 00:00:00 2001 -From: sbwml -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 - } diff --git a/v2dat/patches/101-v2dat-update-dependencies.patch b/v2dat/patches/101-v2dat-update-dependencies.patch deleted file mode 100644 index ac8381e5..00000000 --- a/v2dat/patches/101-v2dat-update-dependencies.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 0e465de6bc63d71dd7371209bbaaf9350e2fc567 Mon Sep 17 00:00:00 2001 -From: sbwml -Date: Fri, 20 Mar 2026 09:45:03 +0800 -Subject: [PATCH 2/3] v2dat: update dependencies - -Signed-off-by: sbwml ---- - 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= diff --git a/v2dat/patches/102-perf-unpack-Use-memory-mapping-to-reduce-memory-usag.patch b/v2dat/patches/102-perf-unpack-Use-memory-mapping-to-reduce-memory-usag.patch deleted file mode 100644 index 530cc15e..00000000 --- a/v2dat/patches/102-perf-unpack-Use-memory-mapping-to-reduce-memory-usag.patch +++ /dev/null @@ -1,128 +0,0 @@ -From 0c6f9a36049423283052dfaf6eb9e211afe95c89 Mon Sep 17 00:00:00 2001 -From: sbwml -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 ---- - 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=