From c8457a27421ba54e0331a25a05f787c52e651dac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Apr 2026 23:57:06 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Sync=202026-04-20=2023:57:06?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ddns-go/Makefile | 4 +- luci-app-mosdns/Makefile | 4 +- .../resources/view/mosdns/basic.js | 5 +- .../resources/view/mosdns/rules.js | 22 +- luci-app-mosdns/root/etc/init.d/mosdns | 5 +- .../root/usr/share/mosdns/mosdns.uc | 6 +- .../usr/share/rpcd/acl.d/luci-app-mosdns.json | 3 +- .../root/usr/share/rpcd/ucode/luci.mosdns | 12 - luci-app-passwall/Makefile | 2 +- .../luasrc/controller/passwall.lua | 14 + .../model/cbi/passwall/client/type/ray.lua | 4 +- luci-app-passwall/luasrc/passwall/api.lua | 28 + .../view/passwall/node_config/header.htm | 17 + luci-app-passwall/po/zh-cn/passwall.po | 6 + .../resources/view/qiyougamebooster.js | 96 +++ .../luasrc/controller/qiyougamebooster.lua | 12 - .../luasrc/model/cbi/qiyougamebooster.lua | 28 - .../po/templates/qiyougamebooster.pot | 52 ++ luci-app-qiyougamebooster/po/zh-cn | 1 + .../po/zh-cn/qiyougamebooster.po | 38 - luci-app-qiyougamebooster/po/zh_Hans | 1 - .../po/zh_Hans/qiyougamebooster.po | 52 ++ .../root/etc/uci-defaults/qiyougamebooster | 19 - .../menu.d/luci-app-qiyougamebooster.json | 16 + .../rpcd/acl.d/luci-app-qiyougamebooster.json | 5 +- mosdns/Makefile | 2 +- ...to-reload-rule-files-on-modification.patch | 697 ++++++++++++++++++ 27 files changed, 1000 insertions(+), 151 deletions(-) create mode 100644 luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js delete mode 100644 luci-app-qiyougamebooster/luasrc/controller/qiyougamebooster.lua delete mode 100644 luci-app-qiyougamebooster/luasrc/model/cbi/qiyougamebooster.lua create mode 100644 luci-app-qiyougamebooster/po/templates/qiyougamebooster.pot create mode 120000 luci-app-qiyougamebooster/po/zh-cn delete mode 100644 luci-app-qiyougamebooster/po/zh-cn/qiyougamebooster.po delete mode 120000 luci-app-qiyougamebooster/po/zh_Hans create mode 100644 luci-app-qiyougamebooster/po/zh_Hans/qiyougamebooster.po delete mode 100644 luci-app-qiyougamebooster/root/etc/uci-defaults/qiyougamebooster create mode 100644 luci-app-qiyougamebooster/root/usr/share/luci/menu.d/luci-app-qiyougamebooster.json create mode 100644 mosdns/patches/206-add-auto-reload-rule-files-on-modification.patch diff --git a/ddns-go/Makefile b/ddns-go/Makefile index 9b9bfe607..9499dbc70 100644 --- a/ddns-go/Makefile +++ b/ddns-go/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ddns-go -PKG_VERSION:=6.16.9 -PKG_RELEASE:=8 +PKG_VERSION:=6.16.10 +PKG_RELEASE:=9 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)? diff --git a/luci-app-mosdns/Makefile b/luci-app-mosdns/Makefile index 8d33cea09..8f78bfb74 100644 --- a/luci-app-mosdns/Makefile +++ b/luci-app-mosdns/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-mosdns -PKG_VERSION:=1.7.0 -PKG_RELEASE:=4 +PKG_VERSION:=1.7.1 +PKG_RELEASE:=5 LUCI_TITLE:=LuCI Support for mosdns LUCI_PKGARCH:=all diff --git a/luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js b/luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js index d8f047c88..1c9c0bd09 100644 --- a/luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js +++ b/luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js @@ -391,10 +391,7 @@ return view.extend({ if (value == formvalue) { return; } - return fs.write('/etc/mosdns/rule/cloudflare-cidr.txt', formvalue.trim().replace(/\r\n/g, '\n') + '\n') - .then(function (i) { - return fs.exec('/etc/init.d/mosdns', ['restart']); - }); + return fs.write('/etc/mosdns/rule/cloudflare-cidr.txt', formvalue.trim().replace(/\r\n/g, '\n') + '\n'); }); }; diff --git a/luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js b/luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js index 4f49aaa36..e43bb3579 100644 --- a/luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js +++ b/luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js @@ -3,12 +3,6 @@ 'require fs'; 'require ui'; 'require view'; -'require rpc'; - -var callRestart = rpc.declare({ - object: 'luci.mosdns', - method: 'restart' -}); return view.extend({ render: function () { @@ -274,19 +268,9 @@ return view.extend({ }, handleSaveApply: function (ev) { - var m = this.map; - onclick = L.bind(this.handleSave, this, m); - return callRestart() - .then(function (res) { - if (res && res.code === 0) { - window.location.reload(); - } else { - ui.addNotification(null, E('p', _('Failed to restart mosdns: %s').format(res.output || 'Unknown error'))); - } - }) - .catch(function (e) { - ui.addNotification(null, E('p', _('Failed to restart mosdns: %s').format(e.message))); - }); + return this.handleSave(ev).then(function() { + window.location.reload(); + }); }, handleReset: null }); diff --git a/luci-app-mosdns/root/etc/init.d/mosdns b/luci-app-mosdns/root/etc/init.d/mosdns index 4d2861480..59d8073dd 100755 --- a/luci-app-mosdns/root/etc/init.d/mosdns +++ b/luci-app-mosdns/root/etc/init.d/mosdns @@ -822,10 +822,7 @@ start_service() { fi fi - [ "$update_list" -eq 1 ] && { - sleep 3 - $MOSDNS_SCRIPT update_adlist - } & + [ "$update_list" -eq 1 ] && $MOSDNS_SCRIPT update_adlist & } stop_service() { diff --git a/luci-app-mosdns/root/usr/share/mosdns/mosdns.uc b/luci-app-mosdns/root/usr/share/mosdns/mosdns.uc index 943a1ffd7..cf0a537a4 100755 --- a/luci-app-mosdns/root/usr/share/mosdns/mosdns.uc +++ b/luci-app-mosdns/root/usr/share/mosdns/mosdns.uc @@ -315,12 +315,10 @@ switch (action) { case "update": update_geodat(); update_adlist(); - exec_sys('/etc/init.d/mosdns restart'); + v2dat_dump(); break; case "update_adlist": - if (update_adlist()) { - exec_sys('/etc/init.d/mosdns restart'); - } + update_adlist(); break; case "v2dat_dump": v2dat_dump(); diff --git a/luci-app-mosdns/root/usr/share/rpcd/acl.d/luci-app-mosdns.json b/luci-app-mosdns/root/usr/share/rpcd/acl.d/luci-app-mosdns.json index 35a86d07f..438fefcc3 100644 --- a/luci-app-mosdns/root/usr/share/rpcd/acl.d/luci-app-mosdns.json +++ b/luci-app-mosdns/root/usr/share/rpcd/acl.d/luci-app-mosdns.json @@ -18,8 +18,7 @@ "clean_log", "flush_cache", "get_version", - "print_log", - "restart" + "print_log" ] }, "uci": [ "mosdns" ] diff --git a/luci-app-mosdns/root/usr/share/rpcd/ucode/luci.mosdns b/luci-app-mosdns/root/usr/share/rpcd/ucode/luci.mosdns index d04e367b0..25247d21b 100644 --- a/luci-app-mosdns/root/usr/share/rpcd/ucode/luci.mosdns +++ b/luci-app-mosdns/root/usr/share/rpcd/ucode/luci.mosdns @@ -34,11 +34,6 @@ function exec_sys(cmd) { return { code: code, stdout: stdout || "" }; } -function restart_mosdns() { - let p = popen('(sleep 1; /etc/init.d/mosdns restart) >/dev/null 2>&1 &', 'r'); - if (p) p.close(); -} - function get_logfile_path_internal() { let uci_cursor = cursor(); uci_cursor.load('mosdns'); @@ -124,13 +119,6 @@ const methods = { return { error: res.stdout }; } } - }, - - restart: { - call: function() { - restart_mosdns(); - return { code: 0, output: "mosdns is restarting in background." }; - } } }; diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index f992e5492..8fa03a358 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.4.15 -PKG_RELEASE:=85 +PKG_RELEASE:=86 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/luasrc/controller/passwall.lua b/luci-app-passwall/luasrc/controller/passwall.lua index 070a7ea28..8f0f6609d 100644 --- a/luci-app-passwall/luasrc/controller/passwall.lua +++ b/luci-app-passwall/luasrc/controller/passwall.lua @@ -117,6 +117,8 @@ function index() --[[geoview]] entry({"admin", "services", appname, "geo_view"}, call("geo_view")).leaf = true + + entry({"admin", "services", appname, "fetch_certsha256"}, call("fetch_certsha256")).leaf = true end local function http_write_json(content) @@ -1103,3 +1105,15 @@ function flush_set() http.redirect(api.url("log")) end end + +function fetch_certsha256() + local id = http.formvalue("id") or "" + local address = (id ~= "") and uci:get(appname, id, "address") or "" + local port = (id ~= "") and uci:get(appname, id, "port") or 0 + if id == "" or address == "" or not api.datatypes.hostname(address) or port == 0 then + http_write_json({ code = 1 }) + return + end + local data = api.fetch_cert_sha256(address, port, 5) + http_write_json(data ~= "" and { code = 0, data = data } or { code = 1 }) +end diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua index 9957ac930..cbab09588 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua @@ -376,9 +376,11 @@ if api.compare_versions(os.date("%Y.%m.%d"), "<", "2026.6.1") then end if api.compare_versions(xray_version, ">=", "26.1.31") then - o = s:option(Value, _n("tls_CertSha"), translate("TLS Chain Fingerprint (SHA256)"), translate("Once set, connects only when the server’s chain fingerprint matches.")) + o = s:option(Value, _n("tls_CertSha"), translate("TLS Chain Fingerprint (SHA256)")) o:depends({ [_n("tls")] = true, [_n("reality")] = false }) o:depends({ [_n("protocol")] = "hysteria2" }) + o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") .. + string.format("%s", "[ " .. translate("Fetch Manually") .. " ]") o = s:option(Value, _n("tls_CertByName"), translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name.")) o:depends({ [_n("tls")] = true, [_n("reality")] = false }) diff --git a/luci-app-passwall/luasrc/passwall/api.lua b/luci-app-passwall/luasrc/passwall/api.lua index ddd5d7d59..65e839046 100644 --- a/luci-app-passwall/luasrc/passwall/api.lua +++ b/luci-app-passwall/luasrc/passwall/api.lua @@ -1531,3 +1531,31 @@ function cleanEmptyTables(t) end return next(t) and t or nil end + +function fetch_cert_sha256(host, port, timeout) + if not host or not datatypes.hostname(host) then return "" end + port = tonumber(port) or 443 + timeout = tonumber(timeout) or 5 + local xray = finded_com("xray") + local cmd + if xray then + cmd = string.format( + "timeout %d %q tls ping %s:%d 2>/dev/null " .. + "| awk 'tolower($0) ~ /without sni/ {f=1} tolower($0) ~ /with sni/ {f=0} " .. + "f && tolower($0) ~ /cert.*leaf.*sha256/ {sub(/.*:/,\"\"); gsub(/[[:space:]]/,\"\"); print; exit}'", + timeout, xray, host, port + ) + else + cmd = string.format( + "timeout %d openssl s_client -connect %s:%d -servername %s -showcerts /dev/null " .. + "| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " .. + "| openssl x509 -outform der 2>/dev/null " .. + "| sha256sum 2>/dev/null", + timeout, host, port, host + ) + end + local out = trim(sys.exec(cmd)) + local fp = out:match("^([0-9a-fA-F]+)") + if not fp then return "" end + return fp:upper() +end diff --git a/luci-app-passwall/luasrc/view/passwall/node_config/header.htm b/luci-app-passwall/luasrc/view/passwall/node_config/header.htm index 06bf0eea8..108ad838d 100644 --- a/luci-app-passwall/luasrc/view/passwall/node_config/header.htm +++ b/luci-app-passwall/luasrc/view/passwall/node_config/header.htm @@ -52,5 +52,22 @@ local api = self.api } }); } + + function fetchCertSha256() { + XHR.get('<%=api.url("fetch_certsha256")%>', { id: node_id }, function(x, res) { + if (!x || x.status !== 200) { + alert("<%:Error%>"); + return; + } + if (!res || res.code !== 0 || !res.data) { + alert(res && res.msg ? res.msg : "<%:Fetch Failed%> !"); + return; + } + const inputs = document.querySelectorAll('input[id$=".xray_tls_CertSha"]'); + inputs.forEach(function(input) { + input.value = res.data; + }); + }); + } //]]> diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po index 6c4dd7ee1..105d0c4ea 100644 --- a/luci-app-passwall/po/zh-cn/passwall.po +++ b/luci-app-passwall/po/zh-cn/passwall.po @@ -1897,6 +1897,12 @@ msgstr "TLS 证书名称(CertName)" msgid "TLS is used to verify the leaf certificate name." msgstr "TLS 用于验证 leaf 证书的 name。" +msgid "Fetch Manually" +msgstr "手动获取" + +msgid "Fetch Failed" +msgstr "获取失败" + msgid "ECH Config" msgstr "ECH 配置" diff --git a/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js b/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js new file mode 100644 index 000000000..be1e631d9 --- /dev/null +++ b/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 + +'use strict'; +'require form'; +'require poll'; +'require uci'; +'require view'; +'require fs'; + +function getStatus() { + var result + try { + result = fs.exec('/usr/bin/qiyougamebooster.sh', ['status']); + return result.then(function(status) { + return status.stdout.trim() + }) + } catch (e) { } + return result; +} + +function getVersion() { + var result + try { + result = fs.exec('/usr/bin/qiyougamebooster.sh', ['version']) + return result.then(function(version) { + return version.stdout.trim() || _('QiYou Game Booster'); + }).catch(function() { + return _('QiYou Game Booster'); + }); + } catch (e) { } + return Promise.resolve(_('QiYou Game Booster')); +} + +function renderStatus(status, version) { + var spanTemp = '%s: %s %s'; + var renderHTML; + if (status == 'NOT ENABLED' || status == 'NOT RUNNING' || status == 'NOT SUPPORTED') { + renderHTML = spanTemp.format('red', _('Status'), _(version), _(status)); + } else { + renderHTML = spanTemp.format('green', _('Status'), _(version), _(status)); + } + return renderHTML; +} + +return view.extend({ + load: function() { + return Promise.all([ + uci.load('qiyougamebooster') + ]); + }, + + render: function() { + let m, s, o; + + m = new form.Map('qiyougamebooster', _('QiYou Game Booster'), + _('Play console games online with less lag and more stability.') + '
' + + _('— now supporting PS, Switch, Xbox, PC, and mobile.')); + + s = m.section(form.TypedSection); + s.anonymous = true; + s.render = function () { + poll.add(function () { + return L.resolveDefault(getStatus()).then(function (status) { + return L.resolveDefault(getVersion()).then(function (version) { + var view = document.getElementById('service_status'); + view.innerHTML = renderStatus(status, version); + }); + }); + }); + + return E('div', { class: 'cbi-section', id: 'status_bar' }, [ + E('p', { id: 'service_status' }, _('Collecting data...')) + ]); + } + + s = m.section(form.NamedSection, 'config', 'qiyougamebooster'); + + o = s.option(form.Flag, 'enabled', _('Enable')); + o.default = o.disabled; + o.rmempty = false; + + s = m.section(form.TypedSection); + s.anonymous = true; + s.render = function () { + return E('div', {class: 'cbi-section'}, [ + E('p', [ + E('img', {src: '/qiyougamebooster/Tutorial.png', height: '350'}) + ]) + ]) + } + + + return m.render(); + } + +}); diff --git a/luci-app-qiyougamebooster/luasrc/controller/qiyougamebooster.lua b/luci-app-qiyougamebooster/luasrc/controller/qiyougamebooster.lua deleted file mode 100644 index 7a28ee80e..000000000 --- a/luci-app-qiyougamebooster/luasrc/controller/qiyougamebooster.lua +++ /dev/null @@ -1,12 +0,0 @@ -module("luci.controller.qiyougamebooster", package.seeall) - -function index() - if not nixio.fs.access("/etc/config/qiyougamebooster") then - return - end - - local page - page = entry({"admin", "services", "qiyougamebooster"}, cbi("qiyougamebooster"), ("QiYou Game Booster"), 99) - page.dependent = false - page.acl_depends = {"luci-app-qiyougamebooster"} -end diff --git a/luci-app-qiyougamebooster/luasrc/model/cbi/qiyougamebooster.lua b/luci-app-qiyougamebooster/luasrc/model/cbi/qiyougamebooster.lua deleted file mode 100644 index 4fcde4a06..000000000 --- a/luci-app-qiyougamebooster/luasrc/model/cbi/qiyougamebooster.lua +++ /dev/null @@ -1,28 +0,0 @@ -require("luci.sys") - -m = Map("qiyougamebooster", translate("QiYou Game Booster"), - translate("Play console games online with less lag and more stability.") .. "
" .. - translate("— now supporting PS, Switch, Xbox, PC, and mobile.")) - -s = m:section(TypedSection, "qiyougamebooster") -s.anonymous = true -s.addremove = false - -sts = luci.sys.exec("qiyougamebooster.sh status 2> /dev/null") -ver = luci.sys.exec("qiyougamebooster.sh version 2> /dev/null") -o = s:option(DummyValue, "status") -o.rawhtml = true -if sts == "NOT ENABLED" or sts == "NOT SUPPORTED" or sts == "NOT RUNNING" then - o.value = string.format('%s: %s %s', "red", translate("Status"), ver, translate(sts)) -else - o.value = string.format('%s: %s %s', "green", translate("Status"), ver, translate(sts)) -end - -o = s:option(Flag, "enabled", translate("Enable")) -o.default = 0 - -o = s:option(DummyValue, "instructions") -o.rawhtml = true -o.value = "

