⛅ Sync 2026-10-05 09:23:32

This commit is contained in:
github-actions[bot] committed 2026-10-05 09:23:32 +08:00
1 parent 9c9db09f6b
commit e50b16f095
6 files changed
+1 -272

No files matched your search

@@ -90,71 +90,6 @@ return baseclass.extend({
user copies. */
rule_path_default: HP_RULE_PATH_DEFAULT,
/* Parse sing-box's rule-set download failures out of a log.
*
* A remote rule-set is fetched during initialization, before the inbounds
* bind, so a failure there is a failed start:
*
* 2026-10-03 01:15:42 ERROR router: fetch rule-set example: Get "https://…": dial tcp …
*
* That is loud - but only in a log, and only for the rule-sets a user
* defined themselves (the built-in China split is local files, nothing to
* download). A start that failed leaves no running instance and no status
* page, so the reason is readable only over ssh. Surfacing it here is the
* whole job.
*
* Two deliberate choices about the format:
*
* - the tag and the reason are matched on the stable part
* (`fetch rule-set <tag>:`) and nothing assumes sing-box's timestamp
* or level layout. A rule-set whose download failed long ago and whose
* log line moves out of the file must not read as "no failures", so a
* missing timestamp is omitted rather than faked.
* - the last occurrence of a tag wins. The file is truncated at 50 KB by
* clean_log.sh, so a tag that failed every 24 hours is in there
* repeatedly, and the most recent reason is the one that is true now.
*
* The timestamp shape is the one thing that IS pinned, and it was pinned
* from a real 1.14.2 line rather than guessed - the device writes
*
* +0800 2026-10-03 09:29:34 ERROR router: fetch rule-set geoip-cn: Get ...
*
* i.e. a UTC offset BEFORE the date. An earlier version of this anchored
* the date at the start of the line and therefore returned no time for
* every real line, which is the one shape the panel is supposed to show.
* The offset is optional in the pattern so a build that omits it still
* parses.
*
* Returns [{ tag, reason, at }], newest last. Never throws: this reads a
* file that may be empty, half-written, or rotated mid-read. */
parseRuleSetFetchFailures(text) {
const out = [];
if (!text)
return out;
const seen = {};
for (const line of String(text).split('\n')) {
const m = /fetch rule-set\s+([^\s:]+):\s*(.*)$/.exec(line);
if (!m)
continue;
const tag = m[1];
const at = /^(?:[+-]\d{4}\s+)?(\d{4}-\d{2}-\d{2}[ T][0-9:.]+)/.exec(line);
if (seen[tag] !== undefined) {
out[seen[tag]].reason = m[2].trim();
out[seen[tag]].at = at ? at[1] : null;
continue;
}
seen[tag] = out.length;
out.push({ tag: tag, reason: m[2].trim(), at: at ? at[1] : null });
}
return out;
},
dns_strategy: {
'': _('Default'),
'prefer_ipv4': _('Prefer IPv4'),
@@ -75,68 +75,6 @@ const ensureLogPoll = hp.statusPoller({
}
});
/* --- rule-set download failures ------------------------------------------
*
* A remote rule-set that cannot be downloaded stops the service: sing-box
* fetches it during initialization, before the inbounds bind, so a failure
* there is a failed start. That makes it a loud problem, and this block is
* here to make it legible rather than to solve it - a start that failed leaves
* no instance, so sing-box-c.log is the only place the reason is written down.
*
* sing-box-c.log is read rather than a new RPC: the ACL already grants the
* browser read on that file for the log viewer below, so this costs no
* permission and no new method to keep in step with the method table.
*
* The built-in China split is not among the rule-sets this can be about: those
* are local files the resource updater maintains, generated from the same lists
* the nft sets are rendered from, and there is nothing to download. */
const ruleSetNode = E('div', { 'id': 'ruleset_status' },
E('img', {
'src': L.resource('icons/loading.svg'),
'alt': _('Loading'),
'style': 'vertical-align:middle'
}, _('Collecting data...'))
);
function renderRuleSetStatus() {
/* Same poller as the log views, so there is still one handler per view
* no matter how often render() runs. */
ensureLogPoll();
fs.read_direct(`${hp_dir}/sing-box-c.log`, 'text')
.then((text) => {
const failures = hp.parseRuleSetFetchFailures(text);
if (!failures.length) {
dom.content(ruleSetNode, E('span', { 'style': 'color:green' },
[_('No rule-set download failures in the current log.')]));
return;
}
dom.content(ruleSetNode, E('div', [
E('p', { 'style': 'color:red' }, [
_('%d rule-set(s) cannot be downloaded.').format(failures.length)
]),
E('p', { 'style': 'color:gray' }, [
_('A remote rule-set is fetched before the service starts, so a rule-set that cannot be downloaded prevents the start. The service is running, so the log below is from an earlier attempt or the download has since recovered.')
]),
E('ul', {}, failures.map((f) => E('li', [
E('code', [ f.tag ]),
' — ',
/* Text nodes, never markup: the reason is a string
* sing-box wrote, and it contains a URL. */
f.reason || _('(no reason recorded)'),
f.at ? E('small', { 'style': 'color:gray' }, [ ' — ' + f.at ]) : ''
])))
]));
})
.catch((err) => {
dom.content(ruleSetNode, E('span', { 'style': 'color:gray' }, [
_('Cannot read the sing-box client log (%s).').format(String(err))
]));
});
}
function getConnStat(o, site) {
o.default = E('div', { 'style': 'cbi-value-field' }, [
E('button', {
@@ -363,20 +301,6 @@ return view.extend({
return node;
}
s = m.section(form.NamedSection, 'config', 'homeproxy-pro', _('Rule sets'));
s.anonymous = true;
/* A dedicated section rather than a fourth log view: the question is
* "is any rule-set currently broken", and the answer has to be a
* verdict plus a per-tag reason, not a wall of log the user has to read
* a timestamp out of. */
o = s.option(form.DummyValue, '_ruleset_status');
o.rawhtml = true;
o.render = function() {
renderRuleSetStatus();
return ruleSetNode;
};
/* A dedicated section rather than a fourth log view: the question this
* answers is "collect everything someone would ask me to look at, in
* one file", and the answer is a download rather than something to read