mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-26 23:12:52 +08:00
✨ Sync 2026-09-19 17:06:32
This commit is contained in:
1 parent
cee0877435
commit
d1cbb03aed
8 files changed
+251
-7
No files matched your search
@@ -22,8 +22,8 @@ LUCI_DEPENDS:= \
|
||||
+ucode-mod-digest
|
||||
|
||||
PKG_NAME:=luci-app-homeproxy
|
||||
PKG_VERSION:=27.911.1.14
|
||||
PKG_RELEASE:=16
|
||||
PKG_VERSION:=27.919.1.14
|
||||
PKG_RELEASE:=17
|
||||
|
||||
define Package/luci-app-homeproxy/conffiles
|
||||
/etc/config/homeproxy
|
||||
|
||||
@@ -86,6 +86,19 @@
|
||||
| --- | --- | --- |
|
||||
| 重构和修复错误 | 重构:前端 TLS/传输表单收敛、generator 共享 TLS/transport 构建、`parse_uri` 拆为 13 个协议函数<br>测试:协议单测 153 条、generator 回归、LuCI 表单快照<br>修复:`executeCommand` 清理、dnsmasq 路径告警、fw4 清单单源、启动日志、PEM 校验、`wGET` 失败原因<br>工程:提高中文翻译率 CI、架构核实 | <br>parse_uri 对比零差异、generator JSON 逐字节一致、表单快照逐字段一致<br>顺带修复 3 个既有缺陷:证书上传、fw4 清理回滚、dnsmasq 路径 |
|
||||
|
||||
## 稳定性修复(r12)
|
||||
|
||||
修掉两个会让功能整体不可用的缺陷,均由 issue 复现确认。
|
||||
|
||||
| 领域 | 变更 |
|
||||
| --- | --- |
|
||||
| **订阅抓取**(#5) | 抓取前先探测 wget 实现:GNU wget 用 `-nv`(静音进度条但保留失败原因),uclient-fetch / busybox wget 用 `-q`;`--user-agent` / `--timeout` 一并换成三家通用的 `-U` / `-T`。r11 引入的 `-nv` 是 GNU 专有选项,而 OpenWrt / ImmortalWrt 的 `/usr/bin/wget` 默认由 uclient-fetch 提供,参数解析阶段即失败,导致所有订阅更新以 `no valid node found` 结束 |
|
||||
| **自定义路由**(#3 #4) | 修复 `routing_mode='custom'` 完全无法生成配置:`direct_overrides` 的使用早于声明(ucode 不做变量提升,strict 模式下直接抛未声明变量);本地规则集残留 legacy `download_detour` 字段,被 sing-box 判为 `unknown field` 而拒绝整个配置 |
|
||||
| **预设规则集** | `geoip-cn` / `geosite-cn` / `geosite-noncn` 改用 SagerNet 上游 raw URL,并经 `main-out` 下载:直连受 DNS 污染时会超时并静默沿用缓存;每天约 250 KB 的代理开销可忽略,节点引导不受影响 |
|
||||
| **依赖** | Makefile 补上 `+ip-full`、`+kmod-tun` |
|
||||
|
||||
> 回归保障:订阅抓取用 stub wget 覆盖 GNU / 非 GNU 两条分支,并在装有 `/bin/uclient-fetch` 的目标上直接执行生成的命令行;自定义路由新增 `custom.uci` fixture(本地规则集 + 直连节点覆盖 + 路由规则)。
|
||||
|
||||
## 运行要求
|
||||
|
||||
- ImmortalWrt / OpenWrt ≥ 24.10+(apk 或 opkg 均可安装)
|
||||
|
||||
@@ -91,10 +91,44 @@ export function getTime(epoch) {
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* GNU wget and OpenWrt's default /usr/bin/wget do not accept the same flags.
|
||||
* On OpenWrt/ImmortalWrt that path comes from the uclient-fetch package
|
||||
* (`ALTERNATIVES:=200:/usr/bin/wget:/bin/uclient-fetch`, `PROVIDES:=wget` in
|
||||
* package/libs/uclient) and busybox ships no wget applet, so the effective
|
||||
* wget is uclient-fetch unless wget-ssl happens to be installed on top of it.
|
||||
* GNU-only flags break the fetch outright: uclient-fetch's getopt rejects
|
||||
* `-nv` with "unrecognized option: n" before a single request is sent.
|
||||
*
|
||||
* The probe below tells the two apart. `-nv` (--no-verbose) is how GNU wget
|
||||
* mutes the progress meter while still printing the failure reason on stderr;
|
||||
* `-q` is the only meter switch uclient-fetch and busybox wget share.
|
||||
*/
|
||||
export function classifyWgetFlavor(exitcode, stdout) {
|
||||
if (exitcode !== 0 || !stdout)
|
||||
return 'compat';
|
||||
|
||||
return match(stdout, /GNU Wget/) ? 'gnu' : 'compat';
|
||||
};
|
||||
|
||||
let wget_flavor = null;
|
||||
|
||||
export function wgetFlavor() {
|
||||
if (wget_flavor === null) {
|
||||
const probe = executeCommand('/usr/bin/wget --version') || {};
|
||||
|
||||
wget_flavor = classifyWgetFlavor(probe.exitcode, probe.stdout);
|
||||
}
|
||||
|
||||
return wget_flavor;
|
||||
};
|
||||
|
||||
/*
|
||||
* Fetch a URL and report both the body and, on failure, the reason. The
|
||||
* reason is wget's own stderr (whitespace collapsed, length-capped) so the
|
||||
* caller can tell a DNS failure from a timeout or a TLS handshake error.
|
||||
* Note that uclient-fetch's -q hides most failure messages, so on such a
|
||||
* target the reason is often empty and only the exit code is left.
|
||||
*/
|
||||
export function wGETVerbose(url, ua) {
|
||||
if (!url || type(url) !== 'string')
|
||||
@@ -103,8 +137,10 @@ export function wGETVerbose(url, ua) {
|
||||
if (!ua)
|
||||
ua = 'Wget/1.21 (HomeProxy, like v2rayN)';
|
||||
|
||||
/* -nv (not -q) so wget still reports *why* a fetch failed on stderr */
|
||||
const output = executeCommand(`/usr/bin/wget -nv -O- --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {};
|
||||
/* -U/-T are understood by GNU wget, uclient-fetch and busybox wget alike;
|
||||
* the long forms --user-agent/--timeout are not, busybox wget in
|
||||
* particular knows neither of them. */
|
||||
const output = executeCommand(`/usr/bin/wget ${wgetFlavor() === 'gnu' ? '-nv' : '-q'} -O- -U ${shellQuote(ua)} -T 10 ${shellQuote(url)}`) || {};
|
||||
if (output.exitcode !== 0) {
|
||||
let reason = trim(output.stderr || '');
|
||||
reason = reason ? replace(reason, /\s+/g, ' ') : 'no error output';
|
||||
|
||||
@@ -38,6 +38,8 @@ sh tests/ucode/run.sh "$PWD"
|
||||
| `tests/luci-form-snapshot.js` | Dumps every option (name, kind, title, description, depends, values, datatype/default/…) of the node and server views. Diffs against `tests/snapshots/{node,server}.json`, so a refactor that changes a field or its visibility fails. |
|
||||
| `tests/ucode/test_homeproxy_utils.uc` | `executeCommand()` return shape, stderr/exit-code capture, binary detection, and a descriptor-leak check (200 calls). |
|
||||
| `tests/ucode/test_homeproxy_utils_inject.uc` | The failure path of `executeCommand()`: the script stages a copy of `homeproxy.uc` whose `system()` call is replaced by `die()`, then checks that the exception still propagates and that neither descriptor leaks. |
|
||||
| `tests/ucode/test_wget_flavor.uc` | The `wget --version` probe (`classifyWgetFlavor()`) and the command line `wGETVerbose()` builds from it. `run.sh` points the module at a stub wget and runs the file twice — once answering like GNU wget (expects `-nv`), once like uclient-fetch (expects `-q` and no GNU-only option at all). |
|
||||
| `tests/ucode/test_wget_verbose_uclient.uc` | Runs `wGETVerbose()` against the target's real `/bin/uclient-fetch` (skipped where it is absent). The request goes to a closed loopback port, so the point is only that uclient-fetch gets past getopt — a GNU-only flag in the command line fails the test. |
|
||||
| `tests/ucode/test_fw4_names.sh` | Keeps the fw4 chain/set inventory in `scripts/fw4_names.sh` (used by `init.d/homeproxy` to clean up on stop) in sync with the objects declared in `scripts/firewall_post.ut`. |
|
||||
| `tests/ucode/test_parse_uri.uc` | Per-scheme assertions on the `parse_uri()` result: anytls, http/https, hysteria, hysteria2/hy2, snell, socks(4/4a/5/5h), shadowsocks (SIP002 base64 + plain + plugin, Shadowrocket), trojan (ws/grpc), tuic, vless (reality/ws/http/httpupgrade), vmess (ws/h2/httpupgrade/grpc) and SIP008 objects, plus the rejection paths (unsupported kcp/quic, no QUIC support, invalid port, unknown scheme). |
|
||||
| `tests/ucode/test_generators.sh` | Runs `generate_client.uc`/`generate_server.uc` against the UCI fixtures in `tests/fixtures/generators/` inside a scratch directory (the `uci` cursor and `HP_DIR`/`RUN_DIR` are redirected) and validates each result with `sing-box check`. |
|
||||
|
||||
@@ -81,6 +81,68 @@ else
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo "== wget flavour selection =="
|
||||
# A stub /usr/bin/wget whose --version answer comes from the environment, so
|
||||
# both flavours can be exercised on a single host. The empty-answer/non-zero
|
||||
# case is uclient-fetch, whose getopt rejects --version just like it rejects
|
||||
# -nv before sending anything.
|
||||
rm -rf "$WORK/wget_flavor"
|
||||
mkdir -p "$WORK/wget_flavor"
|
||||
cat > "$WORK/wget_flavor/wget-stub" <<'STUB'
|
||||
#!/bin/sh
|
||||
if [ "$1" = "--version" ]; then
|
||||
printf '%s\n' "$HP_WGET_STUB_VERSION"
|
||||
exit "${HP_WGET_STUB_VERSION_RC:-0}"
|
||||
fi
|
||||
for arg in "$@"; do printf '%s\n' "$arg"; done >> "$HP_WGET_STUB_LOG"
|
||||
printf 'stub body\n'
|
||||
STUB
|
||||
chmod +x "$WORK/wget_flavor/wget-stub"
|
||||
sed "s|/usr/bin/wget|$WORK/wget_flavor/wget-stub|" \
|
||||
"$ROOT/root/etc/homeproxy/scripts/homeproxy.uc" > "$WORK/wget_flavor/homeproxy.uc"
|
||||
cp "$ROOT/tests/ucode/test_wget_flavor.uc" "$WORK/wget_flavor/"
|
||||
|
||||
for flavor in gnu compat; do
|
||||
if [ "$flavor" = "gnu" ]; then
|
||||
version="GNU Wget 1.25.0 built on linux-gnu."
|
||||
rc=0
|
||||
else
|
||||
version=""
|
||||
rc=1
|
||||
fi
|
||||
rm -f "$WORK/wget_flavor/args.log"
|
||||
if ( cd "$WORK/wget_flavor" &&
|
||||
HP_WGET_EXPECT_FLAVOR="$flavor" \
|
||||
HP_WGET_STUB_VERSION="$version" \
|
||||
HP_WGET_STUB_VERSION_RC="$rc" \
|
||||
HP_WGET_STUB_LOG="$WORK/wget_flavor/args.log" \
|
||||
ucode test_wget_flavor.uc ); then
|
||||
echo "PASS: wget flavour selection ($flavor)"
|
||||
else
|
||||
echo "FAIL: wget flavour selection ($flavor)"
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "== real uclient-fetch compatibility =="
|
||||
# uclient-fetch is the default /usr/bin/wget on OpenWrt/ImmortalWrt, so a
|
||||
# GNU-only option anywhere in the generated command line has to fail here.
|
||||
if [ -x /bin/uclient-fetch ]; then
|
||||
rm -rf "$WORK/uclient"
|
||||
mkdir -p "$WORK/uclient"
|
||||
sed "s|/usr/bin/wget|/bin/uclient-fetch|" \
|
||||
"$ROOT/root/etc/homeproxy/scripts/homeproxy.uc" > "$WORK/uclient/homeproxy.uc"
|
||||
cp "$ROOT/tests/ucode/test_wget_verbose_uclient.uc" "$WORK/uclient/"
|
||||
if ( cd "$WORK/uclient" && ucode test_wget_verbose_uclient.uc ); then
|
||||
echo "PASS: uclient-fetch accepts the generated command line"
|
||||
else
|
||||
echo "FAIL: uclient-fetch rejects the generated command line"
|
||||
FAILED=1
|
||||
fi
|
||||
else
|
||||
echo "SKIP: /bin/uclient-fetch not installed on this host"
|
||||
fi
|
||||
|
||||
echo "== generator regression tests =="
|
||||
sh "$ROOT/tests/ucode/test_generators.sh" "$ROOT" "$WORK/generators" || FAILED=1
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/ucode
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Copyright (C) 2026 ImmortalWrt.org
|
||||
*
|
||||
* Regression tests for the wget flavour probe and for the command line
|
||||
* wGETVerbose() builds from it.
|
||||
*
|
||||
* OpenWrt/ImmortalWrt's default /usr/bin/wget is uclient-fetch, which rejects
|
||||
* GNU-only flags such as -nv with "unrecognized option: n" *before* it opens a
|
||||
* connection, so a single GNU-ism turns every subscription update into
|
||||
* "no valid node found". run.sh stages a copy of homeproxy.uc whose
|
||||
* /usr/bin/wget points at a stub; HP_WGET_STUB_VERSION steers the answer of
|
||||
* the `--version` probe (empty output plus a non-zero exit mimics
|
||||
* uclient-fetch, whose getopt rejects the option and returns 1).
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { readfile } from 'fs';
|
||||
import { classifyWgetFlavor, wGETVerbose, wgetFlavor } from 'homeproxy';
|
||||
|
||||
let failures = 0, checks = 0;
|
||||
|
||||
function expect(name, actual, want) {
|
||||
checks++;
|
||||
if (sprintf('%J', actual) !== sprintf('%J', want)) {
|
||||
printf('FAIL %s: expected %J, got %J\n', name, want, actual);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
function has(list, value) {
|
||||
for (let v in list)
|
||||
if (v === value)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The value following the first occurrence of `key` */
|
||||
function after(list, key) {
|
||||
let prev = null;
|
||||
|
||||
for (let v in list) {
|
||||
if (prev === key)
|
||||
return v;
|
||||
|
||||
prev = v;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* Classification of the `wget --version` probe. */
|
||||
expect('gnu.version', classifyWgetFlavor(0, 'GNU Wget 1.25.0 built on linux-gnu.\n'), 'gnu');
|
||||
expect('gnu.multiline', classifyWgetFlavor(0, "GNU Wget 1.21.4 built on linux-gnu.\n\nCopyright (C) 2015 Free Software Foundation.\n"), 'gnu');
|
||||
expect('uclient.rejects-version', classifyWgetFlavor(1, null), 'compat');
|
||||
expect('busybox.version', classifyWgetFlavor(0, 'BusyBox v1.36.1 (2024-01-01) multi-call binary.\n'), 'compat');
|
||||
expect('empty.stdout', classifyWgetFlavor(0, ''), 'compat');
|
||||
expect('missing.binary', classifyWgetFlavor(127, null), 'compat');
|
||||
|
||||
/* wgetFlavor() probes the stub and caches the answer. */
|
||||
const want_flavor = getenv('HP_WGET_EXPECT_FLAVOR');
|
||||
expect('flavor', wgetFlavor(), want_flavor);
|
||||
expect('flavor.cached', wgetFlavor(), want_flavor);
|
||||
|
||||
const res = wGETVerbose('https://example.invalid/subs?token=abc', 'HomeProxy test UA');
|
||||
expect('content', res.content, 'stub body');
|
||||
expect('error', res.error, null);
|
||||
|
||||
const log = readfile(getenv('HP_WGET_STUB_LOG'));
|
||||
const args = log ? split(trim(log), '\n') : [];
|
||||
|
||||
expect('agent flag', has(args, '-U'), true);
|
||||
expect('agent value', after(args, '-U'), 'HomeProxy test UA');
|
||||
expect('timeout flag', after(args, '-T'), '10');
|
||||
expect('output flag', has(args, '-O-'), true);
|
||||
expect('url', has(args, 'https://example.invalid/subs?token=abc'), true);
|
||||
|
||||
/* -nv is GNU wget's --no-verbose; every other wget on the target rejects it,
|
||||
* and -q is the meter switch they all share. */
|
||||
expect('meter flag', has(args, want_flavor === 'gnu' ? '-nv' : '-q'), true);
|
||||
expect('no gnu-only flag', has(args, '-nv'), want_flavor === 'gnu');
|
||||
expect('no long options', has(args, '--user-agent') || has(args, '--timeout=10'), false);
|
||||
|
||||
printf('%d checks, %d failures\n', checks, failures);
|
||||
exit(failures === 0 ? 0 : 1);
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/ucode
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Copyright (C) 2026 ImmortalWrt.org
|
||||
*
|
||||
* Feeds the command line wGETVerbose() builds to the target's real
|
||||
* uclient-fetch - the default /usr/bin/wget on OpenWrt/ImmortalWrt. run.sh
|
||||
* rewrites the module's wget path to /bin/uclient-fetch and skips this file
|
||||
* where that binary does not exist.
|
||||
*
|
||||
* A closed loopback port is the entire request: what matters is that
|
||||
* uclient-fetch gets past getopt. Before the flavour probe existed it did not -
|
||||
* `-nv` made it print "unrecognized option: n" and exit before the request was
|
||||
* sent, so every subscription fetch came back empty. Any failure observed here
|
||||
* must therefore come from the network, never from the option parser.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { wGETVerbose, wgetFlavor } from 'homeproxy';
|
||||
|
||||
let failures = 0, checks = 0;
|
||||
|
||||
function expect(name, actual, want) {
|
||||
checks++;
|
||||
if (sprintf('%J', actual) !== sprintf('%J', want)) {
|
||||
printf('FAIL %s: expected %J, got %J\n', name, want, actual);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
/* uclient-fetch has no --version and rejects it, so it is classified compat. */
|
||||
expect('flavor', wgetFlavor(), 'compat');
|
||||
|
||||
const res = wGETVerbose('http://127.0.0.1:1/subs', 'HomeProxy test UA');
|
||||
expect('no content', res.content, null);
|
||||
expect('not an option error', match(res.error || '', /unrecognized option/) == null, true);
|
||||
expect('error carries the exit code', match(res.error || '', /^wget exited with status \d+: /) != null, true);
|
||||
|
||||
printf('%d checks, %d failures\n', checks, failures);
|
||||
exit(failures === 0 ? 0 : 1);
|
||||
+3
-3
@@ -49,12 +49,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand
|
||||
PKG_VERSION:=1.6.7_p17
|
||||
PKG_RELEASE:=45
|
||||
PKG_VERSION:=1.6.7_p21
|
||||
PKG_RELEASE:=46
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||
PKG_SOURCE_VERSION:=245058186b1ee22b8e6eb92ca9745d4ea811b116
|
||||
PKG_SOURCE_VERSION:=910e38b7e4c92e273b91aeead3bea7e2f7104a27
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
|
||||
Reference in new issue
Block a user