" - -return m diff --git a/luci-app-qiyougamebooster/po/templates/qiyougamebooster.pot b/luci-app-qiyougamebooster/po/templates/qiyougamebooster.pot new file mode 100644 index 000000000..d01d1f585 --- /dev/null +++ b/luci-app-qiyougamebooster/po/templates/qiyougamebooster.pot @@ -0,0 +1,52 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:26 +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:28 +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:53 +msgid "QiYou Game Booster" +msgstr "" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:54 +msgid "Play console games online with less lag and more stability." +msgstr "" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:55 +msgid "— now supporting PS, Switch, Xbox, PC, and mobile." +msgstr "" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:70 +msgid "Collecting data..." +msgstr "" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:76 +msgid "Enable" +msgstr "" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:38 +msgid "Status" +msgstr "" + +msgid "NOT ENABLED" +msgstr "" + +msgid "CLOSED" +msgstr "" + +msgid "BOOSTING" +msgstr "" + +msgid "RUNNING" +msgstr "" + +msgid "NOT SUPPORTED" +msgstr "" + +msgid "DOWNLOADING" +msgstr "" + +msgid "INSTALLING" +msgstr "" + +msgid "NOT RUNNING" +msgstr "" diff --git a/luci-app-qiyougamebooster/po/zh-cn b/luci-app-qiyougamebooster/po/zh-cn new file mode 120000 index 000000000..8d69574dd --- /dev/null +++ b/luci-app-qiyougamebooster/po/zh-cn @@ -0,0 +1 @@ +zh_Hans \ No newline at end of file diff --git a/luci-app-qiyougamebooster/po/zh-cn/qiyougamebooster.po b/luci-app-qiyougamebooster/po/zh-cn/qiyougamebooster.po deleted file mode 100644 index 9740f1354..000000000 --- a/luci-app-qiyougamebooster/po/zh-cn/qiyougamebooster.po +++ /dev/null @@ -1,38 +0,0 @@ -msgid "QiYou Game Booster" -msgstr "奇游联机宝" - -msgid "Play console games online with less lag and more stability." -msgstr "加速主机游戏,实现满速下载、稳定联机、飞快匹配体验。" - -msgid "— now supporting PS, Switch, Xbox, PC, and mobile." -msgstr "—— 现已支持 PS、Switch、Xbox、PC 与移动端。" - -msgid "Enable" -msgstr "启用" - -msgid "Status" -msgstr "状态" - -msgid "NOT ENABLED" -msgstr "未启用" - -msgid "CLOSED" -msgstr "已关闭" - -msgid "BOOSTING" -msgstr "加速中" - -msgid "RUNNING" -msgstr "运行中" - -msgid "NOT SUPPORTED" -msgstr "不支持" - -msgid "DOWNLOADING" -msgstr "下载中" - -msgid "INSTALLING" -msgstr "安装中" - -msgid "NOT RUNNING" -msgstr "未运行" diff --git a/luci-app-qiyougamebooster/po/zh_Hans b/luci-app-qiyougamebooster/po/zh_Hans deleted file mode 120000 index 41451e4a1..000000000 --- a/luci-app-qiyougamebooster/po/zh_Hans +++ /dev/null @@ -1 +0,0 @@ -zh-cn \ No newline at end of file diff --git a/luci-app-qiyougamebooster/po/zh_Hans/qiyougamebooster.po b/luci-app-qiyougamebooster/po/zh_Hans/qiyougamebooster.po new file mode 100644 index 000000000..1e5856f32 --- /dev/null +++ b/luci-app-qiyougamebooster/po/zh_Hans/qiyougamebooster.po @@ -0,0 +1,52 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:26 +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:28 +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:53 +msgid "QiYou Game Booster" +msgstr "奇游联机宝" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:54 +msgid "Play console games online with less lag and more stability." +msgstr "加速主机游戏,实现满速下载、稳定联机、飞快匹配体验。" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:55 +msgid "— now supporting PS, Switch, Xbox, PC, and mobile." +msgstr "—— 现已支持 PS、Switch、Xbox、PC 与移动端。" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:70 +msgid "Collecting data..." +msgstr "正在收集数据中..." + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:76 +msgid "Enable" +msgstr "启用" + +#: applications/luci-app-qiyougamebooster/htdocs/luci-static/resources/view/qiyougamebooster.js:38 +msgid "Status" +msgstr "状态" + +msgid "NOT ENABLED" +msgstr "未启用" + +msgid "CLOSED" +msgstr "已关闭" + +msgid "BOOSTING" +msgstr "加速中" + +msgid "RUNNING" +msgstr "运行中" + +msgid "NOT SUPPORTED" +msgstr "不支持" + +msgid "DOWNLOADING" +msgstr "下载中" + +msgid "INSTALLING" +msgstr "安装中" + +msgid "NOT RUNNING" +msgstr "未运行" diff --git a/luci-app-qiyougamebooster/root/etc/uci-defaults/qiyougamebooster b/luci-app-qiyougamebooster/root/etc/uci-defaults/qiyougamebooster deleted file mode 100644 index fbd81c30a..000000000 --- a/luci-app-qiyougamebooster/root/etc/uci-defaults/qiyougamebooster +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -[ ! -f "/usr/share/ucitrack/luci-app-qiyougamebooster.json" ] && { - cat > /usr/share/ucitrack/luci-app-qiyougamebooster.json << EEOF -{ - "config": "qiyougamebooster", - "init": "qiyougamebooster" -} -EEOF -} - -uci -q batch <<-EOF >/dev/null - delete ucitrack.@qiyougamebooster[-1] - add ucitrack qiyougamebooster - set ucitrack.@qiyougamebooster[-1].init=qiyougamebooster - commit ucitrack -EOF - -rm -f /tmp/luci-indexcache -exit 0 diff --git a/luci-app-qiyougamebooster/root/usr/share/luci/menu.d/luci-app-qiyougamebooster.json b/luci-app-qiyougamebooster/root/usr/share/luci/menu.d/luci-app-qiyougamebooster.json new file mode 100644 index 000000000..0c161061a --- /dev/null +++ b/luci-app-qiyougamebooster/root/usr/share/luci/menu.d/luci-app-qiyougamebooster.json @@ -0,0 +1,16 @@ +{ + "admin/services/qiyougamebooster": { + "title": "QiYou Game Booster", + "action": { + "type": "view", + "path": "qiyougamebooster" + }, + "depends": { + "acl": [ "luci-app-qiyougamebooster" ], + "fs": { + "/usr/bin/qiyougamebooster.sh": "executable" + }, + "uci": { "qiyougamebooster": true } + } + } +} diff --git a/luci-app-qiyougamebooster/root/usr/share/rpcd/acl.d/luci-app-qiyougamebooster.json b/luci-app-qiyougamebooster/root/usr/share/rpcd/acl.d/luci-app-qiyougamebooster.json index ed85a9ce5..205ce66e3 100644 --- a/luci-app-qiyougamebooster/root/usr/share/rpcd/acl.d/luci-app-qiyougamebooster.json +++ b/luci-app-qiyougamebooster/root/usr/share/rpcd/acl.d/luci-app-qiyougamebooster.json @@ -1,7 +1,10 @@ { - "luci-app-qiyougamebooster": { + "luci-app-qiyougamebooster": { "description": "Grant UCI access for luci-app-qiyougamebooster", "read": { + "file": { + "/usr/bin/qiyougamebooster.sh": [ "exec" ] + }, "uci": [ "qiyougamebooster" ] }, "write": { diff --git a/mosdns/Makefile b/mosdns/Makefile index c6e2048c9..6d21b80cd 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:=7 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/206-add-auto-reload-rule-files-on-modification.patch b/mosdns/patches/206-add-auto-reload-rule-files-on-modification.patch new file mode 100644 index 000000000..403e714cf --- /dev/null +++ b/mosdns/patches/206-add-auto-reload-rule-files-on-modification.patch @@ -0,0 +1,697 @@ +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 + }