mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 11:45:13 +08:00
⛅ Sync 2026-10-05 09:23:32
This commit is contained in:
1 parent
9c9db09f6b
commit
e50b16f095
6 files changed
+1
-272
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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);
|
||||
Reference in new issue
Block a user