mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 08:05:13 +08:00
🎉 Sync 2026-04-20 23:57:06
This commit is contained in:
1 parent
ede68af29e
commit
c8457a2742
27 files changed
+1000
-151
No files matched your search
+2
-2
@@ -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)?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
"clean_log",
|
||||
"flush_cache",
|
||||
"get_version",
|
||||
"print_log",
|
||||
"restart"
|
||||
"print_log"
|
||||
]
|
||||
},
|
||||
"uci": [ "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." };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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:= \
|
||||
|
||||
@@ -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
|
||||
@@ -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("<a href='#' onclick='fetchCertSha256(); return false;'>%s</a>", "[ " .. 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 })
|
||||
|
||||
@@ -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 2>/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
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
@@ -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 配置"
|
||||
|
||||
|
||||
@@ -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 = '<span style="color:%s"><strong>%s: %s %s</strong></span>';
|
||||
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.') + '<br />' +
|
||||
_('— 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();
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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
|
||||
@@ -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.") .. "<br />" ..
|
||||
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('<span style="color:%s"><strong>%s: %s %s</strong></span>', "red", translate("Status"), ver, translate(sts))
|
||||
else
|
||||
o.value = string.format('<span style="color:%s"><strong>%s: %s %s</strong></span>', "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 = "<p><img src='/qiyougamebooster/Tutorial.png' height='350'/></p>"
|
||||
|
||||
return m
|
||||
@@ -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 ""
|
||||
+1
@@ -0,0 +1 @@
|
||||
zh_Hans
|
||||
@@ -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 "未运行"
|
||||
@@ -1 +0,0 @@
|
||||
zh-cn
|
||||
@@ -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 "未运行"
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
+1
-1
@@ -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)?
|
||||
|
||||
@@ -0,0 +1,697 @@
|
||||
From 9554c5b29aff4107504bdf45c35582e6d9743a93 Mon Sep 17 00:00:00 2001
|
||||
From: sbwml <admin@cooluc.com>
|
||||
Date: Mon, 20 Apr 2026 23:06:17 +0800
|
||||
Subject: [PATCH] add auto-reload rule files on modification
|
||||
|
||||
Signed-off-by: sbwml <admin@cooluc.com>
|
||||
---
|
||||
pkg/utils/file_watcher.go | 73 +++++++++++++++
|
||||
plugin/data_provider/domain_set/domain_set.go | 71 +++++++++++---
|
||||
plugin/data_provider/ip_set/ip_set.go | 75 +++++++++++----
|
||||
plugin/executable/arbitrary/arbitrary.go | 81 +++++++++++-----
|
||||
plugin/executable/hosts/hosts.go | 87 ++++++++++++-----
|
||||
plugin/executable/redirect/redirect.go | 93 +++++++++++++------
|
||||
6 files changed, 374 insertions(+), 106 deletions(-)
|
||||
create mode 100644 pkg/utils/file_watcher.go
|
||||
|
||||
--- /dev/null
|
||||
+++ b/pkg/utils/file_watcher.go
|
||||
@@ -0,0 +1,73 @@
|
||||
+package utils
|
||||
+
|
||||
+import (
|
||||
+ "os"
|
||||
+ "time"
|
||||
+)
|
||||
+
|
||||
+// FileWatcher periodically checks the modification time of the specified files.
|
||||
+type FileWatcher struct {
|
||||
+ done chan struct{}
|
||||
+}
|
||||
+
|
||||
+// StartFileWatcher starts a new FileWatcher that checks files every 'interval'.
|
||||
+// If any file modification time changes or a file is missing, onChange is triggered.
|
||||
+// Note: onChange must be safe to run concurrently with other components.
|
||||
+func StartFileWatcher(files []string, interval time.Duration, onChange func(changedFiles []string)) *FileWatcher {
|
||||
+ if len(files) == 0 {
|
||||
+ return nil
|
||||
+ }
|
||||
+ fw := &FileWatcher{
|
||||
+ done: make(chan struct{}),
|
||||
+ }
|
||||
+ go func() {
|
||||
+ modTimes := make(map[string]time.Time)
|
||||
+ for _, f := range files {
|
||||
+ stat, err := os.Stat(f)
|
||||
+ if err == nil {
|
||||
+ modTimes[f] = stat.ModTime()
|
||||
+ }
|
||||
+ }
|
||||
+ ticker := time.NewTicker(interval)
|
||||
+ defer ticker.Stop()
|
||||
+
|
||||
+ for {
|
||||
+ select {
|
||||
+ case <-fw.done:
|
||||
+ return
|
||||
+ case <-ticker.C:
|
||||
+ var changedFiles []string
|
||||
+ for _, f := range files {
|
||||
+ stat, err := os.Stat(f)
|
||||
+ if err != nil {
|
||||
+ if os.IsNotExist(err) {
|
||||
+ // File is missing or deleted.
|
||||
+ if mt, ok := modTimes[f]; ok && !mt.IsZero() {
|
||||
+ modTimes[f] = time.Time{} // mark as missing
|
||||
+ changedFiles = append(changedFiles, f)
|
||||
+ }
|
||||
+ }
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ if mt, ok := modTimes[f]; !ok || mt != stat.ModTime() {
|
||||
+ modTimes[f] = stat.ModTime()
|
||||
+ changedFiles = append(changedFiles, f)
|
||||
+ }
|
||||
+ }
|
||||
+ if len(changedFiles) > 0 {
|
||||
+ onChange(changedFiles)
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }()
|
||||
+ return fw
|
||||
+}
|
||||
+
|
||||
+// Close stops the FileWatcher.
|
||||
+func (fw *FileWatcher) Close() error {
|
||||
+ if fw != nil {
|
||||
+ close(fw.done)
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
--- a/plugin/data_provider/domain_set/domain_set.go
|
||||
+++ b/plugin/data_provider/domain_set/domain_set.go
|
||||
@@ -22,10 +22,15 @@ package domain_set
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
+ "os"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
"github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
"github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
|
||||
- "os"
|
||||
+ "go.uber.org/zap"
|
||||
)
|
||||
|
||||
const PluginType = "domain_set"
|
||||
@@ -51,33 +56,69 @@ type Args struct {
|
||||
var _ data_provider.DomainMatcherProvider = (*DomainSet)(nil)
|
||||
|
||||
type DomainSet struct {
|
||||
- mg []domain.Matcher[struct{}]
|
||||
+ mg atomic.Pointer[MatcherGroup]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
func (d *DomainSet) GetDomainMatcher() domain.Matcher[struct{}] {
|
||||
- return MatcherGroup(d.mg)
|
||||
+ return d
|
||||
+}
|
||||
+
|
||||
+func (d *DomainSet) Match(s string) (struct{}, bool) {
|
||||
+ if mg := d.mg.Load(); mg != nil {
|
||||
+ return mg.Match(s)
|
||||
+ }
|
||||
+ return struct{}{}, false
|
||||
+}
|
||||
+
|
||||
+func (d *DomainSet) Close() error {
|
||||
+ if d.fw != nil {
|
||||
+ return d.fw.Close()
|
||||
+ }
|
||||
+ return nil
|
||||
}
|
||||
|
||||
// NewDomainSet inits a DomainSet from given args.
|
||||
func NewDomainSet(bp *coremain.BP, args *Args) (*DomainSet, error) {
|
||||
ds := &DomainSet{}
|
||||
|
||||
- m := domain.NewDomainMixMatcher()
|
||||
- if err := LoadExpsAndFiles(args.Exps, args.Files, m); err != nil {
|
||||
- return nil, err
|
||||
+ loadInner := func() (*MatcherGroup, error) {
|
||||
+ var mg MatcherGroup
|
||||
+ m := domain.NewDomainMixMatcher()
|
||||
+ if err := LoadExpsAndFiles(args.Exps, args.Files, m); err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ if m.Len() > 0 {
|
||||
+ mg = append(mg, m)
|
||||
+ }
|
||||
+
|
||||
+ for _, tag := range args.Sets {
|
||||
+ provider, _ := bp.M().GetPlugin(tag).(data_provider.DomainMatcherProvider)
|
||||
+ if provider == nil {
|
||||
+ return nil, fmt.Errorf("%s is not a DomainMatcherProvider", tag)
|
||||
+ }
|
||||
+ m := provider.GetDomainMatcher()
|
||||
+ mg = append(mg, m)
|
||||
+ }
|
||||
+ return &mg, nil
|
||||
}
|
||||
- if m.Len() > 0 {
|
||||
- ds.mg = append(ds.mg, m)
|
||||
+
|
||||
+ mg, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
}
|
||||
+ ds.mg.Store(mg)
|
||||
|
||||
- for _, tag := range args.Sets {
|
||||
- provider, _ := bp.M().GetPlugin(tag).(data_provider.DomainMatcherProvider)
|
||||
- if provider == nil {
|
||||
- return nil, fmt.Errorf("%s is not a DomainMatcherProvider", tag)
|
||||
- }
|
||||
- m := provider.GetDomainMatcher()
|
||||
- ds.mg = append(ds.mg, m)
|
||||
+ if len(args.Files) > 0 {
|
||||
+ ds.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newMg, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ ds.mg.Store(newMg)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
|
||||
+ }
|
||||
+ })
|
||||
}
|
||||
+
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
--- a/plugin/data_provider/ip_set/ip_set.go
|
||||
+++ b/plugin/data_provider/ip_set/ip_set.go
|
||||
@@ -22,12 +22,17 @@ package ip_set
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
- "github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
- "github.com/IrineSistiana/mosdns/v5/pkg/matcher/netlist"
|
||||
- "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
+ "github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/netlist"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
|
||||
+ "go.uber.org/zap"
|
||||
)
|
||||
|
||||
const PluginType = "ip_set"
|
||||
@@ -49,31 +54,67 @@ type Args struct {
|
||||
var _ data_provider.IPMatcherProvider = (*IPSet)(nil)
|
||||
|
||||
type IPSet struct {
|
||||
- mg []netlist.Matcher
|
||||
+ mg atomic.Pointer[MatcherGroup]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
func (d *IPSet) GetIPMatcher() netlist.Matcher {
|
||||
- return MatcherGroup(d.mg)
|
||||
+ return d
|
||||
+}
|
||||
+
|
||||
+func (d *IPSet) Match(addr netip.Addr) bool {
|
||||
+ if mg := d.mg.Load(); mg != nil {
|
||||
+ return mg.Match(addr)
|
||||
+ }
|
||||
+ return false
|
||||
+}
|
||||
+
|
||||
+func (d *IPSet) Close() error {
|
||||
+ if d.fw != nil {
|
||||
+ return d.fw.Close()
|
||||
+ }
|
||||
+ return nil
|
||||
}
|
||||
|
||||
func NewIPSet(bp *coremain.BP, args *Args) (*IPSet, error) {
|
||||
p := &IPSet{}
|
||||
|
||||
- l := netlist.NewList()
|
||||
- if err := LoadFromIPsAndFiles(args.IPs, args.Files, l); err != nil {
|
||||
- return nil, err
|
||||
+ loadInner := func() (*MatcherGroup, error) {
|
||||
+ var mg MatcherGroup
|
||||
+ l := netlist.NewList()
|
||||
+ if err := LoadFromIPsAndFiles(args.IPs, args.Files, l); err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ l.Sort()
|
||||
+ if l.Len() > 0 {
|
||||
+ mg = append(mg, l)
|
||||
+ }
|
||||
+ for _, tag := range args.Sets {
|
||||
+ provider, _ := bp.M().GetPlugin(tag).(data_provider.IPMatcherProvider)
|
||||
+ if provider == nil {
|
||||
+ return nil, fmt.Errorf("%s is not an IPMatcherProvider", tag)
|
||||
+ }
|
||||
+ mg = append(mg, provider.GetIPMatcher())
|
||||
+ }
|
||||
+ return &mg, nil
|
||||
}
|
||||
- l.Sort()
|
||||
- if l.Len() > 0 {
|
||||
- p.mg = append(p.mg, l)
|
||||
+
|
||||
+ mg, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
}
|
||||
- for _, tag := range args.Sets {
|
||||
- provider, _ := bp.M().GetPlugin(tag).(data_provider.IPMatcherProvider)
|
||||
- if provider == nil {
|
||||
- return nil, fmt.Errorf("%s is not an IPMatcherProvider", tag)
|
||||
- }
|
||||
- p.mg = append(p.mg, provider.GetIPMatcher())
|
||||
+ p.mg.Store(mg)
|
||||
+
|
||||
+ if len(args.Files) > 0 {
|
||||
+ p.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newMg, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ p.mg.Store(newMg)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
|
||||
+ }
|
||||
+ })
|
||||
}
|
||||
+
|
||||
return p, nil
|
||||
}
|
||||
|
||||
--- a/plugin/executable/arbitrary/arbitrary.go
|
||||
+++ b/plugin/executable/arbitrary/arbitrary.go
|
||||
@@ -23,12 +23,17 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
+ "os"
|
||||
+ "strings"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
"github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/zone_file"
|
||||
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
|
||||
- "os"
|
||||
- "strings"
|
||||
+ "go.uber.org/zap"
|
||||
)
|
||||
|
||||
const PluginType = "arbitrary"
|
||||
@@ -45,38 +50,68 @@ type Args struct {
|
||||
var _ sequence.Executable = (*Arbitrary)(nil)
|
||||
|
||||
type Arbitrary struct {
|
||||
- m *zone_file.Matcher
|
||||
+ m atomic.Pointer[zone_file.Matcher]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
-func NewArbitrary(args *Args) (*Arbitrary, error) {
|
||||
- m := new(zone_file.Matcher)
|
||||
- for i, s := range args.Rules {
|
||||
- if err := m.Load(strings.NewReader(s)); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load rr #%d [%s], %w", i, s, err)
|
||||
- }
|
||||
- }
|
||||
- for i, file := range args.Files {
|
||||
- b, err := os.ReadFile(file)
|
||||
- if err != nil {
|
||||
- return nil, fmt.Errorf("failed to read file #%d [%s], %w", i, file, err)
|
||||
+func NewArbitrary(bp *coremain.BP, args *Args) (*Arbitrary, error) {
|
||||
+ a := &Arbitrary{}
|
||||
+
|
||||
+ loadInner := func() (*zone_file.Matcher, error) {
|
||||
+ m := new(zone_file.Matcher)
|
||||
+ for i, s := range args.Rules {
|
||||
+ if err := m.Load(strings.NewReader(s)); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load rr #%d [%s], %w", i, s, err)
|
||||
+ }
|
||||
}
|
||||
- if err := m.Load(bytes.NewReader(b)); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load rr file #%d [%s], %w", i, file, err)
|
||||
+ for i, file := range args.Files {
|
||||
+ b, err := os.ReadFile(file)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("failed to read file #%d [%s], %w", i, file, err)
|
||||
+ }
|
||||
+ if err := m.Load(bytes.NewReader(b)); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load rr file #%d [%s], %w", i, file, err)
|
||||
+ }
|
||||
}
|
||||
+ return m, nil
|
||||
+ }
|
||||
+
|
||||
+ inner, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ a.m.Store(inner)
|
||||
+
|
||||
+ if len(args.Files) > 0 {
|
||||
+ a.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newInner, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ a.m.Store(newInner)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
|
||||
+ }
|
||||
+ })
|
||||
}
|
||||
- return &Arbitrary{
|
||||
- m: m,
|
||||
- }, nil
|
||||
+
|
||||
+ return a, nil
|
||||
}
|
||||
|
||||
func (a *Arbitrary) Exec(_ context.Context, qCtx *query_context.Context) error {
|
||||
- if r := a.m.Reply(qCtx.Q()); r != nil {
|
||||
- qCtx.SetResponse(r)
|
||||
+ if inner := a.m.Load(); inner != nil {
|
||||
+ if r := inner.Reply(qCtx.Q()); r != nil {
|
||||
+ qCtx.SetResponse(r)
|
||||
+ }
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+func (a *Arbitrary) Close() error {
|
||||
+ if a.fw != nil {
|
||||
+ return a.fw.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
-func Init(_ *coremain.BP, v any) (any, error) {
|
||||
+func Init(bp *coremain.BP, v any) (any, error) {
|
||||
args := v.(*Args)
|
||||
- return NewArbitrary(args)
|
||||
+ return NewArbitrary(bp, args)
|
||||
}
|
||||
--- a/plugin/executable/hosts/hosts.go
|
||||
+++ b/plugin/executable/hosts/hosts.go
|
||||
@@ -23,13 +23,18 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
+ "os"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
"github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/hosts"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
|
||||
"github.com/miekg/dns"
|
||||
- "os"
|
||||
+ "go.uber.org/zap"
|
||||
)
|
||||
|
||||
const PluginType = "hosts"
|
||||
@@ -46,44 +51,76 @@ type Args struct {
|
||||
}
|
||||
|
||||
type Hosts struct {
|
||||
- h *hosts.Hosts
|
||||
+ h atomic.Pointer[hosts.Hosts]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
-func Init(_ *coremain.BP, args any) (any, error) {
|
||||
- return NewHosts(args.(*Args))
|
||||
+func Init(bp *coremain.BP, args any) (any, error) {
|
||||
+ return NewHosts(bp, args.(*Args))
|
||||
}
|
||||
|
||||
-func NewHosts(args *Args) (*Hosts, error) {
|
||||
- m := domain.NewMixMatcher[*hosts.IPs]()
|
||||
- m.SetDefaultMatcher(domain.MatcherFull)
|
||||
- for i, entry := range args.Entries {
|
||||
- if err := domain.Load[*hosts.IPs](m, entry, hosts.ParseIPs); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load entry #%d %s, %w", i, entry, err)
|
||||
- }
|
||||
- }
|
||||
- for i, file := range args.Files {
|
||||
- b, err := os.ReadFile(file)
|
||||
- if err != nil {
|
||||
- return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
|
||||
+func NewHosts(bp *coremain.BP, args *Args) (*Hosts, error) {
|
||||
+ h := &Hosts{}
|
||||
+
|
||||
+ loadInner := func() (*hosts.Hosts, error) {
|
||||
+ m := domain.NewMixMatcher[*hosts.IPs]()
|
||||
+ m.SetDefaultMatcher(domain.MatcherFull)
|
||||
+ for i, entry := range args.Entries {
|
||||
+ if err := domain.Load[*hosts.IPs](m, entry, hosts.ParseIPs); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load entry #%d %s, %w", i, entry, err)
|
||||
+ }
|
||||
}
|
||||
- if err := domain.LoadFromTextReader[*hosts.IPs](m, bytes.NewReader(b), hosts.ParseIPs); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
|
||||
+ for i, file := range args.Files {
|
||||
+ b, err := os.ReadFile(file)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
|
||||
+ }
|
||||
+ if err := domain.LoadFromTextReader[*hosts.IPs](m, bytes.NewReader(b), hosts.ParseIPs); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
|
||||
+ }
|
||||
}
|
||||
+ return hosts.NewHosts(m), nil
|
||||
+ }
|
||||
+
|
||||
+ inner, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
}
|
||||
+ h.h.Store(inner)
|
||||
|
||||
- return &Hosts{
|
||||
- h: hosts.NewHosts(m),
|
||||
- }, nil
|
||||
+ if len(args.Files) > 0 {
|
||||
+ h.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newInner, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ h.h.Store(newInner)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
|
||||
+ }
|
||||
+ })
|
||||
+ }
|
||||
+
|
||||
+ return h, nil
|
||||
}
|
||||
|
||||
func (h *Hosts) Response(q *dns.Msg) *dns.Msg {
|
||||
- return h.h.LookupMsg(q)
|
||||
+ if inner := h.h.Load(); inner != nil {
|
||||
+ return inner.LookupMsg(q)
|
||||
+ }
|
||||
+ return nil
|
||||
}
|
||||
|
||||
func (h *Hosts) Exec(_ context.Context, qCtx *query_context.Context) error {
|
||||
- r := h.h.LookupMsg(qCtx.Q())
|
||||
- if r != nil {
|
||||
- qCtx.SetResponse(r)
|
||||
+ if inner := h.h.Load(); inner != nil {
|
||||
+ r := inner.LookupMsg(qCtx.Q())
|
||||
+ if r != nil {
|
||||
+ qCtx.SetResponse(r)
|
||||
+ }
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+func (h *Hosts) Close() error {
|
||||
+ if h.fw != nil {
|
||||
+ return h.fw.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
--- a/plugin/executable/redirect/redirect.go
|
||||
+++ b/plugin/executable/redirect/redirect.go
|
||||
@@ -25,10 +25,13 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
|
||||
"github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
|
||||
"github.com/miekg/dns"
|
||||
"go.uber.org/zap"
|
||||
@@ -48,11 +51,12 @@ type Args struct {
|
||||
}
|
||||
|
||||
type Redirect struct {
|
||||
- m *domain.MixMatcher[string]
|
||||
+ m atomic.Pointer[domain.MixMatcher[string]]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
func Init(bp *coremain.BP, args any) (any, error) {
|
||||
- r, err := NewRedirect(args.(*Args))
|
||||
+ r, err := NewRedirect(bp, args.(*Args))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,7 +64,8 @@ func Init(bp *coremain.BP, args any) (an
|
||||
return r, nil
|
||||
}
|
||||
|
||||
-func NewRedirect(args *Args) (*Redirect, error) {
|
||||
+func NewRedirect(bp *coremain.BP, args *Args) (*Redirect, error) {
|
||||
+ r := &Redirect{}
|
||||
parseFunc := func(s string) (p, v string, err error) {
|
||||
f := strings.Fields(s)
|
||||
if len(f) != 2 {
|
||||
@@ -68,23 +73,44 @@ func NewRedirect(args *Args) (*Redirect,
|
||||
}
|
||||
return f[0], dns.Fqdn(f[1]), nil
|
||||
}
|
||||
- m := domain.NewMixMatcher[string]()
|
||||
- m.SetDefaultMatcher(domain.MatcherFull)
|
||||
- for i, rule := range args.Rules {
|
||||
- if err := domain.Load[string](m, rule, parseFunc); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load rule #%d %s, %w", i, rule, err)
|
||||
- }
|
||||
- }
|
||||
- for i, file := range args.Files {
|
||||
- b, err := os.ReadFile(file)
|
||||
- if err != nil {
|
||||
- return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
|
||||
+
|
||||
+ loadInner := func() (*domain.MixMatcher[string], error) {
|
||||
+ m := domain.NewMixMatcher[string]()
|
||||
+ m.SetDefaultMatcher(domain.MatcherFull)
|
||||
+ for i, rule := range args.Rules {
|
||||
+ if err := domain.Load[string](m, rule, parseFunc); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load rule #%d %s, %w", i, rule, err)
|
||||
+ }
|
||||
}
|
||||
- if err := domain.LoadFromTextReader[string](m, bytes.NewReader(b), parseFunc); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
|
||||
+ for i, file := range args.Files {
|
||||
+ b, err := os.ReadFile(file)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
|
||||
+ }
|
||||
+ if err := domain.LoadFromTextReader[string](m, bytes.NewReader(b), parseFunc); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
|
||||
+ }
|
||||
}
|
||||
+ return m, nil
|
||||
+ }
|
||||
+
|
||||
+ inner, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ r.m.Store(inner)
|
||||
+
|
||||
+ if len(args.Files) > 0 {
|
||||
+ r.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newInner, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ r.m.Store(newInner)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles), zap.Int("length", newInner.Len()))
|
||||
+ }
|
||||
+ })
|
||||
}
|
||||
- return &Redirect{m: m}, nil
|
||||
+
|
||||
+ return r, nil
|
||||
}
|
||||
|
||||
func (r *Redirect) Exec(ctx context.Context, qCtx *query_context.Context, next sequence.ChainWalker) error {
|
||||
@@ -94,7 +120,12 @@ func (r *Redirect) Exec(ctx context.Cont
|
||||
}
|
||||
|
||||
orgQName := q.Question[0].Name
|
||||
- redirectTarget, ok := r.m.Match(orgQName)
|
||||
+ inner := r.m.Load()
|
||||
+ if inner == nil {
|
||||
+ return next.ExecNext(ctx, qCtx)
|
||||
+ }
|
||||
+
|
||||
+ redirectTarget, ok := inner.Match(orgQName)
|
||||
if !ok {
|
||||
return next.ExecNext(ctx, qCtx)
|
||||
}
|
||||
@@ -104,16 +135,16 @@ func (r *Redirect) Exec(ctx context.Cont
|
||||
q.Question[0].Name = orgQName
|
||||
}()
|
||||
err := next.ExecNext(ctx, qCtx)
|
||||
- if r := qCtx.R(); r != nil {
|
||||
+ if rResp := qCtx.R(); rResp != nil {
|
||||
// Restore original query name.
|
||||
- for i := range r.Question {
|
||||
- if r.Question[i].Name == redirectTarget {
|
||||
- r.Question[i].Name = orgQName
|
||||
+ for i := range rResp.Question {
|
||||
+ if rResp.Question[i].Name == redirectTarget {
|
||||
+ rResp.Question[i].Name = orgQName
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a CNAME record.
|
||||
- newAns := make([]dns.RR, 1, len(r.Answer)+1)
|
||||
+ newAns := make([]dns.RR, 1, len(rResp.Answer)+1)
|
||||
newAns[0] = &dns.CNAME{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: orgQName,
|
||||
@@ -123,12 +154,22 @@ func (r *Redirect) Exec(ctx context.Cont
|
||||
},
|
||||
Target: redirectTarget,
|
||||
}
|
||||
- newAns = append(newAns, r.Answer...)
|
||||
- r.Answer = newAns
|
||||
+ newAns = append(newAns, rResp.Answer...)
|
||||
+ rResp.Answer = newAns
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Redirect) Len() int {
|
||||
- return r.m.Len()
|
||||
+ if inner := r.m.Load(); inner != nil {
|
||||
+ return inner.Len()
|
||||
+ }
|
||||
+ return 0
|
||||
+}
|
||||
+
|
||||
+func (r *Redirect) Close() error {
|
||||
+ if r.fw != nil {
|
||||
+ return r.fw.Close()
|
||||
+ }
|
||||
+ return nil
|
||||
}
|
||||
Reference in new issue
Block a user