update 2026-07-19 00:51:28

This commit is contained in:
action committed 2026-07-19 00:51:28 +08:00
1 parent c43863be38
commit 270f321458
11 files changed
+1859 -1123

No files matched your search

@@ -24,9 +24,10 @@ local SUPPORTED_COMPONENTS = {
naiveproxy = true
}
local SUPPORTED_GEO_COMPONENTS = {
country_mmdb = true,
geosite = true,
v2ray_geo = true
{ name = "country_mmdb", require = nil },
{ name = "geosite", require = "/etc/openclash" },
{ name = "v2ray_geoip", require = nil },
{ name = "v2ray_geosite", require = nil }
}
local function trim(value)
@@ -576,7 +577,15 @@ local function read_component_state(component, action)
end
local function read_geo_state(component, action)
if not SUPPORTED_GEO_COMPONENTS[component] then
local is_supported = false
for _, item in ipairs(SUPPORTED_GEO_COMPONENTS) do
if item.name == component then
is_supported = true
break
end
end
if not is_supported then
return nil, 400, "unsupported_component"
end
@@ -649,6 +658,7 @@ function index()
entry({"admin", "services", "shadowsocksr", "component_set_mirror"}, call("component_set_mirror")).leaf = true
entry({"admin", "services", "shadowsocksr", "component_status"}, call("component_status")).leaf = true
entry({"admin", "services", "shadowsocksr", "component_upgrade"}, call("component_upgrade")).leaf = true
entry({"admin", "services", "shadowsocksr", "geo_component"}, call("geo_component")).leaf = true
entry({"admin", "services", "shadowsocksr", "geo_local_status"}, call("geo_local_status")).leaf = true
entry({"admin", "services", "shadowsocksr", "geo_status"}, call("geo_status")).leaf = true
entry({"admin", "services", "shadowsocksr", "geo_upgrade"}, call("geo_upgrade")).leaf = true
@@ -1016,6 +1026,20 @@ function geo_status()
write_geo_json(data)
end
function geo_component()
local component = {}
for _, item in ipairs(SUPPORTED_GEO_COMPONENTS) do
local available = true
if item.require then
available = nixio.fs.access(item.require)
end
if available then
table.insert(component, item.name)
end
end
write_geo_json({ component = component })
end
function geo_local_status()
local component = luci.http.formvalue("component")
local data, status, err = read_geo_state(component, "local_info")
@@ -1035,6 +1059,19 @@ function geo_upgrade()
write_geo_json({component = component, error = err or "bad_request", success = "0"})
return
end
local info = read_geo_state(component, "info")
if info then
for key, value in pairs(info) do
if data[key] == nil or data[key] == "" then
data[key] = value
end
end
if data.success == "1" then
data.can_upgrade = info.can_upgrade
end
end
write_geo_json(data)
end
@@ -1287,22 +1324,30 @@ function clash_client_rule_clear()
end
function fetch_certsha256()
local function fetch_cert_sha256(host, port, sni, timeout)
local function fetch_cert_sha256(host, port, sni, timeout, http3)
if not host then return "" end
port = tonumber(port) or 443
sni = sni or host
timeout = tonumber(timeout) or 5
local 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, sni
)
local cmd
if http3 then
cmd = string.format(
"timeout %d curl --http3 -k -w '%%{certs}' -o /dev/null https://%s:%d 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
)
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, sni
)
end
local out = trim(luci.sys.exec(cmd))
local fp = out:match("^([0-9a-fA-F]+)")
if not fp or fp:lower():match("^e3b0c44298fc1c149afbf4c8996fb924") then
return ""
@@ -1316,12 +1361,22 @@ function fetch_certsha256()
local port = tonumber(port_raw) or 0
local sni = (id ~= "") and uci:get("shadowsocksr", sid, "tls_host") or ""
sni = (sni and sni ~= "") and sni or address
local protocol = uci:get("shadowsocksr", id, "v2ray_protocol") or ""
local h3, timeout = false, 10
if protocol == "hysteria2" then
h3 = true
timeout = 60
if port == 0 then
local hop = uci:get("shadowsocksr", id, "port_range") or "0"
port = tonumber(hop:match("^%s*(%d+)"))
end
end
if address == "" or port == 0 then
luci.http.prepare_content("application/json")
luci.http.write_json({ code = 0, msg = "Address or Port is invalid" })
return
end
local data = fetch_cert_sha256(address, port, sni, 5)
local data = fetch_cert_sha256(address, port, sni, timeout, h3)
luci.http.prepare_content("application/json")
luci.http.write_json(data ~= "" and { code = 1, data = data } or { code = 0 })
end
@@ -22,15 +22,20 @@
var geoMeta = {
country_mmdb: {
label: '<%:Country MMDB%>',
help: '<%:Update the Country.mmdb database used by Mihomo/Clash runtime when available.%>'
help: '<%:Update the Country.mmdb database used by Xray/V2Ray runtime when available.%>'
},
geosite: {
label: '<%:GeoSite Database%>',
help: '<%:Update the GeoSite.dat database used by Mihomo/Clash runtime when available.%>'
label: '<%:OpenClash GeoSite Database%>',
help: '<%:Update the GeoSite.dat database used by Mihomo/Clash runtime when available.%>',
require: '/etc/openclash'
},
v2ray_geo: {
label: '<%:V2Ray GEO Databases%>',
help: '<%:Update geoip.dat and geosite.dat used by Xray/V2Ray runtime when available.%>'
v2ray_geoip: {
label: '<%:V2Ray GeoIP Databases%>',
help: '<%:Update the geoip.dat used by Xray/V2Ray runtime when available.%>'
},
v2ray_geosite: {
label: '<%:V2Ray GeoSite Databases%>',
help: '<%:Update the geosite.dat used by Xray/V2Ray runtime when available.%>'
}
};
@@ -91,6 +96,7 @@
}
function renderComponentInfo(data, checkedRemote) {
// console.log('Geo data:', data);
var panel = document.getElementById('component-status');
var upgradeButton = document.getElementById('component-upgrade');
var action = document.getElementById('component-action');
@@ -192,6 +198,7 @@
mirror: mirror
}, function() {
queryLocalComponentInfo();
queryLocalGeoInfo();
});
}
@@ -204,7 +211,41 @@
return geoMeta[component] ? geoMeta[component].help : '';
}
function renderGeoInfo(data) {
function loadGeoComponent() {
var url = '<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "geo_component")%>';
var selector = document.getElementById('geo-selector');
if (!selector)
return;
XHR.get(url, {}, function(x, rv) {
if (!rv || !Array.isArray(rv.component))
return;
var fragment = document.createDocumentFragment();
for (var i = 0; i < rv.component.length; i++) {
var comp = rv.component[i];
var meta = geoMeta[comp];
if (!meta)
continue;
var option = document.createElement('option');
option.value = comp;
option.textContent = meta.label;
fragment.appendChild(option);
}
selector.innerHTML = '';
selector.appendChild(fragment);
if (selector.options.length > 0) {
selector.selectedIndex = 0;
onGeoChange();
}
});
}
function renderGeoInfo(data, checkedRemote) {
// console.log('Geo data:', data);
var panel = document.getElementById('geo-status');
var upgradeButton = document.getElementById('geo-upgrade');
var action = document.getElementById('geo-action');
@@ -227,11 +268,23 @@
}
html.push('<div><strong><%:Current Version%>:</strong> ' + (data.current_version || '<%:File Not Exist%>') + '</div>');
if (data.component !== 'v2ray_geo' && data.current_version_extra) {
html.push('<div><strong><%:Latest Version%>:</strong> ' + (data.latest_version || '<%:Unknown%>') + '</div>');
var installed = data.installed !== undefined ? data.installed : (data.current_version ? true : false);
html.push('<div><strong><%:Installed%>:</strong> ' + (installed ? '<%:Yes%>' : '<%:No%>') + '</div>');
if (data.component !== 'v2ray_geoip' && data.current_version_extra) {
html.push('<div><strong><%:Additional Version%>:</strong> ' + data.current_version_extra + '</div>');
}
if (data.component !== 'v2ray_geosite' && data.current_version_extra) {
html.push('<div><strong><%:Additional Version%>:</strong> ' + data.current_version_extra + '</div>');
}
if (data.error) {
html.push('<div style="color:red"><%:Status%>: ' + displayError(data.error) + '</div>');
} else if (!checkedRemote) {
html.push('<div style="color:#666"><%:Status%>: <%:No Check%></div>');
} else if (data.can_upgrade) {
html.push('<div style="color:#d48806"><%:Status%>: <%:Upgrade available%></div>');
} else if (data.latest_version) {
html.push('<div style="color:green"><%:Status%>: <%:Already up to date%></div>');
} else if (data.message) {
html.push('<div style="color:green"><%:Status%>: ' + (data.success ? displayMessage(data.message) : (data.message || '<%:Unknown%>')) + '</div>');
} else {
@@ -240,7 +293,7 @@
panel.innerHTML = html.join('');
if (upgradeButton) {
upgradeButton.disabled = false;
upgradeButton.disabled = !!data.error || !checkedRemote || !data.latest_version || !data.can_upgrade;
}
if (action) {
action.innerHTML = data.message
@@ -249,26 +302,28 @@
}
}
function queryGeoInfo(button) {
function queryGeoInfo(button, doUpgrade) {
var component = selectedGeoComponent();
var mirror = selectedMirror();
var action = document.getElementById('geo-action');
var url = '<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "geo_upgrade")%>';
var url = doUpgrade
? '<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "geo_upgrade")%>'
: '<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "geo_status")%>';
if (button) {
button.disabled = true;
button.value = '<%:Checking...%>';
button.value = doUpgrade ? '<%:Upgrading...%>' : '<%:Checking...%>';
}
if (action) {
action.innerHTML = '<%:Processing...%>';
action.innerHTML = doUpgrade ? '<%:Processing...%>' : '';
}
XHR.get(url, { component: component, mirror: mirror }, function(x, rv) {
renderGeoInfo(rv);
renderGeoInfo(rv, true);
if (button) {
button.disabled = false;
button.value = '<%:CHECK AND UPDATE%>';
button.value = doUpgrade ? '<%:Upgrade%>' : '<%:Check Update%>';
}
});
}
@@ -282,7 +337,7 @@
component: selectedGeoComponent(),
mirror: selectedMirror()
}, function(x, rv) {
renderGeoInfo(rv);
renderGeoInfo(rv, false);
});
}
@@ -290,9 +345,18 @@
queryLocalGeoInfo();
}
function selectedGeoComponent() {
var selector = document.getElementById('geo-selector');
return selector ? selector.value : 'country_mmdb';
}
function geoHelp(component) {
return geoMeta[component] ? geoMeta[component].help : '';
}
window.setTimeout(function() {
onComponentChange();
onGeoChange();
loadGeoComponent()
}, 0);
//]]>
</script>
@@ -437,8 +501,9 @@
</div>
<select id="geo-selector" class="cbi-input-select component-selector" onchange="onGeoChange()">
<option value="country_mmdb"><%:Country MMDB%></option>
<option value="geosite"><%:GeoSite Database%></option>
<option value="v2ray_geo"><%:V2Ray GEO Databases%></option>
<option value="geosite"><%:OpenClash GeoSite Database%></option>
<option value="v2ray_geoip"><%:V2Ray GeoIP Databases%></option>
<option value="v2ray_geosite"><%:V2Ray GeoSite Databases%></option>
</select>
</div>
</div>
@@ -447,7 +512,8 @@
<div id="geo-status"><em><%:Collecting data...%></em></div>
<div class="component-upgrade-help" id="geo-help"></div>
<div class="component-upgrade-actions">
<input type="button" class="btn cbi-button cbi-button-apply" id="geo-upgrade" value="<%:CHECK AND UPDATE%>" onclick="queryGeoInfo(this)" />
<input type="button" class="btn cbi-button cbi-button-reload" value="<%:Check Update%>" onclick="queryGeoInfo(this, false)" />
<input type="button" class="btn cbi-button cbi-button-apply" id="geo-upgrade" value="<%:Upgrade%>" onclick="queryGeoInfo(this, true)" />
</div>
<div class="component-upgrade-result" id="geo-action"></div>
</div>
@@ -18,7 +18,10 @@
return;
}
if (!res || res.code !== 1 || !res.data) {
alert(res && res.msg ? res.msg : "<%:Fetch Failed%> !");
alert(res && res.msg ? res.msg : "<%:Fetch Failed%> !\n" +
"<%:Requires OpenSSL to retrieve the TLS certificate chain fingerprint.%>\n" +
"<%:QUIC protocols such as HY2 also require curl with http3 support.%>"
);
return;
}
const inputs = document.querySelectorAll('input[id$=".tls_CertSha"]');
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff