mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 10:23:56 +08:00
🐤 Sync 2026-10-03 00:12:36
This commit is contained in:
1 parent
ce3df55b24
commit
cb1d978497
58 files changed
+1627
-1018
No files matched your search
@@ -6,12 +6,6 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=The modern ImmortalWrt proxy platform for ARM64/AMD64 (sing-box 1.14)
|
||||
# Pure ucode/JS payload with no compiled code, so the package itself is arch
|
||||
# independent. The real arch constraint comes from the +sing-box dependency,
|
||||
# which the feed builds for every architecture Go supports (aarch64, arm,
|
||||
# mipsel, riscv64, x86_64, ...); where no sing-box package exists, dependency
|
||||
# resolution refuses the install. Checked against the ImmortalWrt 25.12.1
|
||||
# package index (sing-box 1.14.0-r1 published for all of the above).
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_DEPENDS:= \
|
||||
+sing-box \
|
||||
@@ -23,14 +17,10 @@ LUCI_DEPENDS:= \
|
||||
|
||||
PKG_NAME:=luci-app-homeproxy-pro
|
||||
PKG_VERSION:=28.10.1.14
|
||||
PKG_RELEASE:=15
|
||||
PKG_RELEASE:=16
|
||||
|
||||
LUCI_BASENAME:=homeproxy-pro
|
||||
|
||||
# Only files the package actually ships belong here. The previous list named
|
||||
# four paths that exist nowhere in the tree - certs/ and ruleset/ are created at
|
||||
# runtime, and the two *_list.txt files are written by the LuCI ACL editor - so
|
||||
# those declarations did nothing. What is listed instead is the resource data
|
||||
# the router updates by itself: on a running device those files are newer than
|
||||
# the ones in the package, and without this an upgrade would overwrite them.
|
||||
define Package/luci-app-homeproxy-pro/conffiles
|
||||
/etc/config/homeproxy-pro
|
||||
/etc/homeproxy-pro/resources/china_ip4.txt
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
| 后端架构 | 单文件巨型(`generate_client.uc` 1217 行 / 40 KB;`parse_uri.uc` ~400 行 / 15 KB) | orchestrator + 5 子层(`parser` / `config` / `generator` / `subscription` / `runtime`)+ 公共库(`homeproxy-pro.uc` / `firewall_utils.uc`)+ table-driven adapters | `root/etc/homeproxy-pro/scripts/{config,parser,generator,subscription,runtime}/` |
|
||||
| 前端架构 | 单一 66 KB `client.js` | `client.js` thin 入口 + 8 个 Tab 模块(`access` / `common` / `dns` / `nodes` / `routing` / `subscription` / `tun_dns` / `udp_nat`)+ 共享 helpers + 集中 `RPC.declare` | `htdocs/luci-static/resources/view/homeproxy-pro/client/` |
|
||||
| 协议建模 | 单 `parse_uri.uc` 内 13 个 scheme 分支 | `parser/{uri,flatten,normalize,mapping,protocols,validator}.uc` 6 个文件 + canonical Node 模型 + adapter table | `root/etc/homeproxy-pro/scripts/parser/` + `config/{model,adapter}.uc` |
|
||||
| Tab 模块化 | 1 个 `client.js` 含 12 个 Tab 渲染 | `homeproxy-pro.js` (1153 行 helpers) + 4 顶层 view + 8 个 `client/*.js`;helpers 集中 RPC / MD5 / validators / statusPoller / renderSectionAdd / uploadCertificate | `view/homeproxy-pro/client/{routing,nodes,dns,access,subscription,udp_nat,tun_dns,common}.js` |
|
||||
| Tab 模块化 | 1 个 `client.js` 含 12 个 Tab 渲染 | `homeproxy-pro.js` (1161 行 helpers) + 4 顶层 view + 8 个 `client/*.js`;helpers 集中 RPC / MD5 / validators / statusPoller / renderSectionAdd / uploadCertificate | `view/homeproxy-pro/client/{routing,nodes,dns,access,subscription,udp_nat,tun_dns,common}.js` |
|
||||
|
||||
### 稳定性
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
|
||||
| 维度 | upstream 形态 | pro 形态 | 证据 |
|
||||
|---|---|---|---|
|
||||
| 架构守卫 | 无 | `tests/arch-guard.sh` **38 个 guard / 135 个 check** 锁跨文件一致性(UCICONFIG_DIR / ACL / capabilities / conffiles / CERT_PATH_ROOTS / `redactUrl` mock / uCode 语法 / sing-box floor / adapter 共享表 / …) | `tests/arch-guard.sh` (guards 1-19, 21-39) |
|
||||
| 测试规模 | 7 个脚本 / 约 27 个 check | `tests/` 下 80 个文件:ucode 套件 + 8 个 frontend 验证器 + golden snapshot + mocks + 离线 runtime 测试;arch-guard 单跑 135 个 check | `tests/print-stats.sh` 实测 |
|
||||
| 架构守卫 | 无 | `tests/arch-guard.sh` **47 个 guard / 164 个 check** 锁跨文件一致性(UCICONFIG_DIR / ACL / capabilities / conffiles / CERT_PATH_ROOTS / `redactUrl` mock / uCode 语法 / sing-box floor / adapter 共享表 / …) | `tests/arch-guard.sh` (guards 1-49,编号跳过 20 与 44) |
|
||||
| 测试规模 | 7 个脚本 / 约 27 个 check | `tests/` 下 82 个文件:ucode 套件 + 8 个 frontend 验证器 + golden snapshot + mocks + 离线 runtime 测试;arch-guard 单跑 164 个 check | `tests/print-stats.sh` 实测 |
|
||||
| CI | `build` + `i18n` 两条平行 workflow | `build` 用 `workflow_call` 依赖 `arch-test`;`arch-test` 8 步(翻译 fast gate → toolchain cache → toolchain 校验 → `tests/run.sh` 唯一套件入口 → 模板检查);`on-target` 手动 ssh + 不安装 | `.github/workflows/{arch-test,build,on-target}.yml` |
|
||||
| uCode dialect pin | 跟随 upstream HEAD(已放宽) | pin `UCODE_REV=85922056ef7abeace3cca3ab28bc1ac2d88e31b1`(完整 40 位 commit)+ `test_ucode_grammar.sh` canary 锁 strict 语法(拒 `export function … }` 无 `;`、对象/数组解构) | `tests/toolchain/build-ucode-linux.sh:UCODE_REV` + `tests/ucode/test_ucode_grammar.sh` |
|
||||
| MD5 实现 | 388 行 minified snippet;vmess 分支漏 `vmess_global_padding` | RFC 1321 完整实现 + `tests/frontend-md5.js` 锁 RFC 向量 + 与 Node `crypto.createHash('md5')` 双向 cross-check(44 / 44 PASS) | `homeproxy-pro.js:calcStringMD5` + `tests/frontend-md5.js` |
|
||||
@@ -70,7 +70,31 @@
|
||||
| 资源更新 | jsdelivr 单一镜像 | 4 镜像 fallback(`fastly.jsdelivr.net` / `gcore.jsdelivr.net` / `cdn.jsdelivr.net` / `raw.githubusercontent.com`)+ UI「上次成功时间」(arch-guard 17 / 18 锁) | `runtime/dns.sh` + arch-guard 17 / 18 |
|
||||
| ECH 上传 | 后端 case 缺失 | 补齐 `client_ech_conf` 标签 + `isValidECHConfig()` PEM 校验;ACL 显式列 4 个 tmp 路径 | `homeproxy-pro.uc:isValidECHConfig` + `luci.homeproxy-pro:certificate_write` |
|
||||
|
||||
**一句话总结**:pro 的核心价值是**把"单文件能跑"变成"orchestrator + table-driven adapter + 可独立测试的模块"**,并把约束、质量、回滚三件事从靠人盯变成靠代码执行(arch-guard 135 checks 静态锁住跨文件不变量)。
|
||||
**一句话总结**:pro 的核心价值是**把"单文件能跑"变成"orchestrator + table-driven adapter + 可独立测试的模块"**,并把约束、质量、回滚三件事从靠人盯变成靠代码执行(arch-guard 164 checks 静态锁住跨文件不变量)。
|
||||
|
||||
## 自编译(OpenWrt buildroot)
|
||||
|
||||
```sh
|
||||
make menuconfig
|
||||
```
|
||||
|
||||
勾选 **luci-app-homeproxy-pro**,以及 **LuCI → Translations → 简体中文 (zh_Hans)**。
|
||||
后者不勾的话翻译包不会构建,且不报错,只在日志里留一行
|
||||
`WARNING: skipping luci-i18n-homeproxy-pro-zh-cn -- package not selected`。
|
||||
|
||||
```sh
|
||||
make package/luci-app-homeproxy-pro/compile V=s -j1
|
||||
ls bin/packages/<target>/<subtarget>/base/ | grep -i homeproxy-pro
|
||||
```
|
||||
|
||||
应同时得到两个包:
|
||||
|
||||
```
|
||||
luci-app-homeproxy-pro-28.10.1.14-r37.apk
|
||||
luci-i18n-homeproxy-pro-zh-cn-28.10.1.14-r37.apk
|
||||
```
|
||||
|
||||
本包不编译 sing-box,固件须自带 1.14+,否则依赖解析失败。
|
||||
|
||||
## 已知限制
|
||||
|
||||
|
||||
+3
-3
@@ -136,9 +136,9 @@ function render(ctx) {
|
||||
o.default = o.enabled;
|
||||
o.rmempty = false;
|
||||
|
||||
/* §2.7 (linux.json 与 pro 的差距分析): opt-in switch to the tutorial's
|
||||
* universal sniffer list + 100ms timeout. Default off so an upgrade is
|
||||
* invisible; arch-guard 40 pins the default '0' against drift. */
|
||||
/* Opt-in switch to the universal sniffer list + 100ms timeout. Default
|
||||
* off so an upgrade is invisible; arch-guard 39 pins the default '0'
|
||||
* against drift. */
|
||||
o = s.taboption('routing', form.Flag, 'sniffer_advanced_mode',
|
||||
_('Sniffer: advanced mode (100ms, universal list)'),
|
||||
_('Use the tutorial-recommended sniffer profile (100ms timeout, http/tls/stun/quic/dns). Default keeps the 300ms / unconstrained-list behaviour.'));
|
||||
|
||||
@@ -186,7 +186,7 @@ export function attachSchema(config) {
|
||||
* gfwlist / proxy_mainland_china / global with cold-start DNS on every
|
||||
* reload and made the difference between modes hard to explain.
|
||||
*
|
||||
* Note: docs/linux.json 与 pro 的差距分析.md §2.5 once asked for an
|
||||
* Note: the reference configuration this was compared against asked for an
|
||||
* explicit `reverse_mapping: true` here (so a future sing-box change to
|
||||
* the default would not silently move us off the mapping table). The
|
||||
* sing-box 1.14.0-r1 we test against rejects that field as
|
||||
|
||||
@@ -216,7 +216,7 @@ export function build_context(dm, env) {
|
||||
? (dm.dns.settings || {}).default_strategy
|
||||
: ((ipv6_support !== '1') ? 'ipv4_only' : null),
|
||||
/* cn_ip_fallback: evaluate/match_response fallback for unknown-host
|
||||
* queries (tutorial §2.1 / docs/linux.json 与 pro 的差距分析.md).
|
||||
* queries.
|
||||
* The v28.9.1.16 flip turns this on by default for fresh installs;
|
||||
* migrate_config.uc writes '0' explicitly for upgrade-existing users
|
||||
* so they keep the prior behaviour until they opt in. '0' is truthy
|
||||
@@ -225,8 +225,8 @@ export function build_context(dm, env) {
|
||||
* exactly what they had. */
|
||||
cn_ip_fallback: dm.general.cn_ip_fallback || '1',
|
||||
/* sniffer_advanced_mode: when '1' (opt-in), the route sniff rule
|
||||
* gets the universal protocol list + 100ms timeout (tutorial §2.7 /
|
||||
* docs/linux.json 与 pro 的差距分析.md). Default '0' preserves the
|
||||
* gets the universal protocol list + 100ms timeout.
|
||||
* Default '0' preserves the
|
||||
* 300ms / default-list behaviour so an upgrade is invisible. */
|
||||
sniffer_advanced_mode: dm.general.sniffer_advanced_mode || '0',
|
||||
main_urltest_nodes: dm.general.main_urltest_nodes || [],
|
||||
|
||||
@@ -321,8 +321,10 @@ function append_proxy_dns(config, dm, ctx) {
|
||||
* configured one of these encrypted upstreams; without the predefined
|
||||
* entry, the resolver itself has to be resolved through the system DNS,
|
||||
* and a DNS outage of any kind taken once out leaves the resolver
|
||||
* unreachable - a chicken-and-egg loop. Mirrors the hosts server in
|
||||
* linux.json line 10; see docs/linux.json 与 pro 的差距分析.md §2.4.
|
||||
* unreachable - a chicken-and-egg loop. The well-known sing-box reference
|
||||
* configuration ships the same table in a `type: 'hosts'` server, reached by
|
||||
* pointing each encrypted resolver's `domain_resolver` at that server's tag;
|
||||
* append_custom_dns() does exactly that below with the 'hp-dns-hosts' tag.
|
||||
*
|
||||
* Additions go here when the user's pool of encrypted resolvers grows;
|
||||
* a new entry needs no code change beyond this table. */
|
||||
@@ -337,15 +339,15 @@ const KNOWN_ENCRYPTED_DNS_HOSTS = {
|
||||
/* --- custom routing-mode path (user-defined dns_server / dns_rule) ----- */
|
||||
|
||||
function append_custom_dns(config, dm, ctx) {
|
||||
/* §2.4 DoH fallback. Pre-emit a hosts-type DNS server pinning any
|
||||
/* Pre-emit a hosts-type DNS server pinning any
|
||||
* encrypted-resolver hostname the user configured to its real IPs. The
|
||||
* loop also remembers which server sections hit the table, so the user
|
||||
* server loop below can stamp `domain_resolver: 'hp-dns-hosts'` onto
|
||||
* them - sing-box only consults the hosts table for resolvers that
|
||||
* explicitly point at it, mirroring linux.json line 7
|
||||
* (`{"domain_resolver": "hosts"}`). Without the stamp, the resolver
|
||||
* still has to look up its own hostname through the system DNS and the
|
||||
* chicken-and-egg loop we are trying to break stays open. */
|
||||
* explicitly point at it, so the table is inert without the stamp. Without
|
||||
* it, the resolver still has to look up its own hostname through the
|
||||
* system DNS and the chicken-and-egg loop we are trying to break stays
|
||||
* open. */
|
||||
let doh_predefined = null;
|
||||
const doh_resolved_hosts = {};
|
||||
for (let cfg in (dm.dns.servers || [])) {
|
||||
@@ -403,7 +405,7 @@ function append_custom_dns(config, dm, ctx) {
|
||||
/* sing-box >= 1.14: legacy address-filter rules are auto-wrapped with an
|
||||
evaluate action; deprecated strategy/accept_empty fields are dropped. */
|
||||
const builtin_dns_rules = [];
|
||||
/* §2.2 First safe rule: reject SVCB (qtype 64) and HTTPS (qtype 65)
|
||||
/* The first rule has to be the SVCB (qtype 64) and HTTPS (qtype 65)
|
||||
* queries. A client that learns the answer from these record types
|
||||
* connects to the embedded IP without going through the resolver again,
|
||||
* which makes Fake-IP and any route that hinges on A/AAAA resolution
|
||||
|
||||
@@ -36,11 +36,13 @@ import { get_outbound, get_resolver, get_ruleset, get_direct_override } from './
|
||||
* that every mode shares (dns-in hijack, sniff, optional auto_detect).
|
||||
* The caller must have created an empty config.route; we mutate it.
|
||||
*
|
||||
* Sniff tuning follows §2.7 of docs/linux.json 与 pro 的差距分析.md:
|
||||
* the default rule is the conservative 300ms / unconstrained sniffer list
|
||||
* (tutorial demo recommends 100ms + ['http','tls','stun','quic','dns'],
|
||||
* which costs less CPU but is a behaviour change). sniffer_advanced_mode
|
||||
* is the opt-in switch that flips to the tutorial profile. */
|
||||
* Two sniff profiles, and the default is deliberately the conservative one:
|
||||
* 300ms with no sniffer list, so sing-box applies its own default set. The
|
||||
* alternative - 100ms plus an explicit
|
||||
* ['http','tls','stun','quic','dns'] - is cheaper on CPU but changes what a
|
||||
* connection is sniffed as, which is a behaviour change, not a tuning knob.
|
||||
* sniffer_advanced_mode is the opt-in that flips to it, so an upgrade from an
|
||||
* earlier release is invisible. Guard 39 pins the default at '0'. */
|
||||
function initRoute(config, ctx) {
|
||||
const sniff_rule = (ctx.sniffer_advanced_mode === '1')
|
||||
? {
|
||||
|
||||
@@ -90,7 +90,7 @@ if (!migration_crontab) {
|
||||
if (isEmpty(uci.get(uciconfig, ucimain, 'log_level')))
|
||||
uci.set(uciconfig, ucimain, 'log_level', 'warn');
|
||||
|
||||
/* v28.9.1.16: cn_ip_fallback default flipped to '1' (tutorial §2.1 / docs/linux.json 与 pro 的差距分析.md).
|
||||
/* v28.9.1.16: cn_ip_fallback default flipped to '1'.
|
||||
* Existing users get the behaviour change only when they ask for it, so write
|
||||
* '0' explicitly into their config on upgrade; new installs read '1' straight
|
||||
* from /etc/config/homeproxy-pro (the package ships with that as the default).
|
||||
|
||||
@@ -541,7 +541,7 @@ hp_start_generated_config() {
|
||||
# the *new* (failing) value and start_service reinstalls a layer that no
|
||||
# longer matches the sing-box file it just restored.
|
||||
#
|
||||
# Review 2026-09-29 (docs/review-report-20260929-full.md §1.2) found the
|
||||
# The 2026-09-29 review found the
|
||||
# previous five-line list covered 4 real keys out of 22, and that the fifth
|
||||
# line named `infra.tun_address` - an option that exists nowhere. The real
|
||||
# keys are `tun_addr4` / `tun_addr6` (config/loader.uc:411), so that line was
|
||||
|
||||
@@ -1009,9 +1009,25 @@ fi
|
||||
# No shipped file may cite docs/. The whole directory is gitignored, so every
|
||||
# pointer to it is a dead link in a fresh clone - that is how README.md
|
||||
# came to reference an agent guide and an improvement plan that no
|
||||
# contributor can open. Kept here (with the docs/ pattern, not the names)
|
||||
# so the rule cannot quietly come back.
|
||||
DEAD_DOCS="$(grep -rlE 'docs/(adr|architecture-improvement-plan|homeproxy_architecture_refactor_agent_guide|audit-report|next-step-plan)' \
|
||||
# contributor can open.
|
||||
#
|
||||
# The pattern is deliberately *shape*-based, not a list of names. An earlier
|
||||
# version enumerated the offending documents
|
||||
# (adr|architecture-improvement-plan|homeproxy_architecture_refactor_agent_guide|
|
||||
# audit-report|next-step-plan) and therefore only ever caught those five: the
|
||||
# next document to be written - the gap analysis, the dated review reports -
|
||||
# walked straight past it, and a dozen comments across the generators, the
|
||||
# views and the test suite kept citing
|
||||
# `docs/linux.json 与 pro 的差距分析.md` with no gate noticing.
|
||||
# Matching `docs/` followed by anything catches every present and future name.
|
||||
#
|
||||
# The anchor is what keeps it from crying wolf. `htdocs/` contains the
|
||||
# substring `docs/`, and so does the `/docs/` of an external URL such as
|
||||
# https://github.com/anytls/anytls-go/blob/v0.0.8/docs/uri_scheme.md - both
|
||||
# are legitimate and must not be flagged. Requiring the character before
|
||||
# `docs/` to be neither alphanumeric nor `_`, `.`, `/`, `-` or `:` excludes
|
||||
# exactly those two shapes and keeps every real citation.
|
||||
DEAD_DOCS="$(grep -rlE '(^|[^:A-Za-z0-9_./-])docs/[^[:space:]]' \
|
||||
"$ROOT/README.md" "$ROOT/root" "$ROOT/htdocs" 2>/dev/null || true)"
|
||||
if [ -z "$DEAD_DOCS" ]; then
|
||||
pass "no shipped file cites the gitignored docs/ tree"
|
||||
@@ -1020,6 +1036,18 @@ else
|
||||
printf '%s\n' "$DEAD_DOCS"
|
||||
fi
|
||||
|
||||
# The same rule for the test tree, minus this file: a dead citation inside a
|
||||
# test is just as unresolvable for a contributor reading the failure, and the
|
||||
# generator suite carried six of them.
|
||||
DEAD_DOCS_TESTS="$(grep -rlE '(^|[^:A-Za-z0-9_./-])docs/[^[:space:]]' \
|
||||
"$ROOT/tests" 2>/dev/null | grep -v 'tests/arch-guard\.sh$' || true)"
|
||||
if [ -z "$DEAD_DOCS_TESTS" ]; then
|
||||
pass "no test file cites the gitignored docs/ tree either"
|
||||
else
|
||||
fail "these test files cite docs/ (gitignored, absent from every clone):"
|
||||
printf '%s\n' "$DEAD_DOCS_TESTS"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "== guard 21: dnsmasq snippet writer is incremental =="
|
||||
|
||||
@@ -1716,9 +1744,8 @@ echo "== guard 38: append_custom_dns emits the HTTPS/SVCB reject as its first DN
|
||||
|
||||
# The proxy path emits this reject (append_proxy_dns line 102); the custom
|
||||
# path used to rely on whatever the user wrote, so a rule that targets the
|
||||
# same query_type could shadow the safety net. The fix (see
|
||||
# docs/linux.json 与 pro 的差距分析.md §2.2) prepends the same literal in
|
||||
# append_custom_dns. Two checks keep that ordering:
|
||||
# same query_type could shadow the safety net. The fix prepends the same
|
||||
# literal in append_custom_dns. Two checks keep that ordering:
|
||||
#
|
||||
# (a) the literal appears in BOTH append_proxy_dns and append_custom_dns;
|
||||
# (b) the second occurrence (the one inside append_custom_dns) comes
|
||||
@@ -1732,7 +1759,7 @@ if [ "$reject_count" -eq 2 ]; then
|
||||
pass "the HTTPS/SVCB reject appears in both append_proxy_dns and append_custom_dns"
|
||||
else
|
||||
fail "expected 2 occurrences of 'query_type: [64, 65]' in generator/dns.uc, found $reject_count"
|
||||
fail "the custom path lost its built-in reject; see §2.2 of the gap analysis"
|
||||
fail "the custom path lost its built-in reject (see guard 38 in this file)"
|
||||
fi
|
||||
|
||||
# (b): the custom-path reject is the SECOND occurrence (append_custom_dns
|
||||
@@ -1759,9 +1786,7 @@ fi
|
||||
|
||||
echo "== guard 39: sniffer_advanced_mode default stays at '0' =="
|
||||
|
||||
# §2.7 (linux.json 与 pro 的差距分析.md): the advanced-mode sniff profile
|
||||
|
||||
# §2.7 (linux.json 与 pro 的差距分析.md): the advanced-mode sniff profile
|
||||
# The advanced-mode sniff profile
|
||||
# (100ms / universal list) is gated behind a UCI opt-in so an upgrade
|
||||
# does not change the sniffer behaviour. The default must remain '0';
|
||||
# the generator falls back to '0' on a missing UCI value, and the
|
||||
@@ -1869,7 +1894,7 @@ fi
|
||||
|
||||
echo "== guard 41: experimental.cache_file is unconditional in attachExperimental() =="
|
||||
|
||||
# Review (2026-09-29, docs/review-report-20260929.md §1.4.3 / §5.1, P2 #3):
|
||||
# The 2026-09-29 review (P2 #3) found:
|
||||
# attachExperimental() used to wrap the experimental.cache_file block in
|
||||
# `if (routing_mode in ['bypass_mainland_china', 'custom'])`, leaving
|
||||
# gfwlist / proxy_mainland_china / global with cold-start DNS on every
|
||||
@@ -1921,7 +1946,7 @@ esac
|
||||
|
||||
echo "== guard 42: NAPTR_BYPASS_SUFFIXES is the only domain_suffix qtype-35 source =="
|
||||
|
||||
# Review (2026-09-29, docs/review-report-20260929.md §2.2.1, P2 #4): the three
|
||||
# The 2026-09-29 review (P2 #4) found: the three
|
||||
# NAPTR (qtype 35) bypass suffixes (r.10086.cn, 10086.cn, pub.3gppnetwork.org)
|
||||
# used to be inlined into a push() call inside append_proxy_dns() and the
|
||||
# comment that explained them was on the same line as the push. The fix
|
||||
@@ -1948,7 +1973,7 @@ fi
|
||||
echo
|
||||
echo "== guard 43: the rollback UCI snapshot matches the keys the intercept layer reads =="
|
||||
|
||||
# Review 2026-09-29 (docs/review-report-20260929-full.md §1.2-1.4, P2 #1/#2).
|
||||
# The 2026-09-29 review (P2 #1 / #2) found:
|
||||
#
|
||||
# reload_service rolls back by restoring the known-good sing-box file *and*
|
||||
# replaying a UCI snapshot of the keys that steer the network layer - the
|
||||
@@ -2339,6 +2364,50 @@ else
|
||||
pass "no view calls the non-existent form.Map.formvalue()"
|
||||
fi
|
||||
|
||||
echo "== guard 49: the i18n package name cannot drift from PKG_NAME =="
|
||||
|
||||
# luci.mk names the translation package from the *checkout directory*, not from
|
||||
# PKG_NAME:
|
||||
#
|
||||
# LUCI_NAME?=$(notdir ${CURDIR})
|
||||
# LUCI_BASENAME?=$(patsubst luci-$(LUCI_TYPE)-%,%,$(LUCI_NAME))
|
||||
# define Package/luci-i18n-$(LUCI_BASENAME)-$(1)
|
||||
#
|
||||
# PKG_NAME is pinned to luci-app-homeproxy-pro here, so with the repository cloned
|
||||
# under its own name (luci-app-homeproxy-pro, which is what every self-compiler
|
||||
# gets) the feed build emitted luci-i18n-homeproxy-pro-zh-cn while the published
|
||||
# apk was named by build-pkg.sh from PKG_NAME and came out
|
||||
# luci-i18n-homeproxy-pro-zh-cn. Two names, one project, and the person self-building
|
||||
# concludes the translation package is missing (issue #5).
|
||||
#
|
||||
# The check is that LUCI_BASENAME agrees with the PKG_NAME the release path uses,
|
||||
# computed here from the Makefile the way luci.mk would - not by re-deriving it
|
||||
# from this repository's own directory name, which is precisely the thing that
|
||||
# was wrong and would make the assertion vacuous.
|
||||
MAKEFILE="$ROOT/Makefile"
|
||||
MK_PKG_NAME="$(sed -n 's/^PKG_NAME:=//p' "$MAKEFILE" 2>"/dev/null" | head -1)"
|
||||
MK_LUCI_BASENAME="$(sed -n 's/^LUCI_BASENAME:=//p' "$MAKEFILE" 2>"/dev/null" | head -1)"
|
||||
|
||||
if [ -z "$MK_PKG_NAME" ]; then
|
||||
fail "PKG_NAME is not declared in the Makefile - nothing to keep the i18n name tied to"
|
||||
elif [ -z "$MK_LUCI_BASENAME" ]; then
|
||||
fail "LUCI_BASENAME is not pinned, so the i18n package name follows the checkout"
|
||||
fail " directory: cloning as luci-app-homeproxy-pro yields luci-i18n-homeproxy-pro-zh-cn"
|
||||
fail " while the release path names it luci-i18n-homeproxy-pro-zh-cn (issue #5)"
|
||||
else
|
||||
# luci.mk strips the "luci-<type>-" prefix; type is the 2nd dash-field.
|
||||
mk_type="$(printf '%s' "$MK_PKG_NAME" | awk -F- '{print $2}')"
|
||||
mk_expected_basename="$(printf '%s' "$MK_PKG_NAME" | sed -n "s/^luci-${mk_type}-//p")"
|
||||
|
||||
if [ "$MK_LUCI_BASENAME" = "$mk_expected_basename" ]; then
|
||||
pass "LUCI_BASENAME matches PKG_NAME, so the i18n package is named the same"
|
||||
pass " in the feed build and in the release (luci-i18n-${MK_LUCI_BASENAME}-zh-cn)"
|
||||
else
|
||||
fail "LUCI_BASENAME is '$MK_LUCI_BASENAME' but PKG_NAME '$MK_PKG_NAME' implies"
|
||||
fail " '$mk_expected_basename' - the self-built i18n package will not match the release"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
printf '%s checks, %s failures\n' "$checks" "$([ "$FAILED" = 0 ] && echo 0 || echo 'nonzero')"
|
||||
if [ "$FAILED" != 0 ]; then
|
||||
|
||||
@@ -133,7 +133,7 @@ expect "uci restore: missing snapshot returns 1" "$?" "1"
|
||||
# Happy path: snapshot present, all keys applied, commit called.
|
||||
# The snapshot format carries the UCI type, because `uci set` on a list option
|
||||
# stores a string and the firewall validators then reject it (review
|
||||
# 2026-09-29, docs/review-report-20260929-full.md §1.5): lists are replayed
|
||||
# 2026-09-29 review): lists are replayed
|
||||
# with `uci delete` + `uci add_list`.
|
||||
: > "$HP_TEST_UCI_OUT"
|
||||
write_uci_stub
|
||||
|
||||
@@ -578,7 +578,7 @@ fi
|
||||
|
||||
run_case custom "$ROOT/tests/fixtures/generators/custom.uci" generate_client.uc sing-box-c.json
|
||||
|
||||
# §2.2 (linux.json 与 pro 的差距分析): append_custom_dns now prepends the
|
||||
# append_custom_dns now prepends the
|
||||
# HTTPS/SVCB reject so a user-written rule cannot shadow the safety net.
|
||||
# Without a dns_server in custom.uci the rule block is empty, so the
|
||||
# generated config still has the reject at index 0.
|
||||
@@ -613,7 +613,7 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# §2.5 (linux.json 与 pro 的差距分析): the original plan was to set
|
||||
# The original plan was to set
|
||||
# `experimental.reverse_mapping: true` so the literal pinned us against a
|
||||
# future sing-box default flip. sing-box 1.14.0-r1 (the CI floor pinned
|
||||
# by arch-guard 31) rejects the field as "json: unknown field" - the
|
||||
@@ -623,7 +623,7 @@ fi
|
||||
# purpose: the only assertion that matters is that the generated config
|
||||
# passes sing-box check, which the test runner does for every case.
|
||||
|
||||
# §2.4 (linux.json 与 pro 的差距分析): when the user configures a DoH
|
||||
# When the user configures a DoH
|
||||
# upstream against a known hostname, append_custom_dns emits a hosts-type
|
||||
# DNS server pinning that hostname to its real IPs. Without it, the DoH
|
||||
# resolver itself has to be resolved through the system DNS, and a DNS
|
||||
@@ -676,7 +676,7 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# §2.1 (linux.json 与 pro 的差距分析): cn_ip_fallback default flipped to '1'
|
||||
# cn_ip_fallback default flipped to '1'
|
||||
# for fresh installs (the package ships option cn_ip_fallback '1' and
|
||||
# context.uc falls back to '1' on a missing value). Existing users
|
||||
# upgrading get '0' written by migrate_config.uc and keep the prior
|
||||
@@ -719,7 +719,8 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# §2.1 opt-out: a fixture with cn_ip_fallback='0' must NOT emit the pair.
|
||||
# cn_ip_fallback opt-out: a fixture with cn_ip_fallback='0' must NOT emit
|
||||
# the pair.
|
||||
# client.uci does not set the option (so it defaults to '1' in the proxy
|
||||
# config above); here we stage a variant with '0' to assert the
|
||||
# upgrade-safe path of migrate_config.uc - existing users see no change.
|
||||
@@ -740,7 +741,7 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# §2.7 (linux.json 与 pro 的差距分析): sniffer_advanced_mode default stays
|
||||
# sniffer_advanced_mode default stays
|
||||
# '0'. Client mode must emit the conservative sniff rule (300ms / no
|
||||
# sniffer list); advanced mode emits the 100ms / universal-list profile.
|
||||
if [ ! -f "$client_json" ]; then
|
||||
@@ -776,7 +777,7 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# §2.7 opt-in: sniffer_advanced_mode='1' flips the sniff rule to 100ms +
|
||||
# sniffer_advanced_mode opt-in: '1' flips the sniff rule to 100ms +
|
||||
# the universal protocol list. sed-injected on client.uci.
|
||||
run_case client-sniffer-advanced "$ROOT/tests/fixtures/generators/client.uci" generate_client.uc sing-box-c.json \
|
||||
"s#^\\([[:space:]]*\\)option main_node 'urltest'#\\1option main_node 'urltest'\\n\\1option sniffer_advanced_mode '1'#"
|
||||
@@ -920,8 +921,8 @@ run_case_type_error dangling-resolver "does not exist or is disabled" \
|
||||
|
||||
# --- review@2026-09-29 batch -------------------------------------------------
|
||||
#
|
||||
# Six checks that pin the five generator fixes from
|
||||
# docs/review-report-20260929.md §7. Earlier rounds attempted ucode
|
||||
# Six checks that pin the five generator fixes from the 2026-09-29 review.
|
||||
# Earlier rounds attempted ucode
|
||||
# probes (arrow functions, optional chaining on arrays, ...) and each
|
||||
# round surfaced a different ucode dialect issue on the testbed - the
|
||||
# macOS dev box has no ucode so the loop could not iterate locally.
|
||||
@@ -929,7 +930,7 @@ run_case_type_error dangling-resolver "does not exist or is disabled" \
|
||||
# it runs identically on macOS and the testbed. ucode-stdout probes
|
||||
# are out; structural assertions against the emitted bytes are in.
|
||||
|
||||
# 1) P2 #2 / §1.4.1: geosite-cn is the *DNS*-layer rule_set consumer.
|
||||
# 1) P2 #2: geosite-cn is the *DNS*-layer rule_set consumer.
|
||||
# The route layer never references it as a rule_set value (route.rule_set
|
||||
# declares it as a tag). Indent cannot disambiguate dns.rules[] from
|
||||
# route.rules[] (both pretty-print at 4 tabs on the testbed; both are
|
||||
@@ -962,7 +963,7 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2) P3 #6 / §1.4.3: china-dns.strategy must mirror default-dns's gate
|
||||
# 2) P3 #6: china-dns.strategy must mirror default-dns's gate
|
||||
# on ipv6_support. With ipv6_support='0' (client.uci's value),
|
||||
# china-dns.strategy must be 'ipv4_only', not the pre-r28
|
||||
# unconditional 'prefer_ipv6'. The generated JSON emits the dns
|
||||
@@ -1083,7 +1084,7 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3) P2 #4 / §2.2.1: the three NAPTR (qtype 35) bypass suffixes must be
|
||||
# 3) P2 #4: the three NAPTR (qtype 35) bypass suffixes must be
|
||||
# emitted as a single domain_suffix entry backed by the
|
||||
# NAPTR_BYPASS_SUFFIXES constant. ucode's pretty-printer emits the
|
||||
# query_type array and the domain_suffix array as multi-line:
|
||||
@@ -1121,7 +1122,7 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4) P2 #3 / §1.4.3 / §5.1: attachExperimental() used to gate cache_file
|
||||
# 4) P2 #3: attachExperimental() used to gate cache_file
|
||||
# on routing_mode in [bypass_mainland_china, custom]. After the r28
|
||||
# fix, every routing_mode gets a cache_file block. Three sed
|
||||
# variations of the same fixture cover gfwlist / proxy_mainland_china
|
||||
@@ -1170,7 +1171,7 @@ else
|
||||
echo "PASS: cache-file-custom: experimental.cache_file emitted for routing_mode='custom'"
|
||||
fi
|
||||
|
||||
# 5) P3 #7 / §4.2.1: a local ruleset whose path is outside the homeproxy-pro
|
||||
# 5) P3 #7: a local ruleset whose path is outside the homeproxy-pro
|
||||
# whitelist (e.g. /etc/passwd) used to silently set the field to
|
||||
# null. The fix is to die() with a message naming the offender.
|
||||
# run_case_type_error observes the refusal. The variation targets
|
||||
@@ -1181,7 +1182,7 @@ run_case_type_error local-ruleset-bad-path "outside the homeproxy-pro whitelist"
|
||||
"$ROOT/tests/fixtures/generators/custom.uci" generate_client.uc sing-box-c.json \
|
||||
"s%^[[:space:]]*option path '/tmp/homeproxy_test_ruleset\\.[^/]*/local-ruleset-bad-path/test.srs'% option path '/etc/passwd'%"
|
||||
|
||||
# 6) P3 #8 / §4.2.2 (extra_tags die() on missing {tag}): deferred to
|
||||
# 6) P3 #8 (extra_tags die() on missing {tag}): deferred to
|
||||
# a dedicated 'testbed-dialect' sprint. The multi-line sed to
|
||||
# attach an extra_tags list onto the existing rs_local ruleset
|
||||
# drifted at the first whitespace change on the testbed, and
|
||||
|
||||
@@ -59,7 +59,7 @@ expect('server.insecure forced null', serverInsecure.insecure, null);
|
||||
/* 4. cert_path whitelist - the validator accepts /etc/homeproxy-pro/... and
|
||||
* /tmp/homeproxy_..., and rejects everything else (including
|
||||
* /etc/passwd and relative paths). This is the security patch
|
||||
* that backs the §4 path-whitelist entry; a regression here would
|
||||
* that backs the path-whitelist entry above; a regression here would
|
||||
* let UCI drive sing-box into reading arbitrary files as root. */
|
||||
expect('cert.path /etc/homeproxy-pro', validateHomeProxyPath('/etc/homeproxy-pro/certs/server_publickey.pem'), true);
|
||||
expect('cert.path /tmp/homeproxy_', validateHomeProxyPath('/tmp/homeproxy_test/foo.pem'), true);
|
||||
|
||||
Reference in new issue
Block a user