From e50b16f095e6bf274f87926f6c0f6c6e50a57d93 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 5 Oct 2026 09:23:32 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9B=85=20Sync=202026-10-05=2009:23:32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-homeproxy-pro/Makefile | 2 +- .../luci-static/resources/homeproxy-pro.js | 65 -------------- .../resources/view/homeproxy-pro/status.js | 76 ---------------- .../po/templates/homeproxy-pro.pot | 20 ----- .../po/zh_Hans/homeproxy-pro.po | 20 ----- .../tests/frontend-rpc-fallbacks.js | 90 ------------------- 6 files changed, 1 insertion(+), 272 deletions(-) diff --git a/luci-app-homeproxy-pro/Makefile b/luci-app-homeproxy-pro/Makefile index 4b1333761..2924bd76f 100644 --- a/luci-app-homeproxy-pro/Makefile +++ b/luci-app-homeproxy-pro/Makefile @@ -18,7 +18,7 @@ LUCI_DEPENDS:= \ PKG_NAME:=luci-app-homeproxy-pro PKG_VERSION:=28.10.1.14 -PKG_RELEASE:=19 +PKG_RELEASE:=20 LUCI_BASENAME:=homeproxy-pro diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/homeproxy-pro.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/homeproxy-pro.js index 01cf04886..bf27dc6ad 100644 --- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/homeproxy-pro.js +++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/homeproxy-pro.js @@ -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 :`) 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'), diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/status.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/status.js index ae3e26ebb..5a6dd650c 100644 --- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/status.js +++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/status.js @@ -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 diff --git a/luci-app-homeproxy-pro/po/templates/homeproxy-pro.pot b/luci-app-homeproxy-pro/po/templates/homeproxy-pro.pot index 239b47860..081f1be20 100644 --- a/luci-app-homeproxy-pro/po/templates/homeproxy-pro.pot +++ b/luci-app-homeproxy-pro/po/templates/homeproxy-pro.pot @@ -1,10 +1,6 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:125 -msgid "%d rule-set(s) cannot be downloaded." -msgstr "" - #: htdocs/luci-static/resources/view/homeproxy-pro/status.js:302 msgid "%s log" msgstr "" @@ -25,10 +21,6 @@ msgstr "" msgid "(never updated on this device)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:137 -msgid "(no reason recorded)" -msgstr "" - #: htdocs/luci-static/resources/homeproxy-pro.js:1193 msgid "/etc/homeproxy-pro/certs/..., /etc/acme/..., /etc/ssl/..." msgstr "" @@ -365,10 +357,6 @@ msgstr "" msgid "Cancel" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:144 -msgid "Cannot read the sing-box client log (%s)." -msgstr "" - #: htdocs/luci-static/resources/view/homeproxy-pro/node.js:425 #: htdocs/luci-static/resources/view/homeproxy-pro/server.js:511 msgid "Certificate path" @@ -1797,10 +1785,6 @@ msgstr "" msgid "No additional encryption support: It's basically duplicate encryption." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:119 -msgid "No rule-set download failures in the current log." -msgstr "" - #: htdocs/luci-static/resources/view/homeproxy-pro/node.js:797 msgid "No subscription available" msgstr "" @@ -2366,10 +2350,6 @@ msgstr "" msgid "Rule set URL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:375 -msgid "Rule sets" -msgstr "" - #: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:56 msgid "" "Rule-set file format. Leave it empty and HomeProxy reads it from the file " diff --git a/luci-app-homeproxy-pro/po/zh_Hans/homeproxy-pro.po b/luci-app-homeproxy-pro/po/zh_Hans/homeproxy-pro.po index 3ca75a6c0..65e41ef65 100644 --- a/luci-app-homeproxy-pro/po/zh_Hans/homeproxy-pro.po +++ b/luci-app-homeproxy-pro/po/zh_Hans/homeproxy-pro.po @@ -8,10 +8,6 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 8bit\n" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:125 -msgid "%d rule-set(s) cannot be downloaded." -msgstr "%d 条规则集无法下载。" - #: htdocs/luci-static/resources/view/homeproxy-pro/status.js:302 msgid "%s log" msgstr "%s 日志" @@ -32,10 +28,6 @@ msgstr "(最后更新 %s)" msgid "(never updated on this device)" msgstr "(本设备从未更新)" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:137 -msgid "(no reason recorded)" -msgstr "(未记录原因)" - #: htdocs/luci-static/resources/homeproxy-pro.js:1193 msgid "/etc/homeproxy-pro/certs/..., /etc/acme/..., /etc/ssl/..." msgstr "/etc/homeproxy-pro/certs/...,/etc/acme/...,/etc/ssl/..." @@ -378,10 +370,6 @@ msgstr "CUBIC" msgid "Cancel" msgstr "取消" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:144 -msgid "Cannot read the sing-box client log (%s)." -msgstr "无法读取 sing-box 客户端日志(%s)。" - #: htdocs/luci-static/resources/view/homeproxy-pro/node.js:425 #: htdocs/luci-static/resources/view/homeproxy-pro/server.js:511 msgid "Certificate path" @@ -1844,10 +1832,6 @@ msgstr "无 TCP 传输层, 纯 HTTP 已合并到 HTTP 传输层。" msgid "No additional encryption support: It's basically duplicate encryption." msgstr "无额外加密支持:它基本上是重复加密。" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:119 -msgid "No rule-set download failures in the current log." -msgstr "当前日志中没有规则集下载失败。" - #: htdocs/luci-static/resources/view/homeproxy-pro/node.js:797 msgid "No subscription available" msgstr "无可用订阅" @@ -2422,10 +2406,6 @@ msgstr "规则集 IP CIDR 作为源 IP" msgid "Rule set URL" msgstr "规则集 URL" -#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:375 -msgid "Rule sets" -msgstr "规则集" - #: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:56 msgid "" "Rule-set file format. Leave it empty and HomeProxy reads it from the file " diff --git a/luci-app-homeproxy-pro/tests/frontend-rpc-fallbacks.js b/luci-app-homeproxy-pro/tests/frontend-rpc-fallbacks.js index 81cbc33ac..28ac251db 100644 --- a/luci-app-homeproxy-pro/tests/frontend-rpc-fallbacks.js +++ b/luci-app-homeproxy-pro/tests/frontend-rpc-fallbacks.js @@ -176,95 +176,5 @@ function makePoll() { check('the registered handler calls read() when invoked', reads === 1, `${reads} reads`); } -/* --- the rule-set degradation report ------------------------------------ - * - * A remote rule-set is fetched before the service starts, so a failure there - * is a failed start and the running instance has nothing to show for it. The - * reason is only ever written to a log, and a start that failed has no status - * page - so this parse is the only place it becomes legible without ssh. - * - * It parses a file sing-box is concurrently writing and that clean_log.sh - * truncates at 50 KB, so the cases below are about tolerance and about not - * lying: a missing timestamp must read as "no timestamp", never as a guess. - */ -const FAILURES = hp.parseRuleSetFetchFailures; - -/* The first two lines are VERBATIM from a real sing-box 1.14.2 run on the - * target (an isolated instance with the rule-set URLs pointed at an - * unreachable port), including the `+0800` UTC offset that precedes the date. - * - * That offset is the reason this case exists. The first version of the - * timestamp pattern was anchored at the start of the line and matched a bare - * date, which parses fine against every hand-written sample and returns null - * for every line the device actually writes - so the panel would have shown - * the right tag and the right reason and no time on every real failure. Only - * a real line catches that, and it is pinned here so it cannot come back. */ -const SAMPLE = [ - '+0800 2026-10-03 09:29:34 INFO sing-box started (0.05s)', - '+0800 2026-10-03 09:29:34 ERROR router: fetch rule-set geoip-cn: Get "http://127.0.0.1:9/geoip-cn.srs": remote error: open connection to 127.0.0.1:9 using outbound/direct[direct]: dial tcp 127.0.0.1:9: connect: connection refused', - '+0800 2026-10-03 09:29:34 ERROR router: fetch rule-set geosite-cn: Get "http://127.0.0.1:9/geosite-cn.srs": remote error: open connection to 127.0.0.1:9 using outbound/direct[direct]: dial tcp 127.0.0.1:9: connect: connection refused', - '' -].join('\n'); - -check('an empty log yields no failures', FAILURES('').length === 0); -check('a log of blank lines yields no failures', FAILURES('\n\n').length === 0); -check('a log with no fetch lines yields no failures', - FAILURES('2026-10-03 01:15:42 INFO router: loaded rule-set geoip-cn').length === 0); - -const sample = FAILURES(SAMPLE); -check('both failing tags are reported', sample.length === 2, JSON.stringify(sample)); -check('the tag is captured without the surrounding prose', - sample.map((f) => f.tag).join(',') === 'geoip-cn,geosite-cn', - JSON.stringify(sample.map((f) => f.tag))); -check('the reason is captured', - sample[0].reason.startsWith('Get "http://'), JSON.stringify(sample[0].reason)); -check('the timestamp is captured despite the +0800 offset before it', - sample[0].at === '2026-10-03 09:29:34', String(sample[0].at)); -check('the reason survives the full real line', - sample[1].reason.startsWith('Get "http://127.0.0.1:9/'), JSON.stringify(sample[1].reason)); -check('the reason keeps the nested colons and quotes', - sample[1].reason.includes('dial tcp 127.0.0.1:9: connect: connection refused'), - JSON.stringify(sample[1].reason)); - -/* A negative UTC offset, and a build that omits the offset entirely, both have - * to keep working - the offset is optional in the pattern on purpose. */ -const westOfUTC = FAILURES('-0500 2026-12-31 20:00:00 ERROR router: fetch rule-set geoip-cn: timeout'); -check('a negative UTC offset still yields the time', - westOfUTC[0].at === '2026-12-31 20:00:00', String(westOfUTC[0].at)); -const noOffset = FAILURES('2026-12-31 20:00:00 ERROR router: fetch rule-set geoip-cn: timeout'); -check('a line with no offset at all still yields the time', - noOffset[0].at === '2026-12-31 20:00:00', String(noOffset[0].at)); - -/* A rule-set that fails on every 24-hour refresh is in the log many times, and - * the file is truncated - so the most recent line has to win, or the panel - * shows a reason from yesterday as if it were current. */ -const REPEATED = [ - '+0800 2026-10-01 01:00:00 ERROR router: fetch rule-set geoip-cn: old failure', - '+0800 2026-10-03 01:00:00 ERROR router: fetch rule-set geoip-cn: new failure' -].join('\n'); -const repeated = FAILURES(REPEATED); -check('a repeated tag is reported once', repeated.length === 1, JSON.stringify(repeated)); -check('the most recent failure wins for a repeated tag', - repeated[0].reason === 'new failure', JSON.stringify(repeated[0].reason)); - -/* clean_log.sh can cut mid-line, and a truncated line must not become a - * failure with a nonsense reason - but it must still name its tag, or the - * panel would hide a rule-set that really is failing. */ -const truncated = FAILURES('+0800 2026-10-03 01:00:00 ERROR router: fetch rule-set geoip-cn: Get "https://exa'); -check('a truncated line still names its tag', truncated.length === 1, JSON.stringify(truncated)); -check('a truncated line keeps the timestamp it has', - truncated[0].at === '2026-10-03 01:00:00', String(truncated[0].at)); - -/* The format is not assumed. A line with no timestamp is still reported, with - * the time simply absent - the panel omits it rather than inventing one. */ -const noTime = FAILURES('ERROR router: fetch rule-set geosite-cn: dial tcp: i/o timeout'); -check('a line without a timestamp is still reported', noTime.length === 1, JSON.stringify(noTime)); -check('a line without a timestamp reports no time', noTime[0].at === null, String(noTime[0].at)); -check('a line without a timestamp still yields the reason', - noTime[0].reason === 'dial tcp: i/o timeout', JSON.stringify(noTime[0].reason)); - -check('the parser tolerates null', FAILURES(null).length === 0); -check('the parser tolerates undefined', FAILURES(undefined).length === 0); - console.log(`frontend rpc fallbacks: ${checks} checks, ${failures} failures`); process.exit(failures ? 1 : 0);