From fc84ee6995f1b7d74fa461ecafb55e04926941f5 Mon Sep 17 00:00:00 2001 From: action Date: Fri, 3 Jul 2026 01:41:07 +0800 Subject: [PATCH] update 2026-07-03 01:41:07 --- .../usr/share/shadowsocksr/gen_config.lua | 4 +- .../root/usr/share/shadowsocksr/subscribe.lua | 79 ++++- mosdns/Makefile | 2 +- ...d-backups-config-for-log-file-rotati.patch | 280 ++++++++++++++++++ v2ray-geodata/Makefile | 4 +- 5 files changed, 353 insertions(+), 16 deletions(-) create mode 100644 mosdns/patches/209-feat-add-size-and-backups-config-for-log-file-rotati.patch diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua index c0c5b8d8..a36c25ac 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua @@ -835,7 +835,7 @@ Xray.outbounds = { tcpcongestion = server.custom_tcpcongestion, -- 连接服务器节点的 TCP 拥塞控制算法 -- 出站的 dialerProxy(与 fragment 中的 tag 保持一致) dialerProxy = (xray_fragment.fragment == "1" or xray_fragment.noise == "1") and - ((remarks ~= nil and remarks ~= "") and (node_id .. "." .. remarks) or node_id) or nil + ((remarks and remarks ~= "") and (node_id .. "." .. remarks) or ("direct" .. "." .. node_id)) or nil } } or nil, mux = (server.v2ray_protocol ~= "hysteria2" and server.v2ray_protocol ~= "wireguard") and { @@ -872,7 +872,7 @@ if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.e local n_domainstrategy = xray_noise.domainStrategy table.insert(Xray.outbounds, { protocol = "freedom", - tag = (remarks ~= nil and remarks ~= "") and (node_id .. "." .. remarks) or node_id, + tag = (remarks and remarks ~= "") and (node_id .. "." .. remarks) or ("direct" .. "." .. node_id), settings = (xray_fragment.noise == "1" and xray_noise.enabled == "1") and n_domainstrategy and n_domainstrategy ~= "" and { domainStrategy = n_domainstrategy } or nil, diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua index 687f2daf..11b70ab7 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua @@ -19,6 +19,7 @@ local b64decode = nixio.bin.b64decode local b64encode = nixio.bin.b64encode local URL = require "url" local cache = {} +local old_groupHash_cache = {} local nodeResult = setmetatable({}, {__index = cache}) -- update result local name = 'shadowsocksr' local uciType = 'servers' @@ -378,14 +379,14 @@ local function isClashYAML(str) return false end -local function processClashSubscription(url) +local function processClashSubscription(url, groupHash) local ok, parsed = pcall(URL.parse, url) if not ok or not parsed or not parsed.host then return nil end - local url_hash = string.sub(md5(url), 1, 8) - local alias = "Clash_" .. parsed.host .. "_" .. url_hash + local groupHash_short = string.sub(groupHash, 1, 8) + local alias = "Clash_" .. parsed.host .. "_" .. groupHash_short local server_port = parsed.port or ((parsed.scheme == "http") and "80" or "443") local result = { type = "clash", @@ -2005,14 +2006,55 @@ end local function read_old_md5(groupHash) local path = get_md5_path(groupHash) if nixio.fs.access(path) then - return trim(nixio.fs.readfile(path) or "") + local content = trim(nixio.fs.readfile(path) or "") + if content ~= "" then + -- 尝试解析 JSON 格式 + local ok, data = pcall(jsonParse, content) + if ok and data and data.md5 then + return data.md5 + end + -- 兼容旧格式(纯 MD5 值) + return content + end end return "" end +-- 读取上次保存的 groupHash(扫描所有 sub_md5_* 文件,通过 url 匹配) +local function read_old_groupHash(url) + -- 检查缓存 + if old_groupHash_cache[url] then + return old_groupHash_cache[url] + end + + local files = nixio.fs.dir("/tmp/") or {} + for file in files do + if file:match("^sub_md5_") then + local full_path = "/tmp/" .. file + if nixio.fs.access(full_path) then + local content = trim(nixio.fs.readfile(full_path) or "") + if content ~= "" then + local ok, data = pcall(jsonParse, content) + if ok and data and data.url and data.url == url then + return data.hash, data.url + end + end + end + end + end + return nil +end + -- 将订阅分组最新内容的 MD5 值保存到对应的临时文件中,以便下次更新时进行对比。 -local function write_new_md5(groupHash, md5) - nixio.fs.writefile(get_md5_path(groupHash), md5) +-- 同时保存 groupHash 和 url,用于检测 groupHash 的值变化 +local function write_new_md5(groupHash, md5, url) + local path = get_md5_path(groupHash) + local data = { + md5 = md5, + hash = groupHash, + url = url + } + nixio.fs.writefile(path, jsonStringify(data)) end -- curl @@ -2511,6 +2553,7 @@ end local execute = function() local updated = false local selected_hashes = {} + local old_groupHash = nil for _, item in ipairs(subscribe_items) do local fetch_url = build_convert_url(item.url) @@ -2531,7 +2574,11 @@ local execute = function() local raw, new_md5 local groupHash local yaml_removed_count = 0 - + + -- 读取旧的 groupHash + old_groupHash = read_old_groupHash(url) + log("读取到旧的 groupHash: " .. tostring(old_groupHash)) + if should_use_mihomo_mode() then raw, new_md5 = curl(fetch_url, user_agent) if isClashYAML(raw) then @@ -2557,7 +2604,7 @@ local execute = function() if should_use_mihomo_mode() and yaml_removed_count and yaml_removed_count > 0 then log("Clash/Mihomo YAML 已按订阅过滤关键词移除节点数量: " .. tostring(yaml_removed_count)) end - log("groupHash: " .. groupHash) + log("groupHash: " .. tostring(groupHash)) log("old_md5: " .. tostring(old_md5)) log("new_md5: " .. tostring(new_md5)) @@ -2589,8 +2636,18 @@ local execute = function() log("订阅内容未变化但本地节点不存在,强制重建订阅节点: " .. url) end updated = true - -- 保存更新后的 MD5 值到以 groupHash 为标识的临时文件中,用于下次订阅更新时进行对比 - write_new_md5(groupHash, new_md5) + + -- 当 groupHash 变化时,删除旧的 MD5 文件 + if old_groupHash and old_groupHash ~= groupHash then + local old_path = get_md5_path(old_groupHash) + if nixio.fs.access(old_path) then + nixio.fs.remove(old_path) + log("groupHash 已变化,删除旧 MD5 文件: " .. old_path) + end + end + + -- 保存更新后的 groupHash、MD5 和 URL 值 + write_new_md5(groupHash, new_md5, url) cache[groupHash] = {} tinsert(nodeResult, {}) @@ -2600,7 +2657,7 @@ local execute = function() if isClashYAML(raw) then is_clash_subscription = true - local result = processClashSubscription(fetch_url) + local result = processClashSubscription(fetch_url, groupHash) if result and check_filer(result) then log('过滤 Clash 总节点: ' .. result.alias) elseif result and not cache[groupHash][result.hashkey] then diff --git a/mosdns/Makefile b/mosdns/Makefile index cc69893e..2d88d15b 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:=5 +PKG_RELEASE:=6 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/IrineSistiana/mosdns/tar.gz/v$(PKG_VERSION)? 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 new file mode 100644 index 00000000..f2629e73 --- /dev/null +++ b/mosdns/patches/209-feat-add-size-and-backups-config-for-log-file-rotati.patch @@ -0,0 +1,280 @@ +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/v2ray-geodata/Makefile b/v2ray-geodata/Makefile index bb149b6f..855afbb5 100644 --- a/v2ray-geodata/Makefile +++ b/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=b71d1999439dde2de2d2b6844a2befa50c50211ff739785c005ca7c230a17d6a endef -GEOSITE_VER:=20260701035519 +GEOSITE_VER:=20260702133809 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=76902d5531d1036277d5663ff37d2c60403b16913ebfa82235fbd7139825d3b7 + HASH:=f2ec0b9dc07c03af30d61f84d0d07210235fcc226a9c2b6682717bada6e58c12 endef GEOSITE_IRAN_VER:=202606290159