mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 04:45:13 +08:00
⛅ Sync 2026-09-29 21:45:00
This commit is contained in:
1 parent
a4d6e3e4b9
commit
da77daf88e
191 files changed
+8077
-6247
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
ucode "/etc/homeproxy/scripts/migrate_config.uc"
|
||||
|
||||
exit 0
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"admin/services/homeproxy": {
|
||||
"title": "HomeProxy",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-homeproxy" ],
|
||||
"uci": { "homeproxy": true }
|
||||
}
|
||||
},
|
||||
"admin/services/homeproxy/client": {
|
||||
"title": "Client Settings",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "homeproxy/client"
|
||||
}
|
||||
},
|
||||
"admin/services/homeproxy/node": {
|
||||
"title": "Node Settings",
|
||||
"order": 15,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "homeproxy/node"
|
||||
}
|
||||
},
|
||||
"admin/services/homeproxy/server": {
|
||||
"title": "Server Settings",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "homeproxy/server"
|
||||
}
|
||||
},
|
||||
"admin/services/homeproxy/status": {
|
||||
"title": "Service Status",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "homeproxy/status"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/usr/bin/ucode
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Driver for tests/ucode/test_migrate_config.sh.
|
||||
*
|
||||
* migrate_config.uc has no exports: the migration executes at import
|
||||
* time and writes through a UCI cursor. The staged copy of
|
||||
* migrate_config.uc has been rewritten by the runner so its cursor
|
||||
* reads ARGV[0] (the sandbox dir) instead of /etc/config - so this
|
||||
* driver just imports it and lets the side effect run.
|
||||
*
|
||||
* The assertions below then re-read the sandbox and check the
|
||||
* post-state step by step, so a failure names the migration step
|
||||
* that regressed rather than "migrate broke something".
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { cursor } from 'uci';
|
||||
|
||||
import 'migrate_config';
|
||||
|
||||
const uci = cursor(ARGV[0]);
|
||||
uci.load('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++;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- infra section ---------------------------------------------------- */
|
||||
|
||||
/* chinadns-ng removed: china_dns_port deleted. */
|
||||
expect('infra.china_dns_port deleted', uci.get('homeproxy', 'infra', 'china_dns_port'), null);
|
||||
|
||||
/* github_token moved from infra to config. */
|
||||
expect('config.github_token set', uci.get('homeproxy', 'config', 'github_token'), 'secret');
|
||||
expect('infra.github_token deleted', uci.get('homeproxy', 'infra', 'github_token'), null);
|
||||
|
||||
/* ntp_server introduced, defaults to the 'nil' sentinel. */
|
||||
expect('infra.ntp_server default', uci.get('homeproxy', 'infra', 'ntp_server'), 'nil');
|
||||
|
||||
/* tun_gso deprecated in sing-box 1.11: absent here, so nothing to do -
|
||||
* assert it stays absent rather than flapping to a default. */
|
||||
expect('infra.tun_gso stays absent', uci.get('homeproxy', 'infra', 'tun_gso'), null);
|
||||
|
||||
/* --- config section --------------------------------------------------- */
|
||||
|
||||
/* single-value chinadns server: legacy 'wan_114' maps to 114.114.114.114. */
|
||||
expect('config.china_dns_server→114', uci.get('homeproxy', 'config', 'china_dns_server'), '114.114.114.114');
|
||||
|
||||
/* log_level introduced, defaults to 'warn'. */
|
||||
expect('config.log_level default', uci.get('homeproxy', 'config', 'log_level'), 'warn');
|
||||
|
||||
/* routing_port='all' means "every port" = no option at all. */
|
||||
expect('config.routing_port deleted', uci.get('homeproxy', 'config', 'routing_port'), null);
|
||||
|
||||
/* --- dns section ------------------------------------------------------ */
|
||||
|
||||
/* default_server='block-dns' becomes 'default-dns' plus an appended
|
||||
* reject rule that reproduces the old "block everything" behaviour. */
|
||||
expect('dns.default_server→default-dns', uci.get('homeproxy', 'dns', 'default_server'), 'default-dns');
|
||||
expect('dns final-block action', uci.get('homeproxy', '_migration_dns_final_block', 'action'), 'reject');
|
||||
expect('dns final-block enabled', uci.get('homeproxy', '_migration_dns_final_block', 'enabled'), '1');
|
||||
|
||||
/* sing-box 1.14 deprecated DNS semantics. */
|
||||
expect('dns.independent_cache deleted', uci.get('homeproxy', 'dns', 'independent_cache'), null);
|
||||
expect('dns.store_rdrc→store_dns', uci.get('homeproxy', 'dns', 'cache_file_store_dns'), '1');
|
||||
expect('dns.store_rdrc deleted', uci.get('homeproxy', 'dns', 'cache_file_store_rdrc'), null);
|
||||
expect('dns.rdrc_timeout deleted', uci.get('homeproxy', 'dns', 'cache_file_rdrc_timeout'), null);
|
||||
|
||||
/* --- dns_server: legacy address form ---------------------------------- */
|
||||
|
||||
/* 'tls://1.1.1.1:853' splits into type/server/server_port, and the
|
||||
* legacy address key is dropped so the generator stops reading it. */
|
||||
expect('ds_default.type', uci.get('homeproxy', 'ds_default', 'type'), 'tls');
|
||||
expect('ds_default.server', uci.get('homeproxy', 'ds_default', 'server'), '1.1.1.1');
|
||||
expect('ds_default.server_port', uci.get('homeproxy', 'ds_default', 'server_port'), '853');
|
||||
expect('ds_default.address deleted', uci.get('homeproxy', 'ds_default', 'address'), null);
|
||||
|
||||
/* strategy / client_subnet moved out of the dns_server and into the
|
||||
* dns_rule that referenced it. */
|
||||
expect('ds_default.strategy deleted', uci.get('homeproxy', 'ds_default', 'strategy'), null);
|
||||
expect('ds_default.client_subnet deleted', uci.get('homeproxy', 'ds_default', 'client_subnet'), null);
|
||||
expect('dr_uses_default.strategy', uci.get('homeproxy', 'dr_uses_default', 'strategy'), 'prefer_ipv4');
|
||||
expect('dr_uses_default.client_subnet', uci.get('homeproxy', 'dr_uses_default', 'client_subnet'), '1.2.3.0/24');
|
||||
|
||||
/* RCode-as-address migrated into the predefined-rcode rule form; the
|
||||
* dns_server section itself is removed. */
|
||||
expect('ds_rcode section deleted', uci.get_all('homeproxy', 'ds_rcode'), null);
|
||||
|
||||
/* --- dns_rule --------------------------------------------------------- */
|
||||
|
||||
/* server='block-dns' moved into action='reject'. */
|
||||
expect('dr_block.action=reject', uci.get('homeproxy', 'dr_block', 'action'), 'reject');
|
||||
expect('dr_block.server deleted', uci.get('homeproxy', 'dr_block', 'server'), null);
|
||||
|
||||
/* rule_set_ipcidr_match_source renamed to rule_set_ip_cidr_match_source. */
|
||||
expect('dr_stale.old key deleted', uci.get('homeproxy', 'dr_stale', 'rule_set_ipcidr_match_source'), null);
|
||||
expect('dr_stale.new key set', uci.get('homeproxy', 'dr_stale', 'rule_set_ip_cidr_match_source'), '1');
|
||||
|
||||
/* a rule with no action gets the 'route' default. */
|
||||
expect('dr_uses_default.action=route', uci.get('homeproxy', 'dr_uses_default', 'action'), 'route');
|
||||
|
||||
/* --- routing_rule ----------------------------------------------------- */
|
||||
|
||||
/* outbound='block-out' moved into action='reject'. */
|
||||
expect('rr_block.action=reject', uci.get('homeproxy', 'rr_block', 'action'), 'reject');
|
||||
expect('rr_block.outbound deleted', uci.get('homeproxy', 'rr_block', 'outbound'), null);
|
||||
|
||||
/* --- server ----------------------------------------------------------- */
|
||||
|
||||
/* auto_firewall was a single global flag; it is redistributed onto
|
||||
* each server section as firewall='1'. */
|
||||
expect('srv_one.firewall set', uci.get('homeproxy', 'srv_one', 'firewall'), '1');
|
||||
expect('server.auto_firewall deleted', uci.get('homeproxy', 'server', 'auto_firewall'), null);
|
||||
|
||||
/* sniff_override deprecated in sing-box 1.11. */
|
||||
expect('srv_one.sniff_override deleted', uci.get('homeproxy', 'srv_one', 'sniff_override'), null);
|
||||
|
||||
/* server log_level introduced, defaults to 'warn'. */
|
||||
expect('server.log_level default', uci.get('homeproxy', 'server', 'log_level'), 'warn');
|
||||
|
||||
/* --- migration bookkeeping -------------------------------------------- */
|
||||
|
||||
/* the migration marker section must survive so the crontab cleanup
|
||||
* does not run twice. */
|
||||
expect('migration.crontab marker', uci.get('homeproxy', 'migration', 'crontab'), '1');
|
||||
|
||||
/* --- summary ---------------------------------------------------------- */
|
||||
|
||||
if (failures > 0)
|
||||
printf('FAIL: %d/%d checks failed\n', failures, checks);
|
||||
else
|
||||
printf('PASS: %d checks\n', checks);
|
||||
|
||||
exit(failures > 0 ? 1 : 0);
|
||||
@@ -1,3 +1,3 @@
|
||||
.DS_Store
|
||||
/homeproxy
|
||||
/homeproxy-pro
|
||||
/docs/
|
||||
File renamed without changes.
@@ -21,9 +21,9 @@ LUCI_DEPENDS:= \
|
||||
+kmod-tun \
|
||||
+ucode-mod-digest
|
||||
|
||||
PKG_NAME:=luci-app-homeproxy
|
||||
PKG_NAME:=luci-app-homeproxy-pro
|
||||
PKG_VERSION:=28.9.1.14
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=4
|
||||
|
||||
# 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
|
||||
@@ -31,16 +31,16 @@ PKG_RELEASE:=2
|
||||
# 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/conffiles
|
||||
/etc/config/homeproxy
|
||||
/etc/homeproxy/resources/china_ip4.txt
|
||||
/etc/homeproxy/resources/china_ip6.txt
|
||||
/etc/homeproxy/resources/china_list.txt
|
||||
/etc/homeproxy/resources/gfw_list.txt
|
||||
/etc/homeproxy/resources/china_ip4.ver
|
||||
/etc/homeproxy/resources/china_ip6.ver
|
||||
/etc/homeproxy/resources/china_list.ver
|
||||
/etc/homeproxy/resources/gfw_list.ver
|
||||
define Package/luci-app-homeproxy-pro/conffiles
|
||||
/etc/config/homeproxy-pro
|
||||
/etc/homeproxy-pro/resources/china_ip4.txt
|
||||
/etc/homeproxy-pro/resources/china_ip6.txt
|
||||
/etc/homeproxy-pro/resources/china_list.txt
|
||||
/etc/homeproxy-pro/resources/gfw_list.txt
|
||||
/etc/homeproxy-pro/resources/china_ip4.ver
|
||||
/etc/homeproxy-pro/resources/china_ip6.ver
|
||||
/etc/homeproxy-pro/resources/china_list.ver
|
||||
/etc/homeproxy-pro/resources/gfw_list.ver
|
||||
endef
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
## 项目定位
|
||||
|
||||
本项目是试验性产品,对 [homeproxy](https://github.com/szwjp/luci-app-homeproxy) 架构重构版:以 sing-box **1.14** 内核为唯一目标,
|
||||
本项目是试验性产品,对 [homeproxy-pro](https://github.com/szwjp/luci-app-homeproxy-pro) 架构重构版:以 sing-box **1.14** 内核为唯一目标,
|
||||
充分利用 1.14 引入的新特性,不再兼容 1.13 及更早内核。
|
||||
|
||||
## 运行要求
|
||||
@@ -23,16 +23,16 @@
|
||||
|
||||
| 维度 | upstream 形态 | pro 形态 | 证据 |
|
||||
|---|---|---|---|
|
||||
| 后端架构 | 单文件巨型(`generate_client.uc` 1217 行 / 40 KB;`parse_uri.uc` ~400 行 / 15 KB) | orchestrator + 5 子层(`parser` / `config` / `generator` / `subscription` / `runtime`)+ 公共库(`homeproxy.uc` / `firewall_utils.uc`)+ table-driven adapters | `root/etc/homeproxy/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/client/` |
|
||||
| 协议建模 | 单 `parse_uri.uc` 内 13 个 scheme 分支 | `parser/{uri,flatten,normalize,mapping,protocols,validator}.uc` 6 个文件 + canonical Node 模型 + adapter table | `root/etc/homeproxy/scripts/parser/` + `config/{model,adapter}.uc` |
|
||||
| Tab 模块化 | 1 个 `client.js` 含 12 个 Tab 渲染 | `homeproxy.js` (1153 行 helpers) + 4 顶层 view + 8 个 `client/*.js`;helpers 集中 RPC / MD5 / validators / statusPoller / renderSectionAdd / uploadCertificate | `view/homeproxy/client/{routing,nodes,dns,access,subscription,udp_nat,tun_dns,common}.js` |
|
||||
| 后端架构 | 单文件巨型(`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` |
|
||||
|
||||
### 稳定性
|
||||
|
||||
| 维度 | upstream 形态 | pro 形态 | 证据 |
|
||||
|---|---|---|---|
|
||||
| reload 事务 | 写完直接 restart | 生成 → `sing-box check` → 新实例 probe → 健康门(连续采样)→ 提为 known-good;不健康自动回滚 | `root/etc/init.d/homeproxy` + `runtime/{config,health}.sh` |
|
||||
| reload 事务 | 写完直接 restart | 生成 → `sing-box check` → 新实例 probe → 健康门(连续采样)→ 提为 known-good;不健康自动回滚 | `root/etc/init.d/homeproxy-pro` + `runtime/{config,health}.sh` |
|
||||
| 健康门 | `ubus` + `pgrep` 简单轮询 | `hp_wait_service` 连续 N 次采样(默认 3/30s)+ `netstat -p` 端口归属校验;不健康自动 rollback | `runtime/health.sh:hp_wait_service` |
|
||||
| UCI 1.14 迁移 | r10/r11 自述较弱 | `migrate_config.uc` 36 checks 覆盖 1.14 DNS renames / `dns_server.address` 拆分 / `rcode://` → predefined rule / `rule_set_ipcidr_match_source` rename / `block-out`/`block-dns` → `action='reject'` / `auto_firewall` 重分发 / `block-dns` default-server | `scripts/migrate_config.uc` + `tests/ucode/test_migrate_config.sh` |
|
||||
| sing-box 版本门 | 无 | `hp_require_singbox()` 在 start 时显式拒绝 `<1.14`,启动失败记录明确日志 | `runtime/service.sh:hp_require_singbox` |
|
||||
@@ -41,12 +41,12 @@
|
||||
|
||||
| 维度 | upstream 形态 | pro 形态 | 证据 |
|
||||
|---|---|---|---|
|
||||
| capabilities | `CAP_SYS_PTRACE` + `CAP_NET_RAW` + `CAP_NET_ADMIN` + `CAP_NET_BIND_SERVICE`(4 个) | 仅 `CAP_NET_ADMIN` + `CAP_NET_BIND_SERVICE`(2 个) | `root/etc/capabilities/homeproxy.json` + arch-guard guard 12 |
|
||||
| 路径白名单(cert / rule) | 仅前端 UI 校验 | `validateHomeProxyPath()` 拒 traversal + relative,限 `/etc/homeproxy/` 与 `/tmp/homeproxy_*`;`validateCertificatePath()` 限 `/etc/homeproxy/certs/` `/etc/acme/` `/etc/ssl/`;前后端列表 arch-guard 29 锁同步 | `homeproxy.uc:56 / :104` + `homeproxy.js:HP_CERT_PATH_ROOTS` |
|
||||
| 订阅 token 脱敏 | 调用点各自脱敏 | `wGETVerbose()` 内部脱敏(scheme/host/port 保留,userinfo / path / query 全脱;mock 与生产 `redactUrl` 由 arch-guard 35 锁一致) | `homeproxy.uc:226 (redactUrl)` + arch-guard 14 / 35 |
|
||||
| capabilities | `CAP_SYS_PTRACE` + `CAP_NET_RAW` + `CAP_NET_ADMIN` + `CAP_NET_BIND_SERVICE`(4 个) | 仅 `CAP_NET_ADMIN` + `CAP_NET_BIND_SERVICE`(2 个) | `root/etc/capabilities/homeproxy-pro.json` + arch-guard guard 12 |
|
||||
| 路径白名单(cert / rule) | 仅前端 UI 校验 | `validateHomeProxyPath()` 拒 traversal + relative,限 `/etc/homeproxy-pro/` 与 `/tmp/homeproxy_*`;`validateCertificatePath()` 限 `/etc/homeproxy-pro/certs/` `/etc/acme/` `/etc/ssl/`;前后端列表 arch-guard 29 锁同步 | `homeproxy-pro.uc:56 / :104` + `homeproxy-pro.js:HP_CERT_PATH_ROOTS` |
|
||||
| 订阅 token 脱敏 | 调用点各自脱敏 | `wGETVerbose()` 内部脱敏(scheme/host/port 保留,userinfo / path / query 全脱;mock 与生产 `redactUrl` 由 arch-guard 35 锁一致) | `homeproxy-pro.uc:226 (redactUrl)` + arch-guard 14 / 35 |
|
||||
| crontab 权限 | `sed -i`(GNU-only)+ mv 后留给 0644 | `sed + mv` 跨 busybox / GNU / BSD + `chmod 600` 修 root crontab 0644 → 0600 | `runtime/service.sh:hp_crontab_drop` |
|
||||
| procd jail mount | 默认未对所有模式 mount | tun/wireguard 自动 unjail(要 host netns),其他模式 mount HP_DIR + /etc/acme + /etc/ssl + /tmp/dhcp.leases + /etc/localtime + /etc/TZ | `runtime/service.sh:hp_procd_client_instance` |
|
||||
| cert upload 并发 | 4 个按钮共用 `/tmp/homeproxy_certificate.tmp` | 4 个按钮各自 `/tmp/homeproxy_cert_<name>.tmp`,ACL 显式列 4 个写路径 | `homeproxy.js:uploadCertificate` + `acl.d/luci-app-homeproxy.json` |
|
||||
| cert upload 并发 | 4 个按钮共用 `/tmp/homeproxy_certificate.tmp` | 4 个按钮各自 `/tmp/homeproxy_cert_<name>.tmp`,ACL 显式列 4 个写路径 | `homeproxy-pro.js:uploadCertificate` + `acl.d/luci-app-homeproxy-pro.json` |
|
||||
| firewall 字段白名单 | 部分值校验 | `firewall_pre.uc` `tun_name` 严格 `^[A-Za-z0-9_.-]{1,15}$` 防 nft 注入;`firewall_post.ut` 每个 UCI 派生值走 `*_to_nftarr()` 校验器(guard 11 锁模板全覆盖) | `firewall_pre.uc:32` + `firewall_utils.uc` |
|
||||
|
||||
### 测试 / CI
|
||||
@@ -57,20 +57,20 @@
|
||||
| 测试规模 | 7 个脚本 / 约 27 个 check | `tests/` 下 80 个文件:ucode 套件 + 8 个 frontend 验证器 + golden snapshot + mocks + 离线 runtime 测试;arch-guard 单跑 135 个 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.js:calcStringMD5` + `tests/frontend-md5.js` |
|
||||
| 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` |
|
||||
|
||||
### UX / Ops
|
||||
|
||||
| 维度 | upstream 形态 | pro 形态 | 证据 |
|
||||
|---|---|---|---|
|
||||
| Status 语义 | 2 态(RUNNING / NOT RUNNING) | 3 态(+ **STATUS UNKNOWN** 黄),区分 "rpc 没回" vs "service 死了" | `homeproxy.js:statusLabel` + `client.js:renderStatus` |
|
||||
| 前端特异性 bug 修复 | `stubValidator` 缺 `factory` 致 ip6addr TypeError;`L.bind(hp.renderSectionAdd, …)` 把 factory 塞 `extra_class` 槽致 `InvalidCharacterError` | 已修:`stubValidator` 持 `factory: validation`;`renderSectionAdd` 改薄 wrap(arch-guard 22 / 23 锁 binding 形态) | `view/homeproxy/client.js:73` + `homeproxy.js:1018-1026` |
|
||||
| Status 语义 | 2 态(RUNNING / NOT RUNNING) | 3 态(+ **STATUS UNKNOWN** 黄),区分 "rpc 没回" vs "service 死了" | `homeproxy-pro.js:statusLabel` + `client.js:renderStatus` |
|
||||
| 前端特异性 bug 修复 | `stubValidator` 缺 `factory` 致 ip6addr TypeError;`L.bind(hp.renderSectionAdd, …)` 把 factory 塞 `extra_class` 槽致 `InvalidCharacterError` | 已修:`stubValidator` 持 `factory: validation`;`renderSectionAdd` 改薄 wrap(arch-guard 22 / 23 锁 binding 形态) | `view/homeproxy-pro/client.js:73` + `homeproxy-pro.js:1018-1026` |
|
||||
| 协议增改成本 | 改 110 行 ternary(README 旧行) | 改 5 个表行:`model.uc CREDENTIALS` + `loader.uc PROTOCOL_OPTIONS` + `adapter.uc OPTION_FIELDS` + `fixtures/` + golden snapshot;arch-guard 5 强制 fixtures 同步 | `config/{model,loader,adapter}.uc` + `tests/fixtures/generators/` |
|
||||
| 资源更新 | 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.uc:isValidECHConfig` + `luci.homeproxy:certificate_write` |
|
||||
| ECH 上传 | 后端 case 缺失 | 补齐 `client_ech_conf` 标签 + `isValidECHConfig()` PEM 校验;ACL 显式列 4 个 tmp 路径 | `homeproxy-pro.uc:isValidECHConfig` + `luci.homeproxy-pro:certificate_write` |
|
||||
| 默认测试主机 | 硬编码作者内网 IP | `HP_TEST_HOST` 注入,未设时 fail-fast;`on-target.yml` ;ssh 前 `ssh -o ConnectTimeout=10` 先确认可达(不要从记忆里写地址) | `tests/run.sh` + `.github/workflows/on-target.yml` |
|
||||
| 文档 | 标准(README + CONTRIBUTING + SECURITY) | 精简(README only),`docs/` 自 2026-09-16 起只留本地(untrack + `.gitignore`),远端公开仓库不再发布 | `README.md` + `docs/`(本地) |
|
||||
| 路由 ops | — | 替换文件只动 JS / menu / acl / rpcd ucode(reload 不影响网络);更深层改动前 `cp /etc/config/homeproxy /tmp/hp-r<NN>-preflight-<DATE>/`,保留上一发版 .apk 24h;`killall -HUP rpcd` 由 apk scripts 自动做 | `runtime/{config,service}.sh` |
|
||||
| 路由 ops | — | 替换文件只动 JS / menu / acl / rpcd ucode(reload 不影响网络);更深层改动前 `cp /etc/config/homeproxy-pro /tmp/hp-r<NN>-preflight-<DATE>/`,保留上一发版 .apk 24h;`killall -HUP rpcd` 由 apk scripts 自动做 | `runtime/{config,service}.sh` |
|
||||
|
||||
**一句话总结**:pro 的核心价值是**把"单文件能跑"变成"orchestrator + table-driven adapter + 可独立测试的模块"**,并把约束、质量、回滚三件事从靠人盯变成靠代码执行(arch-guard 135 checks 静态锁住跨文件不变量)。
|
||||
|
||||
+13
-13
@@ -28,11 +28,11 @@ const HTTP_PING_HEALTH_CHECK_HINT = _('Specifies the timeout duration (in second
|
||||
const HTTP_PING_KEEPALIVE_HINT = _('The timeout (in seconds) that after performing a keepalive check, the client will wait for activity. If no activity is detected, the connection will be closed.');
|
||||
|
||||
/* Certificates the backend lets sing-box read. This list mirrors
|
||||
CERT_PATH_ROOTS in /etc/homeproxy/scripts/homeproxy.uc, and
|
||||
CERT_PATH_ROOTS in /etc/homeproxy-pro/scripts/homeproxy-pro.uc, and
|
||||
tests/arch-guard.sh guard 29 fails when the two drift apart: a path the UI
|
||||
accepted but the backend dropped made certificate_path null silently, so the
|
||||
TLS listener failed with nothing pointing at the path. */
|
||||
const HP_CERT_PATH_ROOTS = [ '/etc/homeproxy/certs/', '/etc/acme/', '/etc/ssl/' ];
|
||||
const HP_CERT_PATH_ROOTS = [ '/etc/homeproxy-pro/certs/', '/etc/acme/', '/etc/ssl/' ];
|
||||
|
||||
/* Methods whose failure has already been reported, so that a polled call
|
||||
cannot repeat the same notification every few seconds. */
|
||||
@@ -525,7 +525,7 @@ return baseclass.extend({
|
||||
o.depends('type', 'vless');
|
||||
o.depends('type', 'vmess');
|
||||
o.onchange = function(ev, section_id, value) {
|
||||
let desc = this.map.findElement('id', 'cbid.homeproxy.%s.transport'.format(section_id)).nextElementSibling;
|
||||
let desc = this.map.findElement('id', 'cbid.homeproxy-pro.%s.transport'.format(section_id)).nextElementSibling;
|
||||
if (value === 'http')
|
||||
desc.innerHTML = TRANSPORT_HTTP_HINT;
|
||||
else if (value === 'quic')
|
||||
@@ -533,20 +533,20 @@ return baseclass.extend({
|
||||
else
|
||||
desc.innerHTML = TRANSPORT_NONE_HINT;
|
||||
|
||||
let tls = this.map.findElement('id', 'cbid.homeproxy.%s.tls'.format(section_id)).firstElementChild;
|
||||
let tls = this.map.findElement('id', 'cbid.homeproxy-pro.%s.tls'.format(section_id)).firstElementChild;
|
||||
if ((value === 'http' && tls.checked) || (value === 'grpc' && !features.with_grpc)) {
|
||||
this.map.findElement('id', 'cbid.homeproxy.%s.http_idle_timeout'.format(section_id)).nextElementSibling.innerHTML =
|
||||
this.map.findElement('id', 'cbid.homeproxy-pro.%s.http_idle_timeout'.format(section_id)).nextElementSibling.innerHTML =
|
||||
(side === 'server') ? HTTP_IDLE_GOAWAY_HINT : HTTP_IDLE_HEALTH_CHECK_HINT;
|
||||
|
||||
if (side !== 'server')
|
||||
this.map.findElement('id', 'cbid.homeproxy.%s.http_ping_timeout'.format(section_id)).nextElementSibling.innerHTML =
|
||||
this.map.findElement('id', 'cbid.homeproxy-pro.%s.http_ping_timeout'.format(section_id)).nextElementSibling.innerHTML =
|
||||
HTTP_PING_HEALTH_CHECK_HINT;
|
||||
} else if (value === 'grpc' && features.with_grpc) {
|
||||
this.map.findElement('id', 'cbid.homeproxy.%s.http_idle_timeout'.format(section_id)).nextElementSibling.innerHTML =
|
||||
this.map.findElement('id', 'cbid.homeproxy-pro.%s.http_idle_timeout'.format(section_id)).nextElementSibling.innerHTML =
|
||||
HTTP_IDLE_KEEPALIVE_HINT;
|
||||
|
||||
if (side !== 'server')
|
||||
this.map.findElement('id', 'cbid.homeproxy.%s.http_ping_timeout'.format(section_id)).nextElementSibling.innerHTML =
|
||||
this.map.findElement('id', 'cbid.homeproxy-pro.%s.http_ping_timeout'.format(section_id)).nextElementSibling.innerHTML =
|
||||
HTTP_PING_KEEPALIVE_HINT;
|
||||
}
|
||||
}
|
||||
@@ -655,7 +655,7 @@ return baseclass.extend({
|
||||
o.validate = function(section_id, _value) {
|
||||
if (section_id) {
|
||||
let type = this.map.lookupOption('type', section_id)[0].formvalue(section_id);
|
||||
let tls = this.map.findElement('id', 'cbid.homeproxy.%s.tls'.format(section_id)).firstElementChild;
|
||||
let tls = this.map.findElement('id', 'cbid.homeproxy-pro.%s.tls'.format(section_id)).firstElementChild;
|
||||
|
||||
if ((options.tls_forced_types || []).includes(type)) {
|
||||
tls.checked = true;
|
||||
@@ -833,7 +833,7 @@ return baseclass.extend({
|
||||
* this only covers "the call did not come back". */
|
||||
rpcCall(method, args, options) {
|
||||
options = options || {};
|
||||
const object = options.object || 'luci.homeproxy';
|
||||
const object = options.object || 'luci.homeproxy-pro';
|
||||
|
||||
const call = rpc.declare({
|
||||
object: object,
|
||||
@@ -868,7 +868,7 @@ return baseclass.extend({
|
||||
* that had already drifted (its vmess branch lost vmess_global_padding)
|
||||
* and that validated nothing before writing the node into UCI. */
|
||||
parseShareLink(uri) {
|
||||
/* Backend (luci.homeproxy node_parse) returns one of
|
||||
/* Backend (luci.homeproxy-pro node_parse) returns one of
|
||||
* { config: {...the-node-fields}, error: '' } on success
|
||||
* { config: null, error: '...' } on failure
|
||||
* (rpcParse's `expect` would unpack-and-type-check, which the
|
||||
@@ -1061,7 +1061,7 @@ return baseclass.extend({
|
||||
* land in the certs/ directory. The filename argument is the
|
||||
* UCI option name (e.g. server_publickey), which we map onto
|
||||
* a stable per-button path. The ACL write list in
|
||||
* acl.d/luci-app-homeproxy.json enumerates all four paths.
|
||||
* acl.d/luci-app-homeproxy-pro.json enumerates all four paths.
|
||||
*
|
||||
* The signature is (type, filename, ev) on purpose: L.bind()
|
||||
* binds arguments *in front of* the event the Button hands in
|
||||
@@ -1103,7 +1103,7 @@ return baseclass.extend({
|
||||
/etc/ssl/../shadow, and sing-box reads these paths as root. */
|
||||
if (value.match(/(^|\/)\.\.(\/|$)/) ||
|
||||
!HP_CERT_PATH_ROOTS.some((root) => value.indexOf(root) === 0 && value.length > root.length))
|
||||
return _('Expecting: %s').format(_('/etc/homeproxy/certs/..., /etc/acme/..., /etc/ssl/...'));
|
||||
return _('Expecting: %s').format(_('/etc/homeproxy-pro/certs/..., /etc/acme/..., /etc/ssl/...'));
|
||||
|
||||
return true;
|
||||
},
|
||||
+15
-15
@@ -13,15 +13,15 @@
|
||||
'require validation';
|
||||
'require view';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require view.homeproxy.client.common as common';
|
||||
'require view.homeproxy.client.routing as routing';
|
||||
'require view.homeproxy.client.nodes as nodes';
|
||||
'require view.homeproxy.client.dns as dns';
|
||||
'require view.homeproxy.client.access as access';
|
||||
'require view.homeproxy.client.subscription as subscription';
|
||||
'require view.homeproxy.client.udp_nat as udp_nat';
|
||||
'require view.homeproxy.client.tun_dns as tun_dns';
|
||||
'require homeproxy-pro as hp';
|
||||
'require view.homeproxy-pro.client.common as common';
|
||||
'require view.homeproxy-pro.client.routing as routing';
|
||||
'require view.homeproxy-pro.client.nodes as nodes';
|
||||
'require view.homeproxy-pro.client.dns as dns';
|
||||
'require view.homeproxy-pro.client.access as access';
|
||||
'require view.homeproxy-pro.client.subscription as subscription';
|
||||
'require view.homeproxy-pro.client.udp_nat as udp_nat';
|
||||
'require view.homeproxy-pro.client.tun_dns as tun_dns';
|
||||
|
||||
/* Module-scoped guard: register the view-level status poll exactly
|
||||
* once, regardless of how many times the view's render() runs (LuCI
|
||||
@@ -39,13 +39,13 @@ function getServiceStatus() {
|
||||
* "the status query did not answer" from "the service is not running".
|
||||
* With the default `{}` the try/catch below swallowed the difference and
|
||||
* reported a failed query as NOT RUNNING. */
|
||||
return hp.rpcCall('list', ['homeproxy'],
|
||||
return hp.rpcCall('list', ['homeproxy-pro'],
|
||||
{ object: 'service', params: ['name'], expect: { '': {} }, fallback: null }).then((res) => {
|
||||
if (res === null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
return res['homeproxy']['instances']['sing-box-c']['running'] === true;
|
||||
return res['homeproxy-pro']['instances']['sing-box-c']['running'] === true;
|
||||
} catch (e) {
|
||||
/* It answered, and there is no such instance. */
|
||||
return false;
|
||||
@@ -93,7 +93,7 @@ let stubValidator = {
|
||||
return view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
uci.load('homeproxy'),
|
||||
uci.load('homeproxy-pro'),
|
||||
hp.getBuiltinFeatures(),
|
||||
network.getHostHints()
|
||||
]);
|
||||
@@ -107,7 +107,7 @@ return view.extend({
|
||||
|
||||
/* Cache all configured proxy nodes, they will be called multiple times */
|
||||
let proxy_nodes = {};
|
||||
uci.sections('homeproxy', 'node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'node', (res) => {
|
||||
let nodeaddr = ((res.type === 'direct') ? res.override_address : res.address) || '',
|
||||
nodeport = ((res.type === 'direct') ? res.override_port : res.port) || '';
|
||||
|
||||
@@ -116,7 +116,7 @@ return view.extend({
|
||||
String.format('[%s]', nodeaddr) : nodeaddr) + ':' + nodeport));
|
||||
});
|
||||
|
||||
m = new form.Map('homeproxy', _('HomeProxy'),
|
||||
m = new form.Map('homeproxy-pro', _('HomeProxy'),
|
||||
_('The modern ImmortalWrt proxy platform for ARM64/AMD64.'));
|
||||
|
||||
s = m.section(form.TypedSection);
|
||||
@@ -148,7 +148,7 @@ return view.extend({
|
||||
|
||||
ensureStatusPoll();
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy');
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy-pro');
|
||||
|
||||
/* ctx bundles the values each tab needs so the tab modules don't
|
||||
* have to recompute them. The destructuring keys are intentionally
|
||||
+14
-14
@@ -10,7 +10,7 @@
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require homeproxy-pro as hp';
|
||||
'require tools.firewall as fwtool';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
@@ -24,7 +24,7 @@ function render(ctx) {
|
||||
|
||||
s.tab('control', _('Access Control'));
|
||||
|
||||
o = s.taboption('control', form.SectionValue, '_control', form.NamedSection, 'control', 'homeproxy');
|
||||
o = s.taboption('control', form.SectionValue, '_control', form.NamedSection, 'control', 'homeproxy-pro');
|
||||
ss = o.subsection;
|
||||
|
||||
/* Interface control start */
|
||||
@@ -55,7 +55,7 @@ function render(ctx) {
|
||||
so.depends('lan_proxy_mode', 'except_listed');
|
||||
|
||||
so = fwtool.addIPOption(ss, 'lan_ip_policy', 'lan_direct_ipv6_ips', _('Direct IPv6 IP-s'), null, 'ipv6', hosts, true);
|
||||
so.depends({'lan_proxy_mode': 'except_listed', 'homeproxy.config.ipv6_support': '1'});
|
||||
so.depends({'lan_proxy_mode': 'except_listed', 'homeproxy-pro.config.ipv6_support': '1'});
|
||||
|
||||
so = fwtool.addMACOption(ss, 'lan_ip_policy', 'lan_direct_mac_addrs', _('Direct MAC-s'), null, hosts);
|
||||
so.depends('lan_proxy_mode', 'except_listed');
|
||||
@@ -64,7 +64,7 @@ function render(ctx) {
|
||||
so.depends('lan_proxy_mode', 'listed_only');
|
||||
|
||||
so = fwtool.addIPOption(ss, 'lan_ip_policy', 'lan_proxy_ipv6_ips', _('Proxy IPv6 IP-s'), null, 'ipv6', hosts, true);
|
||||
so.depends({'lan_proxy_mode': 'listed_only', 'homeproxy.config.ipv6_support': '1'});
|
||||
so.depends({'lan_proxy_mode': 'listed_only', 'homeproxy-pro.config.ipv6_support': '1'});
|
||||
|
||||
so = fwtool.addMACOption(ss, 'lan_ip_policy', 'lan_proxy_mac_addrs', _('Proxy MAC-s'), null, hosts);
|
||||
so.depends('lan_proxy_mode', 'listed_only');
|
||||
@@ -72,18 +72,18 @@ function render(ctx) {
|
||||
so = fwtool.addIPOption(ss, 'lan_ip_policy', 'lan_gaming_mode_ipv4_ips', _('Gaming mode IPv4 IP-s'), null, 'ipv4', hosts, true);
|
||||
|
||||
so = fwtool.addIPOption(ss, 'lan_ip_policy', 'lan_gaming_mode_ipv6_ips', _('Gaming mode IPv6 IP-s'), null, 'ipv6', hosts, true);
|
||||
so.depends('homeproxy.config.ipv6_support', '1');
|
||||
so.depends('homeproxy-pro.config.ipv6_support', '1');
|
||||
|
||||
so = fwtool.addMACOption(ss, 'lan_ip_policy', 'lan_gaming_mode_mac_addrs', _('Gaming mode MAC-s'), null, hosts);
|
||||
|
||||
so = fwtool.addIPOption(ss, 'lan_ip_policy', 'lan_global_proxy_ipv4_ips', _('Global proxy IPv4 IP-s'), null, 'ipv4', hosts, true);
|
||||
so.depends({'homeproxy.config.routing_mode': 'custom', '!reverse': true});
|
||||
so.depends({'homeproxy-pro.config.routing_mode': 'custom', '!reverse': true});
|
||||
|
||||
so = fwtool.addIPOption(ss, 'lan_ip_policy', 'lan_global_proxy_ipv6_ips', _('Global proxy IPv6 IP-s'), null, 'ipv6', hosts, true);
|
||||
so.depends({'homeproxy.config.routing_mode': /^((?!custom).)+$/, 'homeproxy.config.ipv6_support': '1'});
|
||||
so.depends({'homeproxy-pro.config.routing_mode': /^((?!custom).)+$/, 'homeproxy-pro.config.ipv6_support': '1'});
|
||||
|
||||
so = fwtool.addMACOption(ss, 'lan_ip_policy', 'lan_global_proxy_mac_addrs', _('Global proxy MAC-s'), null, hosts);
|
||||
so.depends({'homeproxy.config.routing_mode': 'custom', '!reverse': true});
|
||||
so.depends({'homeproxy-pro.config.routing_mode': 'custom', '!reverse': true});
|
||||
/* LAN IP policy end */
|
||||
|
||||
/* WAN IP policy start */
|
||||
@@ -94,14 +94,14 @@ function render(ctx) {
|
||||
|
||||
so = ss.taboption('wan_ip_policy', form.DynamicList, 'wan_proxy_ipv6_ips', _('Proxy IPv6 IP-s'));
|
||||
so.datatype = 'or(ip6addr, cidr6)';
|
||||
so.depends('homeproxy.config.ipv6_support', '1');
|
||||
so.depends('homeproxy-pro.config.ipv6_support', '1');
|
||||
|
||||
so = ss.taboption('wan_ip_policy', form.DynamicList, 'wan_direct_ipv4_ips', _('Direct IPv4 IP-s'));
|
||||
so.datatype = 'or(ip4addr, cidr4)';
|
||||
|
||||
so = ss.taboption('wan_ip_policy', form.DynamicList, 'wan_direct_ipv6_ips', _('Direct IPv6 IP-s'));
|
||||
so.datatype = 'or(ip6addr, cidr6)';
|
||||
so.depends('homeproxy.config.ipv6_support', '1');
|
||||
so.depends('homeproxy-pro.config.ipv6_support', '1');
|
||||
/* WAN IP policy end */
|
||||
|
||||
/* Proxy domain list start */
|
||||
@@ -112,7 +112,7 @@ function render(ctx) {
|
||||
so.monospace = true;
|
||||
so.datatype = 'hostname';
|
||||
so.description = _('One domain per line. No spaces, no comments, no wildcards; the backend rejects invalid characters and tells you which line.');
|
||||
so.depends({'homeproxy.config.routing_mode': 'custom', '!reverse': true});
|
||||
so.depends({'homeproxy-pro.config.routing_mode': 'custom', '!reverse': true});
|
||||
so.load = function(/* ... */) {
|
||||
return hp.rpcCall('acllist_read', ['proxy_list'],
|
||||
{ params: ['type'], expect: { '': {} } }).then((res) => {
|
||||
@@ -142,7 +142,7 @@ function render(ctx) {
|
||||
* so the test below was always true and a reset wiped the file even
|
||||
* in custom mode, where the list is in use. Read the saved UCI value
|
||||
* the backend will act on. */
|
||||
let routing_mode = uci.get('homeproxy', 'config', 'routing_mode');
|
||||
let routing_mode = uci.get('homeproxy-pro', 'config', 'routing_mode');
|
||||
if (routing_mode !== 'custom')
|
||||
return hp.rpcCall('acllist_write', ['proxy_list', ''], { params: ['type', 'content'], expect: { '': {} } }).then((ret) => {
|
||||
if (ret && ret.result === false)
|
||||
@@ -169,7 +169,7 @@ function render(ctx) {
|
||||
so.monospace = true;
|
||||
so.datatype = 'hostname';
|
||||
so.description = _('One domain per line. No spaces, no comments, no wildcards; the backend rejects invalid characters and tells you which line.');
|
||||
so.depends({'homeproxy.config.routing_mode': 'custom', '!reverse': true});
|
||||
so.depends({'homeproxy-pro.config.routing_mode': 'custom', '!reverse': true});
|
||||
so.load = function(/* ... */) {
|
||||
return hp.rpcCall('acllist_read', ['direct_list'],
|
||||
{ params: ['type'], expect: { '': {} } }).then((res) => {
|
||||
@@ -187,7 +187,7 @@ function render(ctx) {
|
||||
}
|
||||
so.remove = function(/* ... */) {
|
||||
/* Same `this.section` mistake as the proxy list above. */
|
||||
let routing_mode = uci.get('homeproxy', 'config', 'routing_mode');
|
||||
let routing_mode = uci.get('homeproxy-pro', 'config', 'routing_mode');
|
||||
if (routing_mode !== 'custom')
|
||||
return hp.rpcCall('acllist_write', ['direct_list', ''], { params: ['type', 'content'], expect: { '': {} } }).then((ret) => {
|
||||
if (ret && ret.result === false)
|
||||
+9
-9
@@ -10,7 +10,7 @@
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require homeproxy-pro as hp';
|
||||
|
||||
/* Build the shared body of a routing/dns rule section. The two sections
|
||||
* differ in their tab/option names, the sniffed-protocol value list, the
|
||||
@@ -32,8 +32,8 @@ function renderRuleSection(s, kind, self) {
|
||||
ss.sortable = true;
|
||||
ss.nodescriptions = true;
|
||||
ss.modaltitle = L.bind(hp.loadModalTitle, hp, is_dns ? _('DNS rule') : _('Routing rule'),
|
||||
is_dns ? _('Add a DNS rule') : _('Add a routing rule'), 'homeproxy');
|
||||
ss.sectiontitle = L.bind(hp.loadDefaultLabel, self, 'homeproxy');
|
||||
is_dns ? _('Add a DNS rule') : _('Add a routing rule'), 'homeproxy-pro');
|
||||
ss.sectiontitle = L.bind(hp.loadDefaultLabel, self, 'homeproxy-pro');
|
||||
/* Plain wrapper, not L.bind(): see hp.renderSectionAdd() for why the bound
|
||||
* form hands a call site's button factory to the wrong argument. */
|
||||
ss.renderSectionAdd = function(extra_class) {
|
||||
@@ -46,8 +46,8 @@ function renderRuleSection(s, kind, self) {
|
||||
ss.tab('fields_process', _('Process fields'));
|
||||
|
||||
so = ss.taboption('field_other', form.Value, 'label', _('Label'));
|
||||
so.load = L.bind(hp.loadDefaultLabel, self, 'homeproxy');
|
||||
so.validate = L.bind(hp.validateUniqueValue, self, 'homeproxy', uci_type, 'label');
|
||||
so.load = L.bind(hp.loadDefaultLabel, self, 'homeproxy-pro');
|
||||
so.validate = L.bind(hp.validateUniqueValue, self, 'homeproxy-pro', uci_type, 'label');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_other', form.Flag, 'enabled', _('Enable'));
|
||||
@@ -144,7 +144,7 @@ function renderRuleSection(s, kind, self) {
|
||||
delete this.keylist;
|
||||
delete this.vallist;
|
||||
|
||||
uci.sections('homeproxy', 'ruleset', (res) => {
|
||||
uci.sections('homeproxy-pro', 'ruleset', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
@@ -196,7 +196,7 @@ function renderRuleSection(s, kind, self) {
|
||||
|
||||
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
||||
this.value('system-dns', _('System DNS'));
|
||||
uci.sections('homeproxy', 'dns_server', (res) => {
|
||||
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
@@ -333,7 +333,7 @@ function renderRuleSection(s, kind, self) {
|
||||
delete this.vallist;
|
||||
|
||||
this.value('direct-out', _('Direct'));
|
||||
uci.sections('homeproxy', 'routing_node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
@@ -416,7 +416,7 @@ function renderRuleSection(s, kind, self) {
|
||||
this.value('', _('Default'));
|
||||
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
||||
this.value('system-dns', _('System DNS'));
|
||||
uci.sections('homeproxy', 'dns_server', (res) => {
|
||||
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
+5
-5
@@ -10,8 +10,8 @@
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require view.homeproxy.client.common as common';
|
||||
'require homeproxy-pro as hp';
|
||||
'require view.homeproxy-pro.client.common as common';
|
||||
|
||||
/* The four cache options exist on the 'dns' NamedSection in both the custom
|
||||
* mode tab (renderDnsSettings) and the preset mode tab (renderDnsCache).
|
||||
@@ -54,7 +54,7 @@ function renderDnsSettings(ctx) {
|
||||
let o, ss, so;
|
||||
|
||||
s.tab('dns', _('DNS Settings'));
|
||||
o = s.taboption('dns', form.SectionValue, '_dns', form.NamedSection, 'dns', 'homeproxy');
|
||||
o = s.taboption('dns', form.SectionValue, '_dns', form.NamedSection, 'dns', 'homeproxy-pro');
|
||||
o.depends('routing_mode', 'custom');
|
||||
|
||||
ss = o.subsection;
|
||||
@@ -70,7 +70,7 @@ function renderDnsSettings(ctx) {
|
||||
|
||||
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
||||
this.value('system-dns', _('System DNS'));
|
||||
uci.sections('homeproxy', 'dns_server', (res) => {
|
||||
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
@@ -198,7 +198,7 @@ function renderDnsCache(ctx) {
|
||||
o.depends('routing_mode', 'bypass_mainland_china');
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('dns_cache', form.SectionValue, '_dns_cache', form.NamedSection, 'dns', 'homeproxy');
|
||||
o = s.taboption('dns_cache', form.SectionValue, '_dns_cache', form.NamedSection, 'dns', 'homeproxy-pro');
|
||||
o.depends({'routing_mode': 'custom', '!reverse': true});
|
||||
ss = o.subsection;
|
||||
|
||||
+16
-16
@@ -10,7 +10,7 @@
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require homeproxy-pro as hp';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
/* Routing nodes sub-grid (the per-outbound entries that routing_rule
|
||||
@@ -29,8 +29,8 @@ function renderRoutingNodes(ctx) {
|
||||
ss.rowcolors = true;
|
||||
ss.sortable = true;
|
||||
ss.nodescriptions = true;
|
||||
ss.modaltitle = L.bind(hp.loadModalTitle, hp, _('Routing node'), _('Add a routing node'), 'homeproxy');
|
||||
ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
ss.modaltitle = L.bind(hp.loadModalTitle, hp, _('Routing node'), _('Add a routing node'), 'homeproxy-pro');
|
||||
ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
/* Plain wrapper, not L.bind(): see hp.renderSectionAdd() for why the bound
|
||||
* form hands a call site's button factory to the wrong argument. */
|
||||
ss.renderSectionAdd = function(extra_class) {
|
||||
@@ -38,8 +38,8 @@ function renderRoutingNodes(ctx) {
|
||||
};
|
||||
|
||||
so = ss.option(form.Value, 'label', _('Label'));
|
||||
so.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy', 'routing_node', 'label');
|
||||
so.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'routing_node', 'label');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.option(form.Flag, 'enabled', _('Enable'));
|
||||
@@ -52,7 +52,7 @@ function renderRoutingNodes(ctx) {
|
||||
so.value('urltest', _('URLTest'));
|
||||
for (let i in proxy_nodes)
|
||||
so.value(i, proxy_nodes[i]);
|
||||
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy', 'routing_node', 'node');
|
||||
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'routing_node', 'node');
|
||||
so.editable = true;
|
||||
|
||||
so = ss.option(form.ListValue, 'domain_resolver', _('Domain resolver'),
|
||||
@@ -64,7 +64,7 @@ function renderRoutingNodes(ctx) {
|
||||
this.value('', _('Default'));
|
||||
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
||||
this.value('system-dns', _('System DNS'));
|
||||
uci.sections('homeproxy', 'dns_server', (res) => {
|
||||
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
@@ -95,7 +95,7 @@ function renderRoutingNodes(ctx) {
|
||||
delete this.vallist;
|
||||
|
||||
this.value('', _('Direct'));
|
||||
uci.sections('homeproxy', 'routing_node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
||||
if (res['.name'] !== section_id && res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
@@ -107,7 +107,7 @@ function renderRoutingNodes(ctx) {
|
||||
let node = this.section.formvalue(section_id, 'node');
|
||||
|
||||
let conflict = false;
|
||||
uci.sections('homeproxy', 'routing_node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
||||
if (res['.name'] !== section_id) {
|
||||
if (res.outbound === section_id && res['.name'] == value)
|
||||
conflict = true;
|
||||
@@ -218,8 +218,8 @@ function renderDnsServers(ctx) {
|
||||
ss.rowcolors = true;
|
||||
ss.sortable = true;
|
||||
ss.nodescriptions = true;
|
||||
ss.modaltitle = L.bind(hp.loadModalTitle, hp, _('DNS server'), _('Add a DNS server'), 'homeproxy');
|
||||
ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
ss.modaltitle = L.bind(hp.loadModalTitle, hp, _('DNS server'), _('Add a DNS server'), 'homeproxy-pro');
|
||||
ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
/* Plain wrapper, not L.bind(): see hp.renderSectionAdd() for why the bound
|
||||
* form hands a call site's button factory to the wrong argument. */
|
||||
ss.renderSectionAdd = function(extra_class) {
|
||||
@@ -227,8 +227,8 @@ function renderDnsServers(ctx) {
|
||||
};
|
||||
|
||||
so = ss.option(form.Value, 'label', _('Label'));
|
||||
so.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy', 'dns_server', 'label');
|
||||
so.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'dns_server', 'label');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.option(form.Flag, 'enabled', _('Enable'));
|
||||
@@ -286,7 +286,7 @@ function renderDnsServers(ctx) {
|
||||
this.value('', _('None'));
|
||||
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
||||
this.value('system-dns', _('System DNS'));
|
||||
uci.sections('homeproxy', 'dns_server', (res) => {
|
||||
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
||||
if (res['.name'] !== section_id && res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
@@ -296,7 +296,7 @@ function renderDnsServers(ctx) {
|
||||
so.validate = function(section_id, value) {
|
||||
if (section_id && value) {
|
||||
let conflict = false;
|
||||
uci.sections('homeproxy', 'dns_server', (res) => {
|
||||
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
||||
if (res['.name'] !== section_id)
|
||||
if (res.address_resolver === section_id && res['.name'] == value)
|
||||
conflict = true;
|
||||
@@ -323,7 +323,7 @@ function renderDnsServers(ctx) {
|
||||
delete this.vallist;
|
||||
|
||||
this.value('direct-out', _('Direct'));
|
||||
uci.sections('homeproxy', 'routing_node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
+10
-10
@@ -10,8 +10,8 @@
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require view.homeproxy.client.common as common';
|
||||
'require homeproxy-pro as hp';
|
||||
'require view.homeproxy-pro.client.common as common';
|
||||
|
||||
/* Routing tab and the wrapping custom-routing NamedSection. The
|
||||
* routing_node tab lives in nodes.js; the routing_rule sub-section is
|
||||
@@ -147,7 +147,7 @@ function render(ctx) {
|
||||
|
||||
/* Custom routing settings start */
|
||||
/* Routing settings start */
|
||||
o = s.taboption('routing', form.SectionValue, '_routing', form.NamedSection, 'routing', 'homeproxy');
|
||||
o = s.taboption('routing', form.SectionValue, '_routing', form.NamedSection, 'routing', 'homeproxy-pro');
|
||||
o.depends('routing_mode', 'custom');
|
||||
|
||||
ss = o.subsection;
|
||||
@@ -159,8 +159,8 @@ function render(ctx) {
|
||||
}
|
||||
so.value('system', _('System'));
|
||||
so.default = 'system';
|
||||
so.depends('homeproxy.config.proxy_mode', 'redirect_tun');
|
||||
so.depends('homeproxy.config.proxy_mode', 'tun');
|
||||
so.depends('homeproxy-pro.config.proxy_mode', 'redirect_tun');
|
||||
so.depends('homeproxy-pro.config.proxy_mode', 'tun');
|
||||
so.rmempty = false;
|
||||
so.onchange = function(ev, section_id, value) {
|
||||
let desc = ev.target.nextElementSibling;
|
||||
@@ -183,9 +183,9 @@ function render(ctx) {
|
||||
_('In seconds.'));
|
||||
so.datatype = 'uinteger';
|
||||
so.placeholder = '300';
|
||||
so.depends('homeproxy.config.proxy_mode', 'redirect_tproxy');
|
||||
so.depends('homeproxy.config.proxy_mode', 'redirect_tun');
|
||||
so.depends('homeproxy.config.proxy_mode', 'tun');
|
||||
so.depends('homeproxy-pro.config.proxy_mode', 'redirect_tproxy');
|
||||
so.depends('homeproxy-pro.config.proxy_mode', 'redirect_tun');
|
||||
so.depends('homeproxy-pro.config.proxy_mode', 'tun');
|
||||
|
||||
so = ss.option(form.Flag, 'bypass_cn_traffic', _('Bypass CN traffic'),
|
||||
_('Bypass mainland China traffic via firewall rules by default.'));
|
||||
@@ -209,7 +209,7 @@ function render(ctx) {
|
||||
this.value('nil', _('Disable (the service)'));
|
||||
this.value('direct-out', _('Direct'));
|
||||
this.value('block-out', _('Block'));
|
||||
uci.sections('homeproxy', 'routing_node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
@@ -227,7 +227,7 @@ function render(ctx) {
|
||||
|
||||
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
||||
this.value('system-dns', _('System DNS'));
|
||||
uci.sections('homeproxy', 'dns_server', (res) => {
|
||||
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
+7
-7
@@ -10,7 +10,7 @@
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require homeproxy-pro as hp';
|
||||
|
||||
/* Rule set sub-grid (per-rule-set entries: local file or remote URL with
|
||||
* download metadata). Called after dns.renderDnsRules() so the
|
||||
@@ -28,8 +28,8 @@ function render(ctx) {
|
||||
ss.rowcolors = true;
|
||||
ss.sortable = true;
|
||||
ss.nodescriptions = true;
|
||||
ss.modaltitle = L.bind(hp.loadModalTitle, hp, _('Rule set'), _('Add a rule set'), 'homeproxy');
|
||||
ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
ss.modaltitle = L.bind(hp.loadModalTitle, hp, _('Rule set'), _('Add a rule set'), 'homeproxy-pro');
|
||||
ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
/* Plain wrapper, not L.bind(): see hp.renderSectionAdd() for why the bound
|
||||
* form hands a call site's button factory to the wrong argument. */
|
||||
ss.renderSectionAdd = function(extra_class) {
|
||||
@@ -37,8 +37,8 @@ function render(ctx) {
|
||||
};
|
||||
|
||||
so = ss.option(form.Value, 'label', _('Label'));
|
||||
so.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy', 'ruleset', 'label');
|
||||
so.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'ruleset', 'label');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.option(form.Flag, 'enabled', _('Enable'));
|
||||
@@ -60,7 +60,7 @@ function render(ctx) {
|
||||
|
||||
so = ss.option(form.Value, 'path', _('Path'));
|
||||
so.datatype = 'file';
|
||||
so.placeholder = '/etc/homeproxy/ruleset/example.json';
|
||||
so.placeholder = '/etc/homeproxy-pro/ruleset/example.json';
|
||||
so.rmempty = false;
|
||||
so.depends('type', 'local');
|
||||
so.modalonly = true;
|
||||
@@ -98,7 +98,7 @@ function render(ctx) {
|
||||
|
||||
this.value('', _('Default'));
|
||||
this.value('direct-out', _('Direct'));
|
||||
uci.sections('homeproxy', 'routing_node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
||||
if (res.enabled === '1')
|
||||
this.value(res['.name'], res.label);
|
||||
});
|
||||
File renamed without changes.
File renamed without changes.
+34
-34
@@ -10,7 +10,7 @@
|
||||
'require ui';
|
||||
'require view';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require homeproxy-pro as hp';
|
||||
|
||||
function allowInsecureConfirm(ev, _section_id, value) {
|
||||
if (value === '1' && !confirm(_('Are you sure to allow insecure?')))
|
||||
@@ -22,8 +22,8 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
s.rowcolors = true;
|
||||
s.sortable = true;
|
||||
s.nodescriptions = true;
|
||||
s.modaltitle = L.bind(hp.loadModalTitle, hp, _('Node'), _('Add a node'), 'homeproxy');
|
||||
s.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
s.modaltitle = L.bind(hp.loadModalTitle, hp, _('Node'), _('Add a node'), 'homeproxy-pro');
|
||||
s.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
|
||||
if (routing_mode !== 'custom') {
|
||||
o = s.option(form.Button, '_apply', _('Apply'));
|
||||
@@ -40,7 +40,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
}
|
||||
}
|
||||
o.onclick = function(ev, section_id) {
|
||||
uci.set('homeproxy', 'config', 'main_node', section_id);
|
||||
uci.set('homeproxy-pro', 'config', 'main_node', section_id);
|
||||
|
||||
return this.map.save(null, true).then(() => {
|
||||
ui.changes.apply(true);
|
||||
@@ -49,8 +49,8 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
}
|
||||
|
||||
o = s.option(form.Value, 'label', _('Label'));
|
||||
o.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
o.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy', 'node', 'label');
|
||||
o.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
o.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'node', 'label');
|
||||
o.modalonly = true;
|
||||
|
||||
o = hp.renderProtocolOptions(s, { features: features, side: 'client' });
|
||||
@@ -424,7 +424,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
|
||||
o = s.option(form.Value, 'tls_cert_path', _('Certificate path'),
|
||||
_('The path to the server certificate, in PEM format.'));
|
||||
o.value('/etc/homeproxy/certs/client_ca.pem');
|
||||
o.value('/etc/homeproxy-pro/certs/client_ca.pem');
|
||||
o.depends('tls_self_sign', '1');
|
||||
o.validate = hp.validateCertificatePath;
|
||||
o.rmempty = false;
|
||||
@@ -434,7 +434,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
_('<strong>Save your configuration before uploading files!</strong>'));
|
||||
o.inputstyle = 'action';
|
||||
o.inputtitle = _('Upload...');
|
||||
o.depends({'tls_self_sign': '1', 'tls_cert_path': '/etc/homeproxy/certs/client_ca.pem'});
|
||||
o.depends({'tls_self_sign': '1', 'tls_cert_path': '/etc/homeproxy-pro/certs/client_ca.pem'});
|
||||
o.onclick = L.bind(hp.uploadCertificate, hp, _('certificate'), 'client_ca');
|
||||
o.modalonly = true;
|
||||
|
||||
@@ -445,10 +445,10 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
|
||||
o = s.option(form.Value, 'tls_ech_config_path', _('ECH config path'),
|
||||
_('The path to the ECH config, in PEM format. If empty, load from DNS will be attempted.'));
|
||||
o.value('/etc/homeproxy/certs/client_ech_conf.pem');
|
||||
o.value('/etc/homeproxy-pro/certs/client_ech_conf.pem');
|
||||
/* Same policy as cert_path / key_path: sing-box reads this file as root and
|
||||
the backend drops any path outside CERT_PATH_ROOTS (validateCertificatePath
|
||||
in homeproxy.uc). Without it the UI accepted a path the generator then
|
||||
in homeproxy-pro.uc). Without it the UI accepted a path the generator then
|
||||
silently discarded. */
|
||||
o.validate = hp.validateCertificatePath;
|
||||
o.depends('tls_ech', '1');
|
||||
@@ -458,7 +458,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
_('<strong>Save your configuration before uploading files!</strong>'));
|
||||
o.inputstyle = 'action';
|
||||
o.inputtitle = _('Upload...');
|
||||
o.depends({'tls_ech': '1', 'tls_ech_config_path': '/etc/homeproxy/certs/client_ech_conf.pem'});
|
||||
o.depends({'tls_ech': '1', 'tls_ech_config_path': '/etc/homeproxy-pro/certs/client_ech_conf.pem'});
|
||||
o.onclick = L.bind(hp.uploadCertificate, hp, _('ECH config'), 'client_ech_conf');
|
||||
o.modalonly = true;
|
||||
|
||||
@@ -479,7 +479,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
o.depends({'tls': '1', 'type': /^((?!hysteria2?|tuic$).)+$/});
|
||||
o.validate = function(section_id, value) {
|
||||
if (section_id) {
|
||||
let tls_reality = this.map.findElement('id', 'cbid.homeproxy.%s.tls_reality'.format(section_id)).firstElementChild;
|
||||
let tls_reality = this.map.findElement('id', 'cbid.homeproxy-pro.%s.tls_reality'.format(section_id)).firstElementChild;
|
||||
if (tls_reality.checked && !value)
|
||||
return _('Expecting: %s').format(_('non-empty value'));
|
||||
|
||||
@@ -541,15 +541,15 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
||||
return view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
uci.load('homeproxy'),
|
||||
uci.load('homeproxy-pro'),
|
||||
hp.getBuiltinFeatures()
|
||||
]);
|
||||
},
|
||||
|
||||
render(data) {
|
||||
let m, s, o, ss, so;
|
||||
let main_node = uci.get('homeproxy', 'config', 'main_node');
|
||||
let routing_mode = uci.get('homeproxy', 'config', 'routing_mode');
|
||||
let main_node = uci.get('homeproxy-pro', 'config', 'main_node');
|
||||
let routing_mode = uci.get('homeproxy-pro', 'config', 'routing_mode');
|
||||
let features = data[1];
|
||||
|
||||
/* Cache subscription information, it will be called multiple times.
|
||||
@@ -569,7 +569,7 @@ return view.extend({
|
||||
* first-seen entry is the one both sides agree on. */
|
||||
let subinfo = [];
|
||||
const seen_hashes = {};
|
||||
for (let suburl of (uci.get('homeproxy', 'subscription', 'subscription_url') || [])) {
|
||||
for (let suburl of (uci.get('homeproxy-pro', 'subscription', 'subscription_url') || [])) {
|
||||
const info = hp.subscriptionInfo(suburl);
|
||||
if (!info)
|
||||
continue;
|
||||
@@ -579,9 +579,9 @@ return view.extend({
|
||||
subinfo.push(info);
|
||||
}
|
||||
|
||||
m = new form.Map('homeproxy', _('Edit nodes'));
|
||||
m = new form.Map('homeproxy-pro', _('Edit nodes'));
|
||||
|
||||
s = m.section(form.NamedSection, 'subscription', 'homeproxy');
|
||||
s = m.section(form.NamedSection, 'subscription', 'homeproxy-pro');
|
||||
|
||||
/* Node settings start */
|
||||
/* User nodes start */
|
||||
@@ -591,7 +591,7 @@ return view.extend({
|
||||
ss.addremove = true;
|
||||
ss.filter = function(section_id) {
|
||||
for (let info of subinfo)
|
||||
if (info.hash === uci.get('homeproxy', section_id, 'grouphash'))
|
||||
if (info.hash === uci.get('homeproxy-pro', section_id, 'grouphash'))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -620,8 +620,8 @@ return view.extend({
|
||||
input_links = input_links.reduce((pre, cur) =>
|
||||
(!pre.includes(cur) && pre.push(cur), pre), []);
|
||||
|
||||
let allow_insecure = uci.get('homeproxy', 'subscription', 'allow_insecure');
|
||||
let packet_encoding = uci.get('homeproxy', 'subscription', 'packet_encoding');
|
||||
let allow_insecure = uci.get('homeproxy-pro', 'subscription', 'allow_insecure');
|
||||
let packet_encoding = uci.get('homeproxy-pro', 'subscription', 'packet_encoding');
|
||||
|
||||
/* Every link is parsed - and rejected - by the
|
||||
* backend parser (parser/uri.uc), so importing a
|
||||
@@ -640,9 +640,9 @@ return view.extend({
|
||||
config.packet_encoding = packet_encoding;
|
||||
|
||||
let nameHash = hp.calcStringMD5(config.label);
|
||||
let sid = uci.add('homeproxy', 'node', nameHash);
|
||||
let sid = uci.add('homeproxy-pro', 'node', nameHash);
|
||||
Object.keys(config).forEach((k) => {
|
||||
uci.set('homeproxy', sid, k, config[k]);
|
||||
uci.set('homeproxy-pro', sid, k, config[k]);
|
||||
});
|
||||
imported_node++;
|
||||
});
|
||||
@@ -677,7 +677,7 @@ return view.extend({
|
||||
])
|
||||
])
|
||||
}
|
||||
/* The shared renderer (homeproxy.js) appends the extra button this
|
||||
/* The shared renderer (homeproxy-pro.js) appends the extra button this
|
||||
* page needs; it used to carry a verbatim copy of the whole method.
|
||||
* The factory gets the section back so the handler binds to it, the
|
||||
* same receiver the copy's `this` had.
|
||||
@@ -708,7 +708,7 @@ return view.extend({
|
||||
o = s.taboption('sub_' + info.hash, form.SectionValue, '_sub_' + info.hash, form.GridSection, 'node');
|
||||
ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode);
|
||||
ss.filter = function(section_id) {
|
||||
return (uci.get('homeproxy', section_id, 'grouphash') === info.hash);
|
||||
return (uci.get('homeproxy-pro', section_id, 'grouphash') === info.hash);
|
||||
}
|
||||
}
|
||||
/* Subscription nodes end */
|
||||
@@ -789,7 +789,7 @@ return view.extend({
|
||||
o = s.taboption('subscription', form.Button, '_update_subscriptions', _('Update nodes from subscriptions'));
|
||||
o.inputstyle = 'apply';
|
||||
o.inputtitle = function(section_id) {
|
||||
let sublist = uci.get('homeproxy', section_id, 'subscription_url') || [];
|
||||
let sublist = uci.get('homeproxy-pro', section_id, 'subscription_url') || [];
|
||||
if (sublist.length > 0) {
|
||||
return _('Update %s subscriptions').format(sublist.length);
|
||||
} else {
|
||||
@@ -799,7 +799,7 @@ return view.extend({
|
||||
}
|
||||
o.onclick = function() {
|
||||
/* The updater pipeline (wget through a CN -> overseas link,
|
||||
UCI commit, /etc/init.d/homeproxy reload, the 30 s sing-box
|
||||
UCI commit, /etc/init.d/homeproxy-pro reload, the 30 s sing-box
|
||||
health gate) takes 5-15 s on a real device, which exceeds
|
||||
the browser XHR timeout. The old synchronous RPC waited
|
||||
for the whole thing inside one XHR; the browser saw
|
||||
@@ -896,7 +896,7 @@ return view.extend({
|
||||
o.inputstyle = 'reset';
|
||||
o.inputtitle = function() {
|
||||
let subnodes = [];
|
||||
uci.sections('homeproxy', 'node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'node', (res) => {
|
||||
if (res.grouphash)
|
||||
subnodes = subnodes.concat(res['.name'])
|
||||
});
|
||||
@@ -910,19 +910,19 @@ return view.extend({
|
||||
}
|
||||
o.onclick = function() {
|
||||
let subnodes = [];
|
||||
uci.sections('homeproxy', 'node', (res) => {
|
||||
uci.sections('homeproxy-pro', 'node', (res) => {
|
||||
if (res.grouphash)
|
||||
subnodes = subnodes.concat(res['.name'])
|
||||
});
|
||||
|
||||
for (let i in subnodes)
|
||||
uci.remove('homeproxy', subnodes[i]);
|
||||
uci.remove('homeproxy-pro', subnodes[i]);
|
||||
|
||||
if (subnodes.includes(uci.get('homeproxy', 'config', 'main_node')))
|
||||
uci.set('homeproxy', 'config', 'main_node', 'nil');
|
||||
if (subnodes.includes(uci.get('homeproxy-pro', 'config', 'main_node')))
|
||||
uci.set('homeproxy-pro', 'config', 'main_node', 'nil');
|
||||
|
||||
if (subnodes.includes(uci.get('homeproxy', 'config', 'main_udp_node')))
|
||||
uci.set('homeproxy', 'config', 'main_udp_node', 'nil');
|
||||
if (subnodes.includes(uci.get('homeproxy-pro', 'config', 'main_udp_node')))
|
||||
uci.set('homeproxy-pro', 'config', 'main_udp_node', 'nil');
|
||||
|
||||
this.inputtitle = _('%s nodes removed').format(subnodes.length);
|
||||
this.readonly = true;
|
||||
+16
-16
@@ -11,7 +11,7 @@
|
||||
'require ui';
|
||||
'require view';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require homeproxy-pro as hp';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
/* Module-scoped guard for the view-level status poll: see the comment
|
||||
@@ -47,13 +47,13 @@ function getServiceStatus() {
|
||||
* "the status query did not answer" from "the service is not running".
|
||||
* With the default `{}` the try/catch below swallowed the difference and
|
||||
* reported a failed query as NOT RUNNING. */
|
||||
return hp.rpcCall('list', ['homeproxy'],
|
||||
return hp.rpcCall('list', ['homeproxy-pro'],
|
||||
{ object: 'service', params: ['name'], expect: { '': {} }, fallback: null }).then((res) => {
|
||||
if (res === null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
return res['homeproxy']['instances']['sing-box-s']['running'] === true;
|
||||
return res['homeproxy-pro']['instances']['sing-box-s']['running'] === true;
|
||||
} catch (e) {
|
||||
/* It answered, and there is no such instance. */
|
||||
return false;
|
||||
@@ -124,7 +124,7 @@ function handleGenKey(option) {
|
||||
return view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
uci.load('homeproxy'),
|
||||
uci.load('homeproxy-pro'),
|
||||
hp.getBuiltinFeatures()
|
||||
]);
|
||||
},
|
||||
@@ -133,7 +133,7 @@ return view.extend({
|
||||
let m, s, o;
|
||||
let features = data[1];
|
||||
|
||||
m = new form.Map('homeproxy', _('HomeProxy Server'),
|
||||
m = new form.Map('homeproxy-pro', _('HomeProxy Server'),
|
||||
_('The modern ImmortalWrt proxy platform for ARM64/AMD64.'));
|
||||
|
||||
s = m.section(form.TypedSection);
|
||||
@@ -163,7 +163,7 @@ return view.extend({
|
||||
|
||||
ensureStatusPoll();
|
||||
|
||||
s = m.section(form.NamedSection, 'server', 'homeproxy', _('Global settings'));
|
||||
s = m.section(form.NamedSection, 'server', 'homeproxy-pro', _('Global settings'));
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable'));
|
||||
o.rmempty = false;
|
||||
@@ -173,8 +173,8 @@ return view.extend({
|
||||
s.rowcolors = true;
|
||||
s.sortable = true;
|
||||
s.nodescriptions = true;
|
||||
s.modaltitle = L.bind(hp.loadModalTitle, hp, _('Server'), _('Add a server'), 'homeproxy');
|
||||
s.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
s.modaltitle = L.bind(hp.loadModalTitle, hp, _('Server'), _('Add a server'), 'homeproxy-pro');
|
||||
s.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
/* Plain wrapper, not L.bind(): see hp.renderSectionAdd() for why the
|
||||
* bound form hands a call site's button factory to the wrong argument. */
|
||||
s.renderSectionAdd = function(extra_class) {
|
||||
@@ -182,8 +182,8 @@ return view.extend({
|
||||
};
|
||||
|
||||
o = s.option(form.Value, 'label', _('Label'));
|
||||
o.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy');
|
||||
o.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy', 'server', 'label');
|
||||
o.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
||||
o.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'server', 'label');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
|
||||
@@ -206,7 +206,7 @@ return view.extend({
|
||||
o = s.option(form.Value, 'port', _('Listen port'),
|
||||
_('The port must be unique.'));
|
||||
o.datatype = 'port';
|
||||
o.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy', 'server', 'port');
|
||||
o.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'server', 'port');
|
||||
|
||||
o = s.option(form.Value, 'username', _('Username'));
|
||||
o.depends('type', 'http');
|
||||
@@ -284,7 +284,7 @@ return view.extend({
|
||||
o = s.option(form.ListValue, 'shadowsocks_encrypt_method', _('Encrypt method'));
|
||||
for (let i of hp.shadowsocks_encrypt_methods)
|
||||
o.value(i);
|
||||
/* The legacy stream ciphers are client-only; see homeproxy.js's
|
||||
/* The legacy stream ciphers are client-only; see homeproxy-pro.js's
|
||||
shadowsocks_stream_methods for why, and the plan's 2.11. */
|
||||
o.default = 'aes-128-gcm';
|
||||
o.depends('type', 'shadowsocks');
|
||||
@@ -510,7 +510,7 @@ return view.extend({
|
||||
|
||||
o = s.option(form.Value, 'tls_cert_path', _('Certificate path'),
|
||||
_('The server public key, in PEM format.'));
|
||||
o.value('/etc/homeproxy/certs/server_publickey.pem');
|
||||
o.value('/etc/homeproxy-pro/certs/server_publickey.pem');
|
||||
o.depends({'tls': '1', 'tls_acme': '0', 'tls_reality': null});
|
||||
o.depends({'tls': '1', 'tls_acme': '0', 'tls_reality': '0'});
|
||||
o.depends({'tls': '1', 'tls_acme': null, 'tls_reality': '0'});
|
||||
@@ -523,13 +523,13 @@ return view.extend({
|
||||
_('<strong>Save your configuration before uploading files!</strong>'));
|
||||
o.inputstyle = 'action';
|
||||
o.inputtitle = _('Upload...');
|
||||
o.depends({'tls': '1', 'tls_cert_path': '/etc/homeproxy/certs/server_publickey.pem'});
|
||||
o.depends({'tls': '1', 'tls_cert_path': '/etc/homeproxy-pro/certs/server_publickey.pem'});
|
||||
o.onclick = L.bind(hp.uploadCertificate, hp, _('certificate'), 'server_publickey');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'tls_key_path', _('Key path'),
|
||||
_('The server private key, in PEM format.'));
|
||||
o.value('/etc/homeproxy/certs/server_privatekey.pem');
|
||||
o.value('/etc/homeproxy-pro/certs/server_privatekey.pem');
|
||||
o.depends({'tls': '1', 'tls_acme': '0', 'tls_reality': '0'});
|
||||
o.depends({'tls': '1', 'tls_acme': '0', 'tls_reality': null});
|
||||
o.depends({'tls': '1', 'tls_acme': null, 'tls_reality': '0'});
|
||||
@@ -542,7 +542,7 @@ return view.extend({
|
||||
_('<strong>Save your configuration before uploading files!</strong>'));
|
||||
o.inputstyle = 'action';
|
||||
o.inputtitle = _('Upload...');
|
||||
o.depends({'tls': '1', 'tls_key_path': '/etc/homeproxy/certs/server_privatekey.pem'});
|
||||
o.depends({'tls': '1', 'tls_key_path': '/etc/homeproxy-pro/certs/server_privatekey.pem'});
|
||||
o.onclick = L.bind(hp.uploadCertificate, hp, _('private key'), 'server_privatekey');
|
||||
o.modalonly = true;
|
||||
|
||||
+10
-10
@@ -13,7 +13,7 @@
|
||||
'require ui';
|
||||
'require view';
|
||||
|
||||
'require homeproxy as hp';
|
||||
'require homeproxy-pro as hp';
|
||||
|
||||
/* Thanks to luci-app-aria2 */
|
||||
const css = ' \
|
||||
@@ -27,7 +27,7 @@ const css = ' \
|
||||
margin: 0; \
|
||||
}';
|
||||
|
||||
const hp_dir = '/var/run/homeproxy';
|
||||
const hp_dir = '/var/run/homeproxy-pro';
|
||||
|
||||
/* One view-level poll for all three log views.
|
||||
*
|
||||
@@ -161,7 +161,7 @@ function getResVersion(o, type) {
|
||||
function getRuntimeLog(o, name, filename, _option_index, section_id, _in_table) {
|
||||
let section, log_level_el;
|
||||
switch (filename) {
|
||||
case 'homeproxy':
|
||||
case 'homeproxy-pro':
|
||||
section = null;
|
||||
break;
|
||||
case 'sing-box-c':
|
||||
@@ -173,7 +173,7 @@ function getRuntimeLog(o, name, filename, _option_index, section_id, _in_table)
|
||||
}
|
||||
|
||||
if (section) {
|
||||
const selected = uci.get('homeproxy', section, 'log_level') || 'warn';
|
||||
const selected = uci.get('homeproxy-pro', section, 'log_level') || 'warn';
|
||||
const choices = {
|
||||
trace: _('Trace'),
|
||||
debug: _('Debug'),
|
||||
@@ -189,7 +189,7 @@ function getRuntimeLog(o, name, filename, _option_index, section_id, _in_table)
|
||||
'class': 'cbi-input-select',
|
||||
'style': 'margin-left: 4px; width: 6em;',
|
||||
'change': ui.createHandlerFn(this, (ev) => {
|
||||
uci.set('homeproxy', section, 'log_level', ev.target.value);
|
||||
uci.set('homeproxy-pro', section, 'log_level', ev.target.value);
|
||||
return o.map.save(null, true).then(() => {
|
||||
ui.changes.apply(true);
|
||||
});
|
||||
@@ -244,9 +244,9 @@ return view.extend({
|
||||
render() {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('homeproxy');
|
||||
m = new form.Map('homeproxy-pro');
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Connection check'));
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy-pro', _('Connection check'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_check_baidu', _('BaiDu'));
|
||||
@@ -255,7 +255,7 @@ return view.extend({
|
||||
o = s.option(form.DummyValue, '_check_google', _('Google'));
|
||||
o.cfgvalue = L.bind(getConnStat, this, o, 'google');
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management'));
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy-pro', _('Resources management'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_china_ip4_version', _('China IPv4 list version'));
|
||||
@@ -292,11 +292,11 @@ return view.extend({
|
||||
return node;
|
||||
}
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy');
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy-pro');
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_homeproxy_logview');
|
||||
o.render = L.bind(getRuntimeLog, this, o, _('HomeProxy'), 'homeproxy');
|
||||
o.render = L.bind(getRuntimeLog, this, o, _('HomeProxy'), 'homeproxy-pro');
|
||||
|
||||
o = s.option(form.DummyValue, '_sing-box-c_logview');
|
||||
o.render = L.bind(getRuntimeLog, this, o, _('sing-box client'), 'sing-box-c');
|
||||
File diff suppressed because it is too large.
Load diff
File renamed without changes.
+989
-989
File diff suppressed because it is too large.
Load diff
File renamed without changes.
+8
-8
@@ -1,5 +1,5 @@
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option __warning 'DO NOT EDIT THIS SECTION, OR YOU ARE ON YOUR OWN!'
|
||||
option common_port '22,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418'
|
||||
option mixed_port '5330'
|
||||
@@ -18,10 +18,10 @@ config homeproxy 'infra'
|
||||
option tproxy_mark '101'
|
||||
option tun_mark '102'
|
||||
|
||||
config homeproxy 'migration'
|
||||
config homeproxy-pro 'migration'
|
||||
option crontab '1'
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option main_node 'nil'
|
||||
option main_udp_node 'same'
|
||||
option dns_server '8.8.8.8'
|
||||
@@ -47,7 +47,7 @@ config homeproxy 'config'
|
||||
option udp_filtering ''
|
||||
option udp_nat_max ''
|
||||
|
||||
config homeproxy 'control'
|
||||
config homeproxy-pro 'control'
|
||||
option lan_proxy_mode 'disabled'
|
||||
list wan_proxy_ipv4_ips '91.105.192.0/23'
|
||||
list wan_proxy_ipv4_ips '91.108.4.0/22'
|
||||
@@ -65,12 +65,12 @@ config homeproxy 'control'
|
||||
list wan_proxy_ipv6_ips '2001:b28:f23f::/48'
|
||||
list wan_proxy_ipv6_ips '2a0a:f280::/32'
|
||||
|
||||
config homeproxy 'routing'
|
||||
config homeproxy-pro 'routing'
|
||||
option default_outbound 'direct-out'
|
||||
option default_outbound_dns 'default-dns'
|
||||
option find_neighbor '0'
|
||||
|
||||
config homeproxy 'dns'
|
||||
config homeproxy-pro 'dns'
|
||||
option default_strategy 'prefer_ipv4'
|
||||
option default_server 'default-dns'
|
||||
option disable_cache '0'
|
||||
@@ -80,7 +80,7 @@ config homeproxy 'dns'
|
||||
option dns_timeout ''
|
||||
option cache_file_store_dns '0'
|
||||
|
||||
config homeproxy 'subscription'
|
||||
config homeproxy-pro 'subscription'
|
||||
option auto_update '0'
|
||||
option allow_insecure '0'
|
||||
option packet_encoding 'xudp'
|
||||
@@ -89,6 +89,6 @@ config homeproxy 'subscription'
|
||||
list filter_keywords '重置|到期|过期|剩余|套餐'
|
||||
list filter_keywords 'Expiration|Remaining'
|
||||
|
||||
config homeproxy 'server'
|
||||
config homeproxy-pro 'server'
|
||||
option enabled '0'
|
||||
option log_level 'warn'
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2022-2023 ImmortalWrt.org
|
||||
|
||||
NAME="homeproxy"
|
||||
NAME="homeproxy-pro"
|
||||
|
||||
log_max_size="50" #KB
|
||||
main_log_file="/var/run/$NAME/$NAME.log"
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
import {
|
||||
isEmpty, strToBool, strToInt, strToTime, removeBlankAttrs,
|
||||
buildTLSObject, buildTransportObject, parse_port
|
||||
} from '../homeproxy.uc';
|
||||
} from '../homeproxy-pro.uc';
|
||||
|
||||
import { Node, Inbound, INBOUND_TLS_SERVER } from './model.uc';
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ import {
|
||||
|
||||
import { PROTOCOL_TO_UCI as PROTOCOL_OPTIONS } from '../parser/mapping.uc';
|
||||
|
||||
const UCICONFIG = 'homeproxy';
|
||||
const UCICONFIG = 'homeproxy-pro';
|
||||
|
||||
const SECTION = {
|
||||
main: 'config',
|
||||
@@ -63,7 +63,7 @@ function node_opt(uci, section_id, name) {
|
||||
/* --- sub-object extraction --------------------------------------------- */
|
||||
|
||||
/* These three functions are the whole reason a Node is hierarchical: the
|
||||
* shared TLS/transport/multiplex builders (homeproxy.uc) take a flat,
|
||||
* shared TLS/transport/multiplex builders (homeproxy-pro.uc) take a flat,
|
||||
* prefixed dict today, so every caller has to hand them the right subset. A
|
||||
* sub-object makes the boundary explicit and testable.
|
||||
*
|
||||
@@ -369,7 +369,7 @@ export const Loader = {
|
||||
|
||||
uci.load(UCICONFIG);
|
||||
|
||||
const config = Config.create('homeproxy');
|
||||
const config = Config.create('homeproxy-pro');
|
||||
|
||||
config.general = {
|
||||
routing_mode: opt(uci, 'main', 'routing_mode') || 'bypass_mainland_china',
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty } from '../homeproxy.uc';
|
||||
import { isEmpty } from '../homeproxy-pro.uc';
|
||||
|
||||
/* Canonical credential multiplexing for a Node. The current generator picks
|
||||
* these apart with ternaries per field (`username` vs `user` vs `password` vs
|
||||
+5
-5
@@ -7,7 +7,7 @@
|
||||
* Copyright (C) 2025 ImmortalWrt.org
|
||||
*
|
||||
* Declares every homeproxy_* fw4 chain/set. The inventory of those objects is
|
||||
* tracked in scripts/fw4_names.sh, which init.d/homeproxy uses to clean them up
|
||||
* tracked in scripts/fw4_names.sh, which init.d/homeproxy-pro uses to clean them up
|
||||
* on stop; add/remove entries there together with any change below.
|
||||
* tests/ucode/test_fw4_names.sh compares both lists, and
|
||||
* tests/ucode/test_firewall_template.sh renders this template and checks the
|
||||
@@ -26,7 +26,7 @@
|
||||
*/
|
||||
import { readfile } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { isEmpty, isValidCIDR } from '/etc/homeproxy/scripts/homeproxy.uc';
|
||||
import { isEmpty, isValidCIDR } from '/etc/homeproxy-pro/scripts/homeproxy-pro.uc';
|
||||
import {
|
||||
ipv4_to_nftarr,
|
||||
mac_to_nftarr,
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
ports_to_nftarr,
|
||||
isValidPort,
|
||||
isValidIface
|
||||
} from '/etc/homeproxy/scripts/firewall_utils.uc';
|
||||
} from '/etc/homeproxy-pro/scripts/firewall_utils.uc';
|
||||
|
||||
const fw4 = require('fw4');
|
||||
|
||||
@@ -139,10 +139,10 @@ function dnsmasq_nftset() {
|
||||
}
|
||||
|
||||
/* Misc config */
|
||||
const resources_dir = '/etc/homeproxy/resources';
|
||||
const resources_dir = '/etc/homeproxy-pro/resources';
|
||||
|
||||
/* UCI config start */
|
||||
const cfgname = 'homeproxy';
|
||||
const cfgname = 'homeproxy-pro';
|
||||
const uci = cursor();
|
||||
uci.load(cfgname);
|
||||
|
||||
+2
-2
@@ -8,9 +8,9 @@
|
||||
|
||||
import { writefile } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { isEmpty, RUN_DIR, validation } from './homeproxy.uc';
|
||||
import { isEmpty, RUN_DIR, validation } from './homeproxy-pro.uc';
|
||||
|
||||
const cfgname = 'homeproxy';
|
||||
const cfgname = 'homeproxy-pro';
|
||||
const uci = cursor();
|
||||
uci.load(cfgname);
|
||||
|
||||
+4
-4
@@ -8,8 +8,8 @@
|
||||
* expression with `}`, and the next thing nft sees is whatever the attacker
|
||||
* put after it - up to and including `accept` for a chain that should be a
|
||||
* drop, or a `reject` outside any chain that the fw4 reload fails to
|
||||
* compile, taking the whole router firewall with it. The homeproxy section
|
||||
* in /etc/config/homeproxy is UCI-writable from the subscription updater,
|
||||
* compile, taking the whole router firewall with it. The homeproxy-pro section
|
||||
* in /etc/config/homeproxy-pro is UCI-writable from the subscription updater,
|
||||
* from any shell on the LAN and from any cron job the user has not locked
|
||||
* down; `tests/ucode/test_firewall_validators.uc` covers that the helpers
|
||||
* drop these inputs rather than letting them through, and `tests/arch-guard.sh`
|
||||
@@ -30,11 +30,11 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/* isEmpty() lives in homeproxy.uc, not in the ucode globals. Without this
|
||||
/* isEmpty() lives in homeproxy-pro.uc, not in the ucode globals. Without this
|
||||
* import every helper below threw "access to undeclared variable isEmpty" the
|
||||
* moment it was called, which no host-side test caught because the test
|
||||
* harness stages its own stand-in module. */
|
||||
import { isEmpty } from './homeproxy.uc';
|
||||
import { isEmpty } from './homeproxy-pro.uc';
|
||||
|
||||
/* IPv4 (with optional /CIDR) - the regex is the standard four-octet body
|
||||
* followed by an optional /0..32. Anything outside that - "1.2.3.4.5",
|
||||
+3
-3
@@ -2,11 +2,11 @@
|
||||
#
|
||||
# Copyright (C) 2025 ImmortalWrt.org
|
||||
#
|
||||
# Single source of truth for every fw4 chain and set owned by homeproxy.
|
||||
# Single source of truth for every fw4 chain and set owned by homeproxy-pro.
|
||||
#
|
||||
# * root/etc/homeproxy/scripts/firewall_post.ut declares these objects and
|
||||
# * root/etc/homeproxy-pro/scripts/firewall_post.ut declares these objects and
|
||||
# decides which of them actually exist for the current proxy mode.
|
||||
# * root/etc/init.d/homeproxy flushes and deletes them on stop.
|
||||
# * root/etc/init.d/homeproxy-pro flushes and deletes them on stop.
|
||||
#
|
||||
# When you add or remove a chain/set in firewall_post.ut, update this list in
|
||||
# the same commit -- tests/ucode/test_fw4_names.sh compares both sides and
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
* scripts/generate_client.uc: process-level entry point.
|
||||
*
|
||||
* The actual client generator is generator/client.uc; this script is the
|
||||
* thin entry point that init.d/homeproxy execve's. Responsibilities:
|
||||
* thin entry point that init.d/homeproxy-pro execve's. Responsibilities:
|
||||
*
|
||||
* 1. Load UCI into the HomeProxyConfig domain model.
|
||||
* 2. Resolve the GenerationContext env: the WAN resolver (ubus) and the two
|
||||
@@ -29,7 +29,7 @@ import { mkdtemp, readfile, writefile } from 'fs';
|
||||
|
||||
import { Loader } from './config/loader.uc';
|
||||
import { generate } from './generator/client.uc';
|
||||
import { removeBlankAttrs, HP_DIR, RUN_DIR, shellQuote, UCICONFIG_DIR } from './homeproxy.uc';
|
||||
import { removeBlankAttrs, HP_DIR, RUN_DIR, shellQuote, UCICONFIG_DIR } from './homeproxy-pro.uc';
|
||||
|
||||
/* Resolve the GenerationContext inputs. This is the only impure step on the
|
||||
* client generation path, and it is deliberately here rather than under
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
import { mkdtemp, writefile } from 'fs';
|
||||
import { Loader } from './config/loader.uc';
|
||||
import { generate_server } from './generator/server.uc';
|
||||
import { removeBlankAttrs, RUN_DIR, shellQuote, UCICONFIG_DIR } from './homeproxy.uc';
|
||||
import { removeBlankAttrs, RUN_DIR, shellQuote, UCICONFIG_DIR } from './homeproxy-pro.uc';
|
||||
|
||||
const dm = Loader.load(UCICONFIG_DIR);
|
||||
const config = generate_server(dm);
|
||||
+1
-1
@@ -39,7 +39,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, strToInt, RUN_DIR } from '../homeproxy.uc';
|
||||
import { isEmpty, strToInt, RUN_DIR } from '../homeproxy-pro.uc';
|
||||
|
||||
import { build_context } from './context.uc';
|
||||
import { build_dns } from './dns.uc';
|
||||
+5
-5
@@ -18,14 +18,14 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty } from '../homeproxy.uc';
|
||||
import { isEmpty } from '../homeproxy-pro.uc';
|
||||
|
||||
import { ConfigQuery } from '../config/model.uc';
|
||||
|
||||
/* parse_port() lives in homeproxy.uc. The Adapter layer needs it
|
||||
/* parse_port() lives in homeproxy-pro.uc. The Adapter layer needs it
|
||||
* too (EndpointFactory builds the WireGuard endpoint) and an adapter must
|
||||
* not import from generator/, so the util moved to the module both layers
|
||||
* already share. Callers import it from '../homeproxy.uc' now. */
|
||||
* already share. Callers import it from '../homeproxy-pro.uc' now. */
|
||||
|
||||
/* Resolve a UCI outbound-style reference (string or array of strings)
|
||||
* into the sing-box tag the generator should emit. The arrays carry
|
||||
@@ -181,7 +181,7 @@ export function attachSchema(config) {
|
||||
* other modes still resolve the proxy node hostname and resolve any
|
||||
* domain the user clicks through. cache_file is a no-op on a fresh
|
||||
* install (no cache to read) and the file is the only piece of state the
|
||||
* generator writes outside /etc/config/homeproxy. Routing-mode gating
|
||||
* generator writes outside /etc/config/homeproxy-pro. Routing-mode gating
|
||||
* used to limit it to bypass_mainland_china + custom, which left
|
||||
* gfwlist / proxy_mainland_china / global with cold-start DNS on every
|
||||
* reload and made the difference between modes hard to explain.
|
||||
@@ -199,7 +199,7 @@ export function attachExperimental(config, routing_mode, dns_store_dns) {
|
||||
config.experimental = {
|
||||
cache_file: {
|
||||
enabled: true,
|
||||
path: '/etc/homeproxy/cache.db',
|
||||
path: '/etc/homeproxy-pro/cache.db',
|
||||
store_dns: (dns_store_dns === '1') ? true : null
|
||||
}
|
||||
};
|
||||
+4
-4
@@ -9,8 +9,8 @@
|
||||
*
|
||||
* env = {
|
||||
* wan_dns, the WAN resolver the CLI resolved via ubus
|
||||
* direct_domain_list, /etc/homeproxy/resources/direct_list.txt
|
||||
* proxy_domain_list, /etc/homeproxy/resources/proxy_list.txt
|
||||
* direct_domain_list, /etc/homeproxy-pro/resources/direct_list.txt
|
||||
* proxy_domain_list, /etc/homeproxy-pro/resources/proxy_list.txt
|
||||
* }
|
||||
*
|
||||
* Why the split exists: this module used to sit inside generator/client.uc
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, strToInt } from '../homeproxy.uc';
|
||||
import { isEmpty, strToInt } from '../homeproxy-pro.uc';
|
||||
|
||||
/* Build the routing-mode-independent ctx the section builders consume.
|
||||
* Mode-dependent defaults are filled in here so each builder can stay a
|
||||
@@ -221,7 +221,7 @@ export function build_context(dm, env) {
|
||||
/* Inline static rule-sets (direct-domain, proxy-domain) only fire
|
||||
* when the routing mode has a non-empty domain list. Both lists are
|
||||
* inputs now: the CLI reads the two files under
|
||||
* /etc/homeproxy/resources/ and passes them in. Custom mode ignores
|
||||
* /etc/homeproxy-pro/resources/ and passes them in. Custom mode ignores
|
||||
* them entirely, exactly as the pre-split read did. */
|
||||
ctx.direct_domain_list = [];
|
||||
ctx.proxy_domain_list = [];
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, strToBool, strToInt, strToTime, parseURL, validation, parse_port } from '../homeproxy.uc';
|
||||
import { isEmpty, strToBool, strToInt, strToTime, parseURL, validation, parse_port } from '../homeproxy-pro.uc';
|
||||
|
||||
|
||||
import { get_outbound, get_resolver, get_ruleset } from './common.uc';
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, strToInt, strToTime, strToBool } from '../homeproxy.uc';
|
||||
import { isEmpty, strToInt, strToTime, strToBool } from '../homeproxy-pro.uc';
|
||||
|
||||
/* Append the always-on inbounds. dns-in is the local listener the DNS chain
|
||||
* hands queries to - runtime/dns.sh points dnsmasq at 127.0.0.1#<dns_port>,
|
||||
+3
-3
@@ -31,7 +31,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, strToInt, strToBool, strToTime } from '../homeproxy.uc';
|
||||
import { isEmpty, strToInt, strToBool, strToTime } from '../homeproxy-pro.uc';
|
||||
|
||||
import { ConfigQuery } from '../config/model.uc';
|
||||
import { OutboundFactory, EndpointFactory } from '../config/adapter.uc';
|
||||
@@ -103,13 +103,13 @@ function keep_candidate(dm, id) {
|
||||
const node = ConfigQuery.node_by_id(dm, id);
|
||||
|
||||
if (!node) {
|
||||
warn(sprintf("homeproxy: urltest candidate '%s' no longer exists, skipping.", id));
|
||||
warn(sprintf("homeproxy-pro: urltest candidate '%s' no longer exists, skipping.", id));
|
||||
return false;
|
||||
}
|
||||
|
||||
const problems = OutboundFactory.problems(node);
|
||||
if (length(problems)) {
|
||||
warn(sprintf("homeproxy: skipping urltest candidate '%s': %s.", id, join(', ', problems)));
|
||||
warn(sprintf("homeproxy-pro: skipping urltest candidate '%s': %s.", id, join(', ', problems)));
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, strToInt, strToTime, strToBool, parse_port } from '../homeproxy.uc';
|
||||
import { isEmpty, strToInt, strToTime, strToBool, parse_port } from '../homeproxy-pro.uc';
|
||||
|
||||
|
||||
import { get_outbound, get_resolver, get_ruleset, get_direct_override } from './common.uc';
|
||||
+4
-4
@@ -26,7 +26,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, validateHomeProxyPath } from '../homeproxy.uc';
|
||||
import { isEmpty, validateHomeProxyPath } from '../homeproxy-pro.uc';
|
||||
|
||||
import { get_outbound, isDirectOutboundTag } from './common.uc';
|
||||
|
||||
@@ -56,9 +56,9 @@ export function build_user_rulesets(rule_set_array, dm, ctx) {
|
||||
fail loud on a missing/disabled reference. */
|
||||
const fetch_ref = (cfg.type === 'remote') ? (cfg.url || '') : (cfg.path || '');
|
||||
if (!match(fetch_ref, /\{tag\}/))
|
||||
die(sprintf("homeproxy: rule-set '%s' uses extra tags but its %s source lacks a {tag} placeholder; add {tag} to the source or drop the extra tags.", cfg.name, cfg.type));
|
||||
die(sprintf("homeproxy-pro: rule-set '%s' uses extra tags but its %s source lacks a {tag} placeholder; add {tag} to the source or drop the extra tags.", cfg.name, cfg.type));
|
||||
if (cfg.type === 'remote' && !isEmpty(cfg.initial_path) && !match(cfg.initial_path, /\{tag\}/))
|
||||
die(sprintf("homeproxy: rule-set '%s' uses extra tags but its initial_path lacks a {tag} placeholder.", cfg.name));
|
||||
die(sprintf("homeproxy-pro: rule-set '%s' uses extra tags but its initial_path lacks a {tag} placeholder.", cfg.name));
|
||||
}
|
||||
|
||||
/* Local rule-set path is read by sing-box as root. The whitelist
|
||||
@@ -70,7 +70,7 @@ export function build_user_rulesets(rule_set_array, dm, ctx) {
|
||||
* sing-box checking whatever the field evaluated to, and the
|
||||
* user only saw the reload keep the previous config. */
|
||||
if (cfg.type === 'local' && cfg.path && !validateHomeProxyPath(cfg.path))
|
||||
die(sprintf("homeproxy: rule-set '%s' path '%s' is outside the homeproxy whitelist; choose a path under /etc/homeproxy/.", cfg.name, cfg.path));
|
||||
die(sprintf("homeproxy-pro: rule-set '%s' path '%s' is outside the homeproxy-pro whitelist; choose a path under /etc/homeproxy-pro/.", cfg.name, cfg.path));
|
||||
|
||||
const ruleset = {
|
||||
type: cfg.type,
|
||||
+1
-1
@@ -40,7 +40,7 @@ export function generate_server(dm) {
|
||||
log: {
|
||||
disabled: false,
|
||||
level: log_level,
|
||||
output: '/var/run/homeproxy/sing-box-s.log',
|
||||
output: '/var/run/homeproxy-pro/sing-box-s.log',
|
||||
timestamp: true
|
||||
},
|
||||
inbounds: []
|
||||
+14
-14
@@ -8,16 +8,16 @@ import { access, lstat, mkdtemp, open, rmdir, unlink } from 'fs';
|
||||
import { urldecode_params } from 'luci.http';
|
||||
|
||||
/* Global variables start */
|
||||
export const HP_DIR = '/etc/homeproxy';
|
||||
export const RUN_DIR = '/var/run/homeproxy';
|
||||
export const HP_DIR = '/etc/homeproxy-pro';
|
||||
export const RUN_DIR = '/var/run/homeproxy-pro';
|
||||
/* Where the UCI configuration lives. This is /etc/config - NOT HP_DIR/config.
|
||||
* The package ships its config as the conffile /etc/config/homeproxy, and
|
||||
* The package ships its config as the conffile /etc/config/homeproxy-pro, and
|
||||
* ucode's `cursor(dir)` treats `dir` as the *confdir*, so
|
||||
* cursor(HP_DIR + '/config') would read /etc/homeproxy/config/homeproxy, a
|
||||
* cursor(HP_DIR + '/config') would read /etc/homeproxy-pro/config/homeproxy-pro, a
|
||||
* path that exists nowhere. uci.load() then returns null, every uci.get()
|
||||
* returns null, and the Loader silently yields pure defaults: no main-out,
|
||||
* no route/direct final, none of the user's ports or nodes.
|
||||
* Verified on a device: cursor('/etc/homeproxy/config').get(...) is empty
|
||||
* Verified on a device: cursor('/etc/homeproxy-pro/config').get(...) is empty
|
||||
* while cursor().get(...) returns the configured value.
|
||||
* The test suite stages a rewritten copy of this constant instead of the
|
||||
* old `__LOADER_DIR__` source sed. */
|
||||
@@ -44,11 +44,11 @@ export function isBinary(str) {
|
||||
* sing-box config. sing-box runs as root and reads the file itself; an
|
||||
* arbitrary UCI value of e.g. /etc/passwd would leak the file to anyone
|
||||
* who could write to the UCI tree. The LuCI UI already gates the input
|
||||
* via validateCertificatePath() in homeproxy.js, but UCI can be set from
|
||||
* via validateCertificatePath() in homeproxy-pro.js, but UCI can be set from
|
||||
* any node on the LAN (subscription updates, scripted edits) and the
|
||||
* UI check is only UX - we have to enforce on the backend.
|
||||
*
|
||||
* /etc/homeproxy/... - project-managed certs / ruleset paths / etc.
|
||||
* /etc/homeproxy-pro/... - project-managed certs / ruleset paths / etc.
|
||||
* /tmp/homeproxy_* - upload staging files (cert_upload_* writes)
|
||||
*
|
||||
* Anything else (relative paths, /etc/passwd, /var/run/...) returns false
|
||||
@@ -58,7 +58,7 @@ export function validateHomeProxyPath(p) {
|
||||
return false;
|
||||
|
||||
/* Reject traversal *before* the prefix checks below: a plain prefix
|
||||
* comparison accepts '/etc/homeproxy/../../etc/shadow', and sing-box reads
|
||||
* comparison accepts '/etc/homeproxy-pro/../../etc/shadow', and sing-box reads
|
||||
* these paths as root. */
|
||||
if (match(p, /(^|\/)\.\.(\/|$)/))
|
||||
return false;
|
||||
@@ -69,7 +69,7 @@ export function validateHomeProxyPath(p) {
|
||||
if (substr(p, 0, 1) !== '/')
|
||||
return false;
|
||||
|
||||
if (substr(p, 0, length('/etc/homeproxy/')) === '/etc/homeproxy/')
|
||||
if (substr(p, 0, length('/etc/homeproxy-pro/')) === '/etc/homeproxy-pro/')
|
||||
return true;
|
||||
|
||||
if (substr(p, 0, length('/tmp/homeproxy_')) === '/tmp/homeproxy_')
|
||||
@@ -84,16 +84,16 @@ export function validateHomeProxyPath(p) {
|
||||
* certificate normally lives under /etc/ssl/ or under the ACME state in
|
||||
* /etc/acme/, and the two sing-box jails mount exactly those directories
|
||||
* (see runtime/service.sh's procd_add_jail_mount calls). validateHomeProxyPath()
|
||||
* only knows /etc/homeproxy/ and /tmp/homeproxy_, so a certificate the user
|
||||
* only knows /etc/homeproxy-pro/ and /tmp/homeproxy_, so a certificate the user
|
||||
* legitimately picked from /etc/ssl/ was accepted by the LuCI validator and
|
||||
* then silently dropped here - certificate_path became null and the TLS
|
||||
* listener failed with no error pointing at the path.
|
||||
*
|
||||
* The list is mirrored by HP_CERT_PATH_ROOTS in
|
||||
* htdocs/luci-static/resources/homeproxy.js; guard 29 in tests/arch-guard.sh
|
||||
* htdocs/luci-static/resources/homeproxy-pro.js; guard 29 in tests/arch-guard.sh
|
||||
* keeps the two in step.
|
||||
*/
|
||||
export const CERT_PATH_ROOTS = ['/etc/homeproxy/certs/', '/etc/acme/', '/etc/ssl/'];
|
||||
export const CERT_PATH_ROOTS = ['/etc/homeproxy-pro/certs/', '/etc/acme/', '/etc/ssl/'];
|
||||
|
||||
/* validateCertificatePath(p) - the TLS certificate/key path gate.
|
||||
*
|
||||
@@ -222,8 +222,8 @@ export function getTime(epoch) {
|
||||
};
|
||||
|
||||
/* Redact the credential-bearing parts of a URL before logging it. Any
|
||||
* subscription URL we ship into /var/run/homeproxy/homeproxy.log is
|
||||
* readable by anyone who can read /var/run/homeproxy - including UCI
|
||||
* subscription URL we ship into /var/run/homeproxy-pro/homeproxy-pro.log is
|
||||
* readable by anyone who can read /var/run/homeproxy-pro - including UCI
|
||||
* defaults that ship on the device and anyone with shell on the LAN.
|
||||
* (Confirmed on the device: the log is 0644.)
|
||||
*
|
||||
+3
-3
@@ -8,11 +8,11 @@
|
||||
'use strict';
|
||||
|
||||
import { cursor } from 'uci';
|
||||
import { isEmpty, parseURL, shellQuote, validation } from './homeproxy.uc';
|
||||
import { isEmpty, parseURL, shellQuote, validation } from './homeproxy-pro.uc';
|
||||
|
||||
const uci = cursor();
|
||||
|
||||
const uciconfig = 'homeproxy';
|
||||
const uciconfig = 'homeproxy-pro';
|
||||
uci.load(uciconfig);
|
||||
|
||||
const uciinfra = 'infra',
|
||||
@@ -93,7 +93,7 @@ if (isEmpty(uci.get(uciconfig, ucimain, 'log_level')))
|
||||
/* v28.9.1.16: cn_ip_fallback default flipped to '1' (tutorial §2.1 / docs/linux.json 与 pro 的差距分析.md).
|
||||
* 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 (the package ships with that as the default).
|
||||
* from /etc/config/homeproxy-pro (the package ships with that as the default).
|
||||
* isEmpty() is the same gate log_level uses, so an upgrade that already has
|
||||
* the option set (the fresh-install path of a prior release) keeps its value. */
|
||||
if (isEmpty(uci.get(uciconfig, ucimain, 'cn_ip_fallback')))
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -24,7 +24,7 @@
|
||||
|
||||
import { urldecode, urlencode } from 'luci.http';
|
||||
|
||||
import { decodeBase64Str, isEmpty, parseURL } from '../homeproxy.uc';
|
||||
import { decodeBase64Str, isEmpty, parseURL } from '../homeproxy-pro.uc';
|
||||
|
||||
/* https://shadowsocks.org/guide/sip008.html */
|
||||
export function parse_sip008_uri(uri) {
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, validation } from '../homeproxy.uc';
|
||||
import { isEmpty, validation } from '../homeproxy-pro.uc';
|
||||
|
||||
import {
|
||||
parse_sip008_uri, parse_anytls_uri, parse_http_uri,
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { validation } from '../homeproxy.uc';
|
||||
import { validation } from '../homeproxy-pro.uc';
|
||||
|
||||
/* Each entry carries a stable `kind` (so callers / tests can branch on
|
||||
* it) and a `message` for the log line. Adding a new rule is:
|
||||
File renamed without changes.
+6
-6
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Copyright (C) 2025 ImmortalWrt.org
|
||||
#
|
||||
# Configuration transaction helpers for homeproxy.
|
||||
# Configuration transaction helpers for homeproxy-pro.
|
||||
#
|
||||
# The generated sing-box configuration is the only thing standing between the
|
||||
# router and a working service, so it is handled as a transaction:
|
||||
@@ -15,9 +15,9 @@
|
||||
# -> ensure-live if generation produced nothing, restore the
|
||||
# known-good copy instead of failing the start
|
||||
# -> rollback a reload that comes up unhealthy puts the known-good
|
||||
# copy back (see reload_service in init.d/homeproxy)
|
||||
# copy back (see reload_service in init.d/homeproxy-pro)
|
||||
#
|
||||
# Sourced by /etc/init.d/homeproxy. The functions only take explicit paths
|
||||
# Sourced by /etc/init.d/homeproxy-pro. The functions only take explicit paths
|
||||
# and never call procd or ubus, so tests/runtime/test_config_transaction.sh
|
||||
# can exercise them without a router.
|
||||
|
||||
@@ -88,8 +88,8 @@ hp_same_file() {
|
||||
#
|
||||
# Two passes over the file, because the snapshot carries two shapes:
|
||||
#
|
||||
# homeproxy.<section>.<option>=<value> scalar, replayed with `uci set`
|
||||
# list homeproxy.<section>.<option>=a,b UCI list, replayed with
|
||||
# homeproxy-pro.<section>.<option>=<value> scalar, replayed with `uci set`
|
||||
# list homeproxy-pro.<section>.<option>=a,b UCI list, replayed with
|
||||
# `uci delete` + `uci add_list`
|
||||
#
|
||||
# A list replayed through `uci set` becomes a *string*, and the firewall
|
||||
@@ -146,6 +146,6 @@ hp_restore_known_good_uci() {
|
||||
done
|
||||
done < "$snap"
|
||||
|
||||
[ "$saved" = "1" ] && uci commit homeproxy </dev/null || return 1
|
||||
[ "$saved" = "1" ] && uci commit homeproxy-pro </dev/null || return 1
|
||||
return 0
|
||||
}
|
||||
+11
-11
@@ -2,21 +2,21 @@
|
||||
#
|
||||
# Copyright (C) 2025 ImmortalWrt.org
|
||||
#
|
||||
# dnsmasq integration for homeproxy.
|
||||
# dnsmasq integration for homeproxy-pro.
|
||||
#
|
||||
# homeproxy does not run its own DNS front end: it points dnsmasq at the
|
||||
# homeproxy-pro does not run its own DNS front end: it points dnsmasq at the
|
||||
# sing-box DNS listener and, in the list-based routing modes, feeds it the
|
||||
# per-domain `server=` / `nftset=` mappings. All of that used to live inline
|
||||
# in /etc/init.d/homeproxy.
|
||||
# in /etc/init.d/homeproxy-pro.
|
||||
#
|
||||
# Layering note: this is a device-side module. It calls `log` and, in
|
||||
# hp_dnsmasq_write_snippets, relies on `config_load` having run (see
|
||||
# runtime/service.sh's header for the rationale).
|
||||
#
|
||||
# Sourced by /etc/init.d/homeproxy.
|
||||
# Sourced by /etc/init.d/homeproxy-pro.
|
||||
|
||||
# hp_dnsmasq_resolve_dir
|
||||
# Locate the directory dnsmasq reads the homeproxy snippets from and store it
|
||||
# Locate the directory dnsmasq reads the homeproxy-pro snippets from and store it
|
||||
# in the global DNSMASQ_DIR.
|
||||
#
|
||||
# The UCI section name of the dnsmasq config is part of
|
||||
@@ -40,12 +40,12 @@ hp_dnsmasq_resolve_dir() {
|
||||
DNSMASQ_DIR=""
|
||||
if [ -n "$dnsmasq_uci_config" ] && [ -f "/tmp/etc/dnsmasq.conf.$dnsmasq_uci_config" ]; then
|
||||
dnsmasq_conf_dir="$(awk -F '=' '/^conf-dir=/ {print $2; exit}' "/tmp/etc/dnsmasq.conf.$dnsmasq_uci_config")"
|
||||
[ -n "$dnsmasq_conf_dir" ] && DNSMASQ_DIR="$dnsmasq_conf_dir/dnsmasq-homeproxy.d"
|
||||
[ -n "$dnsmasq_conf_dir" ] && DNSMASQ_DIR="$dnsmasq_conf_dir/dnsmasq-homeproxy-pro.d"
|
||||
fi
|
||||
|
||||
if [ -z "$DNSMASQ_DIR" ]; then
|
||||
log "WARNING: unable to derive the dnsmasq conf-dir (section '${dnsmasq_uci_config:-unknown}'), using /tmp/dnsmasq.d/dnsmasq-homeproxy.d."
|
||||
DNSMASQ_DIR="/tmp/dnsmasq.d/dnsmasq-homeproxy.d"
|
||||
log "WARNING: unable to derive the dnsmasq conf-dir (section '${dnsmasq_uci_config:-unknown}'), using /tmp/dnsmasq.d/dnsmasq-homeproxy-pro.d."
|
||||
DNSMASQ_DIR="/tmp/dnsmasq.d/dnsmasq-homeproxy-pro.d"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ hp_dnsmasq_write_snippets() {
|
||||
local hp_dir="$2"
|
||||
local routing_mode="$3"
|
||||
local ipv6_support dns_port
|
||||
local include="$dnsmasq_dir/../dnsmasq-homeproxy.conf"
|
||||
local include="$dnsmasq_dir/../dnsmasq-homeproxy-pro.conf"
|
||||
local stage changed=0
|
||||
|
||||
config_get_bool ipv6_support "config" "ipv6_support" "0"
|
||||
@@ -250,7 +250,7 @@ hp_dnsmasq_write_snippets() {
|
||||
# pre-existing state, so there is no original to put back.
|
||||
hp_dnsmasq_remove_snippets() {
|
||||
local dnsmasq_dir="$1"
|
||||
local include="$dnsmasq_dir/../dnsmasq-homeproxy.conf"
|
||||
local include="$dnsmasq_dir/../dnsmasq-homeproxy-pro.conf"
|
||||
|
||||
# Nothing to undo when nothing was written - which is the normal case on a
|
||||
# router whose outbound node is 'nil', or one that never started. Removing
|
||||
@@ -262,7 +262,7 @@ hp_dnsmasq_remove_snippets() {
|
||||
|
||||
rm -rf "$include" "$dnsmasq_dir"
|
||||
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1 \
|
||||
|| log "Warning: failed to restart dnsmasq after removing the homeproxy snippets."
|
||||
|| log "Warning: failed to restart dnsmasq after removing the homeproxy-pro snippets."
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
+8
-8
@@ -2,18 +2,18 @@
|
||||
#
|
||||
# Copyright (C) 2025 ImmortalWrt.org
|
||||
#
|
||||
# fw4 integration for homeproxy.
|
||||
# fw4 integration for homeproxy-pro.
|
||||
#
|
||||
# homeproxy owns a fixed set of nft chains and sets inside the `fw4` table.
|
||||
# homeproxy-pro owns a fixed set of nft chains and sets inside the `fw4` table.
|
||||
# They are created by the rendered template (firewall_post.ut, applied by
|
||||
# `fw4 reload`) and torn down one object at a time on stop. All of that used
|
||||
# to live inline in /etc/init.d/homeproxy.
|
||||
# to live inline in /etc/init.d/homeproxy-pro.
|
||||
#
|
||||
# Layering note: this is a device-side module. It calls `log` and, for the
|
||||
# teardown, requires the HP_FW4_CHAINS / HP_FW4_SETS inventory to be in scope
|
||||
# (root/etc/homeproxy/scripts/fw4_names.sh, sourced by init.d).
|
||||
# (root/etc/homeproxy-pro/scripts/fw4_names.sh, sourced by init.d).
|
||||
#
|
||||
# Sourced by /etc/init.d/homeproxy.
|
||||
# Sourced by /etc/init.d/homeproxy-pro.
|
||||
|
||||
# hp_restore_upnp_mappings
|
||||
# fw4 reload rewrites the whole ruleset, which drops the miniupnpd mappings,
|
||||
@@ -69,7 +69,7 @@ hp_firewall_apply() {
|
||||
}
|
||||
|
||||
# hp_firewall_teardown <run-dir> [keep-pre]
|
||||
# Flush and delete every fw4 object homeproxy owns. Deleting one at a time
|
||||
# Flush and delete every fw4 object homeproxy-pro owns. Deleting one at a time
|
||||
# matters: `nft -f` batches are atomic, so a single object that does not
|
||||
# exist in the current proxy mode would roll back the rest.
|
||||
#
|
||||
@@ -98,13 +98,13 @@ hp_firewall_teardown() {
|
||||
fi
|
||||
: > "$run_dir/fw4_post.nft"
|
||||
|
||||
# Stop-time fw4 reload is best-effort on purpose: every homeproxy-owned
|
||||
# Stop-time fw4 reload is best-effort on purpose: every homeproxy-pro-owned
|
||||
# chain and set has already been flushed and deleted above, so the
|
||||
# live ruleset no longer references them. A reload that succeeds here
|
||||
# also re-loads /etc/config/firewall; a reload that fails leaves the
|
||||
# rest of the user's ruleset untouched, which is the safer outcome -
|
||||
# logging a Warning used to suggest the stop had lost something when
|
||||
# in fact nothing homeproxy-owned was left to lose.
|
||||
# in fact nothing homeproxy-pro-owned was left to lose.
|
||||
fw4 reload >"/dev/null" 2>&1 || true
|
||||
hp_restore_upnp_mappings
|
||||
}
|
||||
+4
-4
@@ -2,12 +2,12 @@
|
||||
#
|
||||
# Copyright (C) 2025 ImmortalWrt.org
|
||||
#
|
||||
# Runtime health helpers for homeproxy.
|
||||
# Runtime health helpers for homeproxy-pro.
|
||||
#
|
||||
# After a reload the service must be able to answer "did the instance
|
||||
# actually come up with the new configuration?". That answer decides whether
|
||||
# the transaction in runtime/config.sh rolls back, so it is kept here instead
|
||||
# of being inlined in init.d/homeproxy.
|
||||
# of being inlined in init.d/homeproxy-pro.
|
||||
#
|
||||
# A single "is a matching process alive?" probe is NOT a health check, and the
|
||||
# first version of this file learned that the hard way. Measured on the test
|
||||
@@ -28,10 +28,10 @@
|
||||
# several consecutive samples (a service that binds and then dies must not be
|
||||
# able to pass on a single lucky poll).
|
||||
#
|
||||
# Sourced by /etc/init.d/homeproxy; testable off-target by stubbing pgrep /
|
||||
# Sourced by /etc/init.d/homeproxy-pro; testable off-target by stubbing pgrep /
|
||||
# ubus / jsonfilter / netstat (see tests/runtime/).
|
||||
|
||||
: "${HP_SERVICE:=homeproxy}"
|
||||
: "${HP_SERVICE:=homeproxy-pro}"
|
||||
|
||||
# How many consecutive healthy samples hp_wait_service requires, and how many
|
||||
# seconds it may spend trying.
|
||||
+4
-4
@@ -2,17 +2,17 @@
|
||||
#
|
||||
# Copyright (C) 2025 ImmortalWrt.org
|
||||
#
|
||||
# Routing/network plumbing for homeproxy.
|
||||
# Routing/network plumbing for homeproxy-pro.
|
||||
#
|
||||
# The tproxy and TUN paths need ip rules, a routing table and (for TUN) the
|
||||
# device itself before procd starts sing-box, and they need to be torn down
|
||||
# again on stop. All of that used to live inline in /etc/init.d/homeproxy.
|
||||
# again on stop. All of that used to live inline in /etc/init.d/homeproxy-pro.
|
||||
#
|
||||
# Layering note: this is a device-side module. It calls `log` and, like the
|
||||
# other runtime modules, requires `config_load` to have run (it reads the
|
||||
# infra marks and the TUN device name itself).
|
||||
#
|
||||
# Sourced by /etc/init.d/homeproxy.
|
||||
# Sourced by /etc/init.d/homeproxy-pro.
|
||||
|
||||
# hp_net_wait_wan [seconds]
|
||||
# Block until a default route or an "up" wan interface exists. Bounded: after
|
||||
@@ -122,7 +122,7 @@ hp_net_setup() {
|
||||
}
|
||||
|
||||
# hp_net_teardown
|
||||
# Remove every tproxy/TUN rule and route homeproxy added. Requires
|
||||
# Remove every tproxy/TUN rule and route homeproxy-pro added. Requires
|
||||
# config_load.
|
||||
#
|
||||
# ip rule del removes a single matching rule, so a repeated start (or an
|
||||
+12
-12
@@ -2,10 +2,10 @@
|
||||
#
|
||||
# Copyright (C) 2025 ImmortalWrt.org
|
||||
#
|
||||
# Service-side runtime helpers for homeproxy.
|
||||
# Service-side runtime helpers for homeproxy-pro.
|
||||
#
|
||||
# These are the device-side orchestration pieces that used to be inlined in
|
||||
# /etc/init.d/homeproxy: the sing-box version gate, the auto-update cron
|
||||
# /etc/init.d/homeproxy-pro: the sing-box version gate, the auto-update cron
|
||||
# entry, the runtime-file preparation, the procd instance registrations and
|
||||
# the "generate -> known-good" transaction that start_service runs for each
|
||||
# side.
|
||||
@@ -17,7 +17,7 @@
|
||||
# have run `config_load` and must be inside a procd init context. That is
|
||||
# exactly why they were moved out of init.d and not turned into pure helpers.
|
||||
#
|
||||
# Sourced by /etc/init.d/homeproxy.
|
||||
# Sourced by /etc/init.d/homeproxy-pro.
|
||||
|
||||
# hp_require_singbox
|
||||
# Refuse to start on a sing-box older than 1.14: the generated configuration
|
||||
@@ -155,7 +155,7 @@ hp_prepare_runtime_files() {
|
||||
if mkdir -p "$hp_dir/certs" 2>"/dev/null"; then
|
||||
# The server runs jailed as the sing-box user and reads its
|
||||
# certificate, key and ECH config from this directory, while an
|
||||
# upload lands as root:0600 (luci.homeproxy). Hand the directory
|
||||
# upload lands as root:0600 (luci.homeproxy-pro). Hand the directory
|
||||
# and its files to that user and keep them 0700/0600: the jailed
|
||||
# server can read them and nobody else can reach the private key.
|
||||
# Without this the choice was "world-readable key" or "jailed
|
||||
@@ -238,7 +238,7 @@ hp_procd_client_instance() {
|
||||
procd_add_jail_mount "/tmp/dhcp.leases"
|
||||
procd_add_jail_mount "/etc/localtime"
|
||||
procd_add_jail_mount "/etc/TZ"
|
||||
procd_set_param capabilities "/etc/capabilities/homeproxy.json"
|
||||
procd_set_param capabilities "/etc/capabilities/homeproxy-pro.json"
|
||||
procd_set_param no_new_privs 1
|
||||
procd_set_param user sing-box
|
||||
procd_set_param group sing-box
|
||||
@@ -278,7 +278,7 @@ hp_procd_server_instance() {
|
||||
procd_add_jail_mount "/etc/ssl/"
|
||||
procd_add_jail_mount "/etc/localtime"
|
||||
procd_add_jail_mount "/etc/TZ"
|
||||
procd_set_param capabilities "/etc/capabilities/homeproxy.json"
|
||||
procd_set_param capabilities "/etc/capabilities/homeproxy-pro.json"
|
||||
procd_set_param no_new_privs 1
|
||||
procd_set_param user sing-box
|
||||
procd_set_param group sing-box
|
||||
@@ -514,9 +514,9 @@ control.wan_direct_ipv6_ips"
|
||||
#
|
||||
# One line per key so a reader without uci-tools can iterate the file:
|
||||
#
|
||||
# homeproxy.<section>.<option>=<value> scalar
|
||||
# list homeproxy.<section>.<option>=a,b list, ','-joined
|
||||
# list homeproxy.<section>.<option>= list that was absent or empty
|
||||
# homeproxy-pro.<section>.<option>=<value> scalar
|
||||
# list homeproxy-pro.<section>.<option>=a,b list, ','-joined
|
||||
# list homeproxy-pro.<section>.<option>= list that was absent or empty
|
||||
#
|
||||
# An absent scalar is written as an empty value, which is how the replay
|
||||
# spells "remove the option again" (UCI deletes an option set to an empty
|
||||
@@ -532,7 +532,7 @@ hp_write_uci_snapshot() {
|
||||
sec="${pair%%.*}"
|
||||
opt="${pair#*.}"
|
||||
config_get val "$sec" "$opt"
|
||||
printf 'homeproxy.%s.%s=%s\n' "$sec" "$opt" "$val"
|
||||
printf 'homeproxy-pro.%s.%s=%s\n' "$sec" "$opt" "$val"
|
||||
done
|
||||
|
||||
for pair in $HP_INTERCEPT_LISTS; do
|
||||
@@ -544,7 +544,7 @@ hp_write_uci_snapshot() {
|
||||
for el in $val; do
|
||||
[ -z "$out" ] && out="$el" || out="$out,$el"
|
||||
done
|
||||
printf 'list homeproxy.%s.%s=%s\n' "$sec" "$opt" "$out"
|
||||
printf 'list homeproxy-pro.%s.%s=%s\n' "$sec" "$opt" "$out"
|
||||
done
|
||||
} > "$snap.tmp" 2>"/dev/null" && mv -f "$snap.tmp" "$snap"
|
||||
}
|
||||
@@ -555,7 +555,7 @@ hp_write_uci_snapshot() {
|
||||
# rollback target is always something that came up.
|
||||
#
|
||||
# Also writes $good_dir/uci-snapshot.txt (see HP_INTERCEPT_SCALARS above).
|
||||
# The rollback path in init.d/homeproxy reads that file back BEFORE its
|
||||
# The rollback path in init.d/homeproxy-pro reads that file back BEFORE its
|
||||
# stop;start, otherwise start_service reinstalls the new (failing) layer on
|
||||
# top of the rolled-back sing-box bytes - the classic "rollback restored the
|
||||
# config but nft is still the new chains" silent failure that took a
|
||||
+2
-2
@@ -26,7 +26,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, decodeBase64Str, redactUrl } from '../homeproxy.uc';
|
||||
import { isEmpty, decodeBase64Str, redactUrl } from '../homeproxy-pro.uc';
|
||||
|
||||
export function decode(content, log, url) {
|
||||
if (isEmpty(content))
|
||||
@@ -59,7 +59,7 @@ export function decode(content, log, url) {
|
||||
}
|
||||
} catch (e) {
|
||||
/* The URL carries the subscription token and this line lands in
|
||||
* homeproxy.log, which is world-readable. fetcher.uc and the
|
||||
* homeproxy-pro.log, which is world-readable. fetcher.uc and the
|
||||
* orchestrator both redact; this path was missed. */
|
||||
const tag = url ? sprintf('for %s, ', redactUrl(url)) : '';
|
||||
log(sprintf('JSON parse failed %strying base64: %s', tag, e.message));
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* Thin wrapper around homeproxy's wGETVerbose that logs
|
||||
* Thin wrapper around homeproxy-pro's wGETVerbose that logs
|
||||
* the failure and returns a flat { content, error } shape. The
|
||||
* orchestrator used to inline the empty-content check + log
|
||||
* message right next to the call, which made the loop body hard
|
||||
@@ -20,13 +20,13 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, redactUrl, wGETVerbose } from '../homeproxy.uc';
|
||||
import { isEmpty, redactUrl, wGETVerbose } from '../homeproxy-pro.uc';
|
||||
|
||||
export function fetch(url, user_agent, log) {
|
||||
const result = wGETVerbose(url, user_agent);
|
||||
if (isEmpty(result.content)) {
|
||||
/* Redact the URL before logging: the query string is the
|
||||
* subscription token and homeproxy.log is world-readable on
|
||||
* subscription token and homeproxy-pro.log is world-readable on
|
||||
* a default install. The original URL goes back to the caller
|
||||
* via result.error so error propagation still works. */
|
||||
log(sprintf('Failed to fetch resources from %s: %s',
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty } from '../homeproxy.uc';
|
||||
import { isEmpty } from '../homeproxy-pro.uc';
|
||||
|
||||
/* Decide whether a node named `name` should be dropped. `mode` is
|
||||
* one of:
|
||||
+2
-2
@@ -44,7 +44,7 @@
|
||||
|
||||
import { md5 } from 'digest';
|
||||
|
||||
import { isEmpty } from '../homeproxy.uc';
|
||||
import { isEmpty } from '../homeproxy-pro.uc';
|
||||
|
||||
/* Relative, like config/loader.uc's '../parser/mapping.uc': ucode's
|
||||
* resolver only searches top-level module names in the -L tree, so a
|
||||
@@ -215,7 +215,7 @@ function maybeScrubUrltestNodes(uci, uciconfig, ucimain, listOption, log) {
|
||||
*
|
||||
* The new value is also pushed onto `result.log` so the orchestrator
|
||||
* can replay it (it uses `result.log` to log the same lines verbatim;
|
||||
* the homeproxy.log capture goes through the separate `log` arg). */
|
||||
* the homeproxy-pro.log capture goes through the separate `log` arg). */
|
||||
function switchMainNodeOffDanglingRef(uci, uciconfig, ucimain, field, current_value, first_server, result, log) {
|
||||
if (uci.get(uciconfig, current_value))
|
||||
return current_value;
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2023 ImmortalWrt.org
|
||||
|
||||
SCRIPTS_DIR="/etc/homeproxy/scripts"
|
||||
SCRIPTS_DIR="/etc/homeproxy-pro/scripts"
|
||||
|
||||
for i in "china_ip4" "china_ip6" "gfw_list" "china_list"; do
|
||||
"$SCRIPTS_DIR"/update_resources.sh "$i"
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
# entry; tests/runtime/test_resource_update.sh exercises the verification path
|
||||
# off-target with stubbed wget/jsonfilter/ucode.
|
||||
|
||||
NAME="homeproxy"
|
||||
NAME="homeproxy-pro"
|
||||
|
||||
# The blob-digest helper sits next to this script and is invoked through it, so
|
||||
# its location follows however the script was started (rpcd, cron, a shell).
|
||||
@@ -75,7 +75,7 @@ check_list_update() {
|
||||
local listref="$3"
|
||||
local listname="$4"
|
||||
local lock="$RUN_DIR/update_resources-$listtype.lock"
|
||||
local github_token="$(uci -q get homeproxy.config.github_token)"
|
||||
local github_token="$(uci -q get homeproxy-pro.config.github_token)"
|
||||
local wget="wget --timeout=10 -q"
|
||||
|
||||
# fd 9, not 200: POSIX only guarantees 0-9, and dash fails the whole
|
||||
+11
-11
@@ -52,7 +52,7 @@ import { cursor } from 'uci';
|
||||
import {
|
||||
executeCommand, getTime, isEmpty, HP_DIR, RUN_DIR, redactUrl, shellQuote,
|
||||
UCICONFIG_DIR
|
||||
} from './homeproxy.uc';
|
||||
} from './homeproxy-pro.uc';
|
||||
|
||||
import { parse_uri } from './parser/uri.uc';
|
||||
import { normalize } from './parser/normalize.uc';
|
||||
@@ -77,12 +77,12 @@ import { Loader } from './config/loader.uc';
|
||||
* A2: the run performs exactly one uci.commit(). The Repository modules stage
|
||||
* mutations only.
|
||||
*
|
||||
* UCICONFIG_DIR (homeproxy.uc) is the config *directory* Loader.load() takes,
|
||||
* UCICONFIG_DIR (homeproxy-pro.uc) is the config *directory* Loader.load() takes,
|
||||
* and the sandboxed test points it at its own config instead of /etc/config
|
||||
* (tests/ucode/test_subscription_updater_runs.sh rewrites that line in
|
||||
* homeproxy.uc and fails loudly if the rewrite did not apply). */
|
||||
const CONFIG_FILE = UCICONFIG_DIR + '/homeproxy';
|
||||
const uciconfig = 'homeproxy';
|
||||
* homeproxy-pro.uc and fails loudly if the rewrite did not apply). */
|
||||
const CONFIG_FILE = UCICONFIG_DIR + '/homeproxy-pro';
|
||||
const uciconfig = 'homeproxy-pro';
|
||||
|
||||
const ucimain = 'config',
|
||||
ucinode = 'node';
|
||||
@@ -139,7 +139,7 @@ function load_locked_state() {
|
||||
|
||||
|
||||
function log(...args) {
|
||||
const logfile = open(`${RUN_DIR}/homeproxy.log`, 'a');
|
||||
const logfile = open(`${RUN_DIR}/homeproxy-pro.log`, 'a');
|
||||
logfile.write(`${getTime()} [SUBSCRIBE] ${join(' ', args)}\n`);
|
||||
logfile.close();
|
||||
}
|
||||
@@ -202,7 +202,7 @@ function main() {
|
||||
/* ubus is unreachable when no ubusd is running (dev host)
|
||||
* and the feature query is optional: fall back to the same
|
||||
* empty feature set the code below already handles. */
|
||||
const sing_features = (ubus?.call('luci.homeproxy', 'singbox_get_features', {})) || {};
|
||||
const sing_features = (ubus?.call('luci.homeproxy-pro', 'singbox_get_features', {})) || {};
|
||||
if (isEmpty(sing_features))
|
||||
log('Warning: Failed to query sing-box features via ubus, assuming defaults.');
|
||||
|
||||
@@ -363,16 +363,16 @@ function main() {
|
||||
log('Reloading service...');
|
||||
|
||||
/* Run the init script directly. The previous code called
|
||||
* `init_action('homeproxy', 'reload')` imported from luci.sys, but
|
||||
* `init_action('homeproxy-pro', 'reload')` imported from luci.sys, but
|
||||
* neither openwrt/luci nor immortalwrt/luci exports an `init_action`
|
||||
* from that module (it has process_list / conntrack_list /
|
||||
* init_list / init_index / init_enabled), so the import failed to
|
||||
* resolve and this script could not load on a router at all.
|
||||
*
|
||||
* executeCommand() is the package's own runner (homeproxy.uc); unlike
|
||||
* executeCommand() is the package's own runner (homeproxy-pro.uc); unlike
|
||||
* a bare system() call it returns the exit status and the captured
|
||||
* stderr, so a failed reload is recorded instead of disappearing. */
|
||||
const reload = executeCommand('/etc/init.d/homeproxy', 'reload');
|
||||
const reload = executeCommand('/etc/init.d/homeproxy-pro', 'reload');
|
||||
if (reload.exitcode !== 0)
|
||||
log(sprintf('Warning: reload exited with status %d: %s',
|
||||
reload.exitcode, trim(reload.stderr || '')));
|
||||
@@ -420,7 +420,7 @@ else {
|
||||
if (config_backup != null) {
|
||||
/* Write the snapshot beside the config and rename it into place.
|
||||
* A bare writefile() truncates first, so a crash or a full disk
|
||||
* part-way through the restore leaves /etc/config/homeproxy
|
||||
* part-way through the restore leaves /etc/config/homeproxy-pro
|
||||
* partial or empty - losing the very configuration this is
|
||||
* meant to protect. rename() is atomic within a directory. */
|
||||
const restore_tmp = CONFIG_FILE + '.hp-restore';
|
||||
+6
-6
@@ -5,7 +5,7 @@
|
||||
#
|
||||
# This file is the lifecycle orchestrator only. Everything
|
||||
# that is not "read the configuration, decide, and drive procd" now lives in
|
||||
# /etc/homeproxy/scripts/runtime/:
|
||||
# /etc/homeproxy-pro/scripts/runtime/:
|
||||
#
|
||||
# config.sh generated-configuration transaction (known-good, restore,
|
||||
# rollback) - pure, takes explicit paths
|
||||
@@ -57,12 +57,12 @@ USE_PROCD=1
|
||||
START=99
|
||||
STOP=10
|
||||
|
||||
CONF="homeproxy"
|
||||
CONF="homeproxy-pro"
|
||||
PROG="/usr/bin/sing-box"
|
||||
|
||||
HP_DIR="/etc/homeproxy"
|
||||
RUN_DIR="/var/run/homeproxy"
|
||||
LOG_PATH="$RUN_DIR/homeproxy.log"
|
||||
HP_DIR="/etc/homeproxy-pro"
|
||||
RUN_DIR="/var/run/homeproxy-pro"
|
||||
LOG_PATH="$RUN_DIR/homeproxy-pro.log"
|
||||
|
||||
# Last configuration that was proven to run. start_service records it only
|
||||
# after the health gate passed, and reload_service restores it when a new
|
||||
@@ -117,7 +117,7 @@ hp_dnsmasq_resolve_dir
|
||||
# points at the snippets. Both live in tmpfs, so after a reboot there is
|
||||
# nothing stale to release.
|
||||
hp_intercept_installed() {
|
||||
[ -s "$RUN_DIR/fw4_post.nft" ] || [ -e "$DNSMASQ_DIR/../dnsmasq-homeproxy.conf" ]
|
||||
[ -s "$RUN_DIR/fw4_post.nft" ] || [ -e "$DNSMASQ_DIR/../dnsmasq-homeproxy-pro.conf" ]
|
||||
}
|
||||
|
||||
# hp_release_intercept
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ -f "/www/luci-static/resources/icons/loading.gif" ] && \
|
||||
sed -i "s,/loading.svg,/loading.gif,g" "/www/luci-static/resources/view/homeproxy/status.js"
|
||||
sed -i "s,/loading.svg,/loading.gif,g" "/www/luci-static/resources/view/homeproxy-pro/status.js"
|
||||
|
||||
uci -q batch <<-EOF >"/dev/null"
|
||||
delete firewall.homeproxy_pre
|
||||
@@ -9,21 +9,21 @@ uci -q batch <<-EOF >"/dev/null"
|
||||
delete firewall.homeproxy_forward
|
||||
set firewall.homeproxy_forward=include
|
||||
set firewall.homeproxy_forward.type=nftables
|
||||
set firewall.homeproxy_forward.path="/var/run/homeproxy/fw4_forward.nft"
|
||||
set firewall.homeproxy_forward.path="/var/run/homeproxy-pro/fw4_forward.nft"
|
||||
set firewall.homeproxy_forward.position="chain-pre"
|
||||
set firewall.homeproxy_forward.chain="forward"
|
||||
|
||||
delete firewall.homeproxy_input
|
||||
set firewall.homeproxy_input=include
|
||||
set firewall.homeproxy_input.type=nftables
|
||||
set firewall.homeproxy_input.path="/var/run/homeproxy/fw4_input.nft"
|
||||
set firewall.homeproxy_input.path="/var/run/homeproxy-pro/fw4_input.nft"
|
||||
set firewall.homeproxy_input.position="chain-pre"
|
||||
set firewall.homeproxy_input.chain="input"
|
||||
|
||||
delete firewall.homeproxy_post
|
||||
set firewall.homeproxy_post=include
|
||||
set firewall.homeproxy_post.type=nftables
|
||||
set firewall.homeproxy_post.path="/var/run/homeproxy/fw4_post.nft"
|
||||
set firewall.homeproxy_post.path="/var/run/homeproxy-pro/fw4_post.nft"
|
||||
set firewall.homeproxy_post.position="table-post"
|
||||
commit firewall
|
||||
EOF
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
ucode "/etc/homeproxy-pro/scripts/migrate_config.uc"
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"admin/services/homeproxy-pro": {
|
||||
"title": "HomeProxy Pro",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-homeproxy-pro" ],
|
||||
"uci": { "homeproxy-pro": true }
|
||||
}
|
||||
},
|
||||
"admin/services/homeproxy-pro/client": {
|
||||
"title": "Client Settings",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "homeproxy-pro/client"
|
||||
}
|
||||
},
|
||||
"admin/services/homeproxy-pro/node": {
|
||||
"title": "Node Settings",
|
||||
"order": 15,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "homeproxy-pro/node"
|
||||
}
|
||||
},
|
||||
"admin/services/homeproxy-pro/server": {
|
||||
"title": "Server Settings",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "homeproxy-pro/server"
|
||||
}
|
||||
},
|
||||
"admin/services/homeproxy-pro/status": {
|
||||
"title": "Service Status",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "homeproxy-pro/status"
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"luci-app-homeproxy": {
|
||||
"description": "Grant access to homeproxy configuration",
|
||||
"luci-app-homeproxy-pro": {
|
||||
"description": "Grant access to homeproxy-pro configuration",
|
||||
"read": {
|
||||
"file": {
|
||||
"/var/run/homeproxy/homeproxy.log": [ "read" ],
|
||||
"/var/run/homeproxy/sing-box-c.log": [ "read" ],
|
||||
"/var/run/homeproxy/sing-box-s.log": [ "read" ]
|
||||
"/var/run/homeproxy-pro/homeproxy-pro.log": [ "read" ],
|
||||
"/var/run/homeproxy-pro/sing-box-c.log": [ "read" ],
|
||||
"/var/run/homeproxy-pro/sing-box-s.log": [ "read" ]
|
||||
},
|
||||
"ubus": {
|
||||
"service": [ "list" ],
|
||||
"luci.homeproxy": [
|
||||
"luci.homeproxy-pro": [
|
||||
"acllist_read",
|
||||
"connection_check",
|
||||
"node_parse",
|
||||
@@ -18,7 +18,7 @@
|
||||
"update_subscriptions_status"
|
||||
]
|
||||
},
|
||||
"uci": [ "homeproxy" ]
|
||||
"uci": [ "homeproxy-pro" ]
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
@@ -28,7 +28,7 @@
|
||||
"/tmp/homeproxy_cert_client_ech_conf.tmp": [ "write" ]
|
||||
},
|
||||
"ubus": {
|
||||
"luci.homeproxy": [
|
||||
"luci.homeproxy-pro": [
|
||||
"acllist_write",
|
||||
"certificate_write",
|
||||
"log_clean",
|
||||
@@ -37,7 +37,7 @@
|
||||
"update_subscriptions"
|
||||
]
|
||||
},
|
||||
"uci": [ "homeproxy" ]
|
||||
"uci": [ "homeproxy-pro" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -9,8 +9,8 @@
|
||||
|
||||
import { access, error, lstat, popen, readfile, writefile } from 'fs';
|
||||
|
||||
import { isBinary, isValidECHConfig, isValidPEM, shellQuote, executeCommand } from '/etc/homeproxy/scripts/homeproxy.uc';
|
||||
import { parse_uri } from '/etc/homeproxy/scripts/parser/uri.uc';
|
||||
import { isBinary, isValidECHConfig, isValidPEM, shellQuote, executeCommand } from '/etc/homeproxy-pro/scripts/homeproxy-pro.uc';
|
||||
import { parse_uri } from '/etc/homeproxy-pro/scripts/parser/uri.uc';
|
||||
|
||||
/* Kanged from ucode/luci - replaced by the imported shellQuote() so every shell
|
||||
* argument in this file goes through one helper. */
|
||||
@@ -18,9 +18,9 @@ function hasKernelModule(kmod) {
|
||||
return (system(sprintf('[ -e "/lib/modules/$(uname -r)"/%s ]', shellQuote(kmod))) === 0);
|
||||
}
|
||||
|
||||
const HP_DIR = '/etc/homeproxy';
|
||||
const RUN_DIR = '/var/run/homeproxy';
|
||||
const LOG_PATH = `${RUN_DIR}/homeproxy.log`;
|
||||
const HP_DIR = '/etc/homeproxy-pro';
|
||||
const RUN_DIR = '/var/run/homeproxy-pro';
|
||||
const LOG_PATH = `${RUN_DIR}/homeproxy-pro.log`;
|
||||
|
||||
/* Queried by singbox_get_features and by node_parse: the share-link parser
|
||||
* skips QUIC protocols and uTLS fingerprints the running sing-box cannot
|
||||
@@ -120,7 +120,7 @@ const methods = {
|
||||
* certificate button, which was a race: the second upload
|
||||
* landed on top of the first while the backend was still
|
||||
* reading it, and a slow upload could overwrite a faster
|
||||
* one. The frontend (homeproxy.js uploadCertificate)
|
||||
* one. The frontend (homeproxy-pro.js uploadCertificate)
|
||||
* derives the same path from the UCI option name, and
|
||||
* the ACL write list enumerates all four. */
|
||||
const tmpcert = `/tmp/homeproxy_cert_${filename}.tmp`;
|
||||
@@ -214,7 +214,7 @@ const methods = {
|
||||
log_clean: {
|
||||
args: { type: 'type' },
|
||||
call: function(req) {
|
||||
if (index(['homeproxy', 'sing-box-c', 'sing-box-s'], req.args?.type) === -1)
|
||||
if (index(['homeproxy-pro', 'sing-box-c', 'sing-box-s'], req.args?.type) === -1)
|
||||
return { result: false, error: 'illegal type' };
|
||||
|
||||
const filestat = lstat(`${RUN_DIR}/${req.args?.type}.log`);
|
||||
@@ -363,7 +363,7 @@ const methods = {
|
||||
/* Run the subscription updater once and report how it went.
|
||||
*
|
||||
* The LuCI button used fs.exec_direct() on
|
||||
* /etc/homeproxy/scripts/update_subscriptions.uc, which is the only
|
||||
* /etc/homeproxy-pro/scripts/update_subscriptions.uc, which is the only
|
||||
* reason the package ACL had to grant `file exec` on a root-owned
|
||||
* entry point: the browser session was authorised to execute that
|
||||
* path. Through one named ubus method the session is granted the
|
||||
@@ -372,7 +372,7 @@ const methods = {
|
||||
*
|
||||
* The previous synchronous shape returned the orchestrator's exit
|
||||
* status and captured stderr, but the full pipeline (wget fetch
|
||||
* through a CN -> overseas link, UCI commit, /etc/init.d/homeproxy
|
||||
* through a CN -> overseas link, UCI commit, /etc/init.d/homeproxy-pro
|
||||
* reload, the 30 s sing-box health gate) regularly took 10-15 s,
|
||||
* which exceeded the browser XHR timeout on every browser we tried.
|
||||
* The UI button then surfaced "XHR request timed out" while the
|
||||
@@ -408,7 +408,7 @@ const methods = {
|
||||
update_subscriptions_status: {
|
||||
/* Status companion to update_subscriptions. Reports whether the
|
||||
* updater is still running (lock file present) and the last ~12
|
||||
* lines of the homeproxy log so the page can show what the run
|
||||
* lines of the homeproxy-pro log so the page can show what the run
|
||||
* said without re-reading everything. The log is world-readable
|
||||
* and is rotated by runtime/service.sh -> log-cleaner.sh.
|
||||
*
|
||||
@@ -439,4 +439,4 @@ const methods = {
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.homeproxy': methods };
|
||||
return { 'luci.homeproxy-pro': methods };
|
||||
+22
-22
@@ -1,4 +1,4 @@
|
||||
# luci-app-homeproxy tests
|
||||
# luci-app-homeproxy-pro tests
|
||||
|
||||
Automated checks for the pieces that `sing-box check` cannot validate: the
|
||||
share-link parsers, the UCI→JSON generators and the LuCI form definitions.
|
||||
@@ -66,7 +66,7 @@ package and never runs `apk` or `opkg` on the target. That is a rule, not a
|
||||
preference.
|
||||
|
||||
Installing on a live device runs the package manager, which rewrites
|
||||
`/etc/config/homeproxy` from the feed package — and on 2026-09-15 that destroyed
|
||||
`/etc/config/homeproxy-pro` from the feed package — and on 2026-09-15 that destroyed
|
||||
the test machine's node configuration. There was no backup and no snapshot, and
|
||||
six nodes plus the `dns`, `server` and `subscription` sections were
|
||||
unrecoverable. Staging cannot do that: everything it writes lives under
|
||||
@@ -78,7 +78,7 @@ reports both versions before staging:
|
||||
```
|
||||
== target and source versions (staging; the package is not installed) ==
|
||||
source : 28.9.1.14-r1
|
||||
target : luci-app-homeproxy-26.236.50544~cb5d434
|
||||
target : luci-app-homeproxy-pro-26.236.50544~cb5d434
|
||||
sing-box: 1.14.1
|
||||
```
|
||||
|
||||
@@ -114,9 +114,9 @@ the workflow print which value was used, so a fw4 layer that reported `NOT RUN`
|
||||
is visible rather than inferred.
|
||||
|
||||
Installing the package and reloading the router is **not** part of this command.
|
||||
If a change needs that (anything in `/etc/init.d/homeproxy`,
|
||||
If a change needs that (anything in `/etc/init.d/homeproxy-pro`,
|
||||
`scripts/runtime/*.sh`, or the generation path), do it as a separate, deliberate
|
||||
step — and take `/etc/config/homeproxy` plus `/etc/homeproxy` aside first, with
|
||||
step — and take `/etc/config/homeproxy-pro` plus `/etc/homeproxy-pro` aside first, with
|
||||
the previous release's package kept for re-install. `apk` keeps a modified
|
||||
conffile and drops a `<file>.apk-new` next to it; check `md5sum` on the config
|
||||
before and after and delete the `.apk-new` files only once that matches.
|
||||
@@ -163,7 +163,7 @@ default), so dropping the prefix undoes it completely. It builds:
|
||||
| `libubox`, `libuci`, `libubus` | ucode's `uci`/`ubus`/`uloop` plugins need them |
|
||||
| `libmd` | the `digest` module (`ucode-mod-digest` in the package Makefile) |
|
||||
| `ucode` | the interpreter itself, plus `utpl`/`ucc`. Pinned to the revision ImmortalWrt/OpenWrt snapshots ship as `2026.01.16~85922056` — see "Why ucode is pinned" below |
|
||||
| `liblucihttp` | `luci.http` is a thin wrapper over this C module and `homeproxy.uc` imports it |
|
||||
| `liblucihttp` | `luci.http` is a thin wrapper over this C module and `homeproxy-pro.uc` imports it |
|
||||
| `luci` ucode sources | `http.uc`, `sys.uc`, … installed to `<prefix>/share/ucode/luci` like OpenWrt does |
|
||||
| `sing-box` | the official 1.14.0 release binary; the fixtures are validated with `sing-box check` and the package targets 1.14 |
|
||||
|
||||
@@ -222,8 +222,8 @@ reach a device:
|
||||
| Was skipped | Now |
|
||||
| --- | --- |
|
||||
| `scripts/update_subscriptions.uc` | compiled like every other source; `luci.sys` comes from the toolchain |
|
||||
| `scripts/firewall_pre.uc` | compiled; its `homeproxy` import resolves through `-L` |
|
||||
| `rpcd/ucode/luci.homeproxy` | compiled from a copy whose absolute `/etc/homeproxy/...` imports are rewritten to the checkout |
|
||||
| `scripts/firewall_pre.uc` | compiled; its `homeproxy-pro` import resolves through `-L` |
|
||||
| `rpcd/ucode/luci.homeproxy-pro` | compiled from a copy whose absolute `/etc/homeproxy-pro/...` imports are rewritten to the checkout |
|
||||
| `tests/ucode/test_firewall_template.sh` | runs; `utpl` is a symlink to `ucode` and ships with the toolchain |
|
||||
|
||||
A missing `utpl`, or any other gap in the toolchain, now FAILS instead of
|
||||
@@ -247,7 +247,7 @@ default can be turned off for a target that legitimately lacks fw4
|
||||
|
||||
Two further host differences are bridged so the remaining checks still run:
|
||||
|
||||
* **`/sbin/validate_data`** — `homeproxy.uc` shells out to this OpenWrt helper
|
||||
* **`/sbin/validate_data`** — `homeproxy-pro.uc` shells out to this OpenWrt helper
|
||||
for hostname/address/port validation. `tests/toolchain/validate-data.sh`
|
||||
reproduces the caller's contract (exit 0 = valid) using the same validation
|
||||
code as the parser unit tests; `tests/ucode/run.sh` exports it as
|
||||
@@ -266,16 +266,16 @@ Two further host differences are bridged so the remaining checks still run:
|
||||
|
||||
| Path | Checks |
|
||||
| --- | --- |
|
||||
| `tests/i18n-coverage.py` | Reports how many `po/templates/homeproxy.pot` strings have a non-fuzzy, non-empty `po/zh_Hans/homeproxy.po` translation. Technical tokens that stay as-is are listed in `tests/i18n-ignore.txt`; anything else missing fails the run (`--fail-below 100`) and adds a job-summary entry. The release workflow (`build.yml`) deliberately warns instead of failing, so a translation gap cannot block packaging - the PR gates already catch it. |
|
||||
| `tests/i18n-coverage.py` | Reports how many `po/templates/homeproxy-pro.pot` strings have a non-fuzzy, non-empty `po/zh_Hans/homeproxy-pro.po` translation. Technical tokens that stay as-is are listed in `tests/i18n-ignore.txt`; anything else missing fails the run (`--fail-below 100`) and adds a job-summary entry. The release workflow (`build.yml`) deliberately warns instead of failing, so a translation gap cannot block packaging - the PR gates already catch it. |
|
||||
| `tests/luci-form-snapshot.js` | Dumps every option (name, kind, title, description, depends, values, datatype/default/…) of the node, client and server views. Diffs against `tests/snapshots/{node,client,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_resource_blob_sha.sh` | `resource_blob_sha.uc`, the digest the resource updater verifies downloads with. The expected values are git's own (`git hash-object` on the same bytes), covering the two cases an implementation gets wrong quietly: the empty file (a real blob id, not "no digest") and content whose byte length differs from its character count. Unreadable input must produce *no* digest and exit non-zero - the updater treats an empty answer as "cannot verify" and refuses, so a made-up digest would turn an unreadable file into a passing check. Re-checks the repository's real resource files against git when the host has it. |
|
||||
| `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_homeproxy_utils_inject.uc` | The failure path of `executeCommand()`: the script stages a copy of `homeproxy-pro.uc` whose `system()` call is replaced by `die()`, then checks that the exception still propagates and that neither descriptor leaks. |
|
||||
| `tests/ucode/test_tls_transport.uc` | Direct calls into `buildTLSObject()` / `buildTransportObject()`: client-vs-server-only fields (no `insecure` on a server inbound, no `utls` on a server, `public_key` only on the client and `private_key` only on the server for reality), the ECH client/server split, and the `validateHomeProxyPath()` gate on `cert_path` / `key_path`. This is the boundary the generator fixtures only exercised end-to-end, so it is where the "server emitted insecure=true" and "reality key on the wrong side" classes of bug get named. |
|
||||
| `tests/ucode/test_subscription_fetcher.uc` | `subscription/fetcher.uc` against a shimmed `wGETVerbose`: empty content logs a **redacted** URL and returns `{content:null,error}`; non-empty content returns the body and logs nothing. The redaction assertions are what stop the subscription token from reappearing in `homeproxy.log`: the userinfo, the **path** (providers hand out `https://host:port/<user>/<token>` with no query string) and the query string are all gone, while the host and port survive so the message still says which endpoint failed. The mocks carry verbatim copies of `redactUrl()`, and arch-guard 35 fails when one of them drifts from `homeproxy.uc`. |
|
||||
| `tests/ucode/test_subscription_fetcher.uc` | `subscription/fetcher.uc` against a shimmed `wGETVerbose`: empty content logs a **redacted** URL and returns `{content:null,error}`; non-empty content returns the body and logs nothing. The redaction assertions are what stop the subscription token from reappearing in `homeproxy-pro.log`: the userinfo, the **path** (providers hand out `https://host:port/<user>/<token>` with no query string) and the query string are all gone, while the host and port survive so the message still says which endpoint failed. The mocks carry verbatim copies of `redactUrl()`, and arch-guard 35 fails when one of them drifts from `homeproxy-pro.uc`. |
|
||||
| `tests/ucode/test_migrate_config.sh` | Runs `migrate_config.uc` (36 checks) against a sandboxed UCI file that needs every migration step it can see at once — the 1.14 DNS renames, the legacy `dns_server.address` split, `rcode://` → predefined rule, `rule_set_ipcidr_match_source` rename, `block-out`/`block-dns` → `action='reject'`, `auto_firewall` redistribution, and the `block-dns` default-server replacement. The staged copy's cursor is redirected at the sandbox; the runner aborts if that rewrite stops matching rather than letting the migration touch the live `/etc/config`. |
|
||||
| `tests/ucode/test_firewall_pre.sh` | Behaviour tests for `firewall_pre.uc` (8 scenarios, one process each because the script has no exports): the tun accept pair, the per-server accept rules, the explicit-network narrowing, and — most importantly — that an invalid port or network skips the server with a WARN instead of interpolating garbage into an nft statement, which would make nft reject the entire ruleset. |
|
||||
| `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_fw4_names.sh` | Keeps the fw4 chain/set inventory in `scripts/fw4_names.sh` (used by `init.d/homeproxy-pro` 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_parser_normalize.uc` | Asserts that `parser/normalize.uc` correctly maps the parser's flat UCI-key output to the canonical Node shape (`tls`, `transport`, `multiplex`, `common`, `credentials`, `protocol_options`); pins the contract between the parser, the Loader's `PROTOCOL_OPTIONS` (now derived from `parser/mapping.uc`) and the Adapter. |
|
||||
| `tests/ucode/test_parser_flatten.uc` | Round-trip invariant for the canonical-Node pipeline: `flatten(normalize(parse_uri(uri)))` must equal the parser's flat UCI dict field-for-field for every supported scheme. This is what stops the parser, the Loader's `PROTOCOL_OPTIONS` (now derived from `parser/mapping.uc`) and the Repository's flatten from drifting apart. |
|
||||
@@ -289,16 +289,16 @@ Two further host differences are bridged so the remaining checks still run:
|
||||
| `tests/ucode/test_golden_inbounds.sh` | PR-04: the server-side counterpart. Builds one inbound per protocol from `tests/fixtures/generators/server.uci` and diffs the result against `tests/snapshots/generator/inbounds.json`. The ACME `data_directory` is normalised to `<HP_DIR>/certs` so the snapshot is portable across hosts. Before this existed the server path had no snapshot at all, which is how the fixture's unused `listen_port` option survived. Regenerate with `HP_UPDATE_SNAPSHOTS=1`. |
|
||||
| `tests/ucode/test_inbound_adapter.uc` | PR-04: `InboundFactory`'s protocol-shape decisions — snell / shadowsocks must get no `users[]` block (a snell users entry is read as an extra user key and makes sing-box reject the section), vless / vmess keep `flow` / `alterId` per-user, the snell listener set omits `udp_fragment` / `udp_timeout` / `network`, the server-only TLS tail (key material, ECH key, REALITY private key + handshake, ACME) reaches `buildTLSObject()`, hysteria v1 emits `obfs` as a string while hysteria2 emits the object, and the per-protocol credential requirements are enforced. |
|
||||
| `tests/ucode/test_protocol_inventory.sh` | Cross-checks the protocol surface: every type named by `parse_uri.uc`, `CREDENTIALS`, `PROTOCOL_OPTIONS`, `REQUIRED_CREDENTIALS`, `OPTION_FIELDS` and the golden snapshot must agree. This is the check that catches "added a protocol to one table and forgot another". |
|
||||
| `tests/frontend-protocol-inventory.js` | The frontend's single ordered protocol table (`homeproxy.js`'s `protocols`) against the backend tables that decide what the generators can build: every offered type must be modelled (`PROTOCOL_TO_UCI` on the client side, `INBOUND_CREDENTIALS` on the server side), every outbound-capable protocol (`OPTION_FIELDS`, `REQUIRED_CREDENTIALS`) must be selectable in the node form, both rendered orders are pinned, and protocols the backend models but no form offers must be listed as deliberate decisions. This is the check that would have caught `snell`: it had a form block, a credentials row and a golden outbound, but was missing from the node form's value list so it could not be selected. Node-only, no ucode needed. |
|
||||
| `tests/frontend-rpc-inventory.js` | The frontend RPC boundary: exactly one `rpc.declare` site (inside `homeproxy.js`'s `rpcCall`), no view wrapping a call in `L.resolveDefault`, `rpcCall` itself catching and falling back and reporting, and no view left with an unused `require rpc`. Source-level on purpose - the behaviour it protects needs a browser, but "nobody reintroduces a second declaration site" does not. Comments are stripped before matching, because a naive grep matches the doc comment that quotes the old idiom. |
|
||||
| `tests/frontend-protocol-inventory.js` | The frontend's single ordered protocol table (`homeproxy-pro.js`'s `protocols`) against the backend tables that decide what the generators can build: every offered type must be modelled (`PROTOCOL_TO_UCI` on the client side, `INBOUND_CREDENTIALS` on the server side), every outbound-capable protocol (`OPTION_FIELDS`, `REQUIRED_CREDENTIALS`) must be selectable in the node form, both rendered orders are pinned, and protocols the backend models but no form offers must be listed as deliberate decisions. This is the check that would have caught `snell`: it had a form block, a credentials row and a golden outbound, but was missing from the node form's value list so it could not be selected. Node-only, no ucode needed. |
|
||||
| `tests/frontend-rpc-inventory.js` | The frontend RPC boundary: exactly one `rpc.declare` site (inside `homeproxy-pro.js`'s `rpcCall`), no view wrapping a call in `L.resolveDefault`, `rpcCall` itself catching and falling back and reporting, and no view left with an unused `require rpc`. Source-level on purpose - the behaviour it protects needs a browser, but "nobody reintroduces a second declaration site" does not. Comments are stripped before matching, because a naive grep matches the doc comment that quotes the old idiom. |
|
||||
| `tests/frontend-validators.js` | Drives the shared form validators with a fake form context. The snapshots cannot cover these: a `validate` callback is a function, and the dump skips function-valued properties. Sharing the password validator between the node and server forms added the 2022-blake3 key-length check to the node form, which the server form had and the node form did not - a client could save a key that makes the generated configuration fail to decode. Reverse-proved: deleting that branch fails six checks. |
|
||||
| `tests/lib/luci-module.js` | The off-target LuCI module loader (`'require x as y';` rewritten into a dependency lookup), shared by the snapshot renderer and the frontend invariant tests. Its default `_()` returns a LuCI String object with `.format`, because the form code calls both halves. |
|
||||
| `tests/runtime/test_config_transaction.sh` | The `runtime/` helpers `init.d/homeproxy` leans on: the known-good copy, the fallback when generation produced nothing, the rollback, and the health probe. Pure shell, runs anywhere. |
|
||||
| `tests/runtime/test_config_transaction.sh` | The `runtime/` helpers `init.d/homeproxy-pro` leans on: the known-good copy, the fallback when generation produced nothing, the rollback, and the health probe. Pure shell, runs anywhere. |
|
||||
| `tests/runtime/test_dns_snippets.sh` | The dnsmasq snippet writer's incremental behaviour (review L6). Counts `init.d/dnsmasq restart` calls through a stub and asserts that an identical second write does *not* restart (a restart flushes every client's DNS cache), while a changed resource list, routing mode, DNS port or `ipv6_support` setting does. Also pins that a mode change drops the snippet the previous mode produced, and that removing a resource list removes its snippet so the next call is stable again. Pure shell, runs anywhere. |
|
||||
| `tests/runtime/test_health_probe.sh` | The functional health probes (`runtime/health.sh`): `hp_probe_dns` / `hp_probe_tcp` answer 0 / 1 / 2 and keep "cannot probe here" (no tool, no port) distinct from "the probe failed"; `hp_run_probes` *reports* by default and only fails the gate when `health_probe_strict` is on, and never fails it for a probe that could not run; `hp_config_port_for_tag` reads the probe ports out of the running configuration in both the generator's pretty shape and the compact one. `nslookup` and `nc` are stubbed, so it is pure shell and runs anywhere. |
|
||||
| `tests/runtime/test_crontab_perms.sh` | `hp_crontab_drop()` (runtime/service.sh) edits `/etc/crontabs/root` through a temporary file, and `mv` replaces the file rather than its contents - so the new one inherited the shell's umask (0644) while procd creates the root crontab as 0600. The test drives the function against a sandboxed crontab and pins: the entry is dropped, other jobs survive, the mode is 0600 (including a file an older version had already loosened), a missing crontab is reported as a failure and is not created. Pure shell, runs anywhere. |
|
||||
| `tests/runtime/test_resource_update.sh` | The content-verification path of `scripts/update_resources.sh`: the download URL is pinned to a commit, and the file is installed only when its git blob id matches the one GitHub reports for that path and commit. Drives all four outcomes (match, mismatch, no digest from the API, no local digest) with `wget` / `jsonfilter` / `ucode` / `uci` / `flock` stubbed and the script's absolute paths rewritten into a sandbox, asserting in each failure case that the installed list and its `.ver` are left untouched - the point of the check is that it refuses, so a driver that only walked the happy path would keep passing with the check deleted. Pure shell, runs anywhere. |
|
||||
| `tests/runtime/test_runtime_extraction.sh` | Drives `init.d/homeproxy` through a stubbed environment (fake `ip` / `nft` / `fw4` / `ucode` / `sing-box` / `uci` / `netstat` / `nslookup` / `nc`, fake procd and jsonfilter state, fixture UCI) across five scenarios and diffs the resulting command + file trace against a baseline. Two baselines exist and are captured with the SAME harness, so the diff between them is exactly the intentional change: `tests/fixtures/runtime/trace.pre-pr05.txt` (the 517-line init script before PHASE 7 moved the plumbing out — the record that the extraction was behaviour-preserving) and `tests/fixtures/runtime/trace.golden.txt` (after the health-gate fix). Scenario D is the P0 regression: a candidate whose `mixed_port` is already taken must be rejected by the health gate, the previous known-good must survive, and the reload must roll back onto it. The generated fixture configuration is pretty-printed like the generator's own `%.J` output, because the runtime reads that file (`hp_config_ports`, `hp_config_port_for_tag`) and a compact fixture would test a shape production never writes. Pure shell, no ucode needed. Regenerate with `HP_UPDATE_GOLDEN=1` (optionally `HP_GOLDEN=` / `HP_INITD=` to target another baseline or revision). |
|
||||
| `tests/runtime/test_runtime_extraction.sh` | Drives `init.d/homeproxy-pro` through a stubbed environment (fake `ip` / `nft` / `fw4` / `ucode` / `sing-box` / `uci` / `netstat` / `nslookup` / `nc`, fake procd and jsonfilter state, fixture UCI) across five scenarios and diffs the resulting command + file trace against a baseline. Two baselines exist and are captured with the SAME harness, so the diff between them is exactly the intentional change: `tests/fixtures/runtime/trace.pre-pr05.txt` (the 517-line init script before PHASE 7 moved the plumbing out — the record that the extraction was behaviour-preserving) and `tests/fixtures/runtime/trace.golden.txt` (after the health-gate fix). Scenario D is the P0 regression: a candidate whose `mixed_port` is already taken must be rejected by the health gate, the previous known-good must survive, and the reload must roll back onto it. The generated fixture configuration is pretty-printed like the generator's own `%.J` output, because the runtime reads that file (`hp_config_ports`, `hp_config_port_for_tag`) and a compact fixture would test a shape production never writes. Pure shell, no ucode needed. Regenerate with `HP_UPDATE_GOLDEN=1` (optionally `HP_GOLDEN=` / `HP_INITD=` to target another baseline or revision). |
|
||||
|
||||
**`HP_UPDATE_GOLDEN=1` is a baseline-update mode, not a test.** It copies the
|
||||
trace the run just produced over the baseline and exits 0 *before* the golden
|
||||
@@ -340,7 +340,7 @@ What the snapshots still cannot tell you: whether the form is *usable*. They
|
||||
prove a change to the option tree is deliberate and reviewable; they say nothing
|
||||
about whether the resulting page works in a browser.
|
||||
|
||||
`tests/ucode/mocks/homeproxy.uc` is a test double for the real module: only
|
||||
`tests/ucode/mocks/homeproxy-pro.uc` is a test double for the real module: only
|
||||
`validation()` is stubbed (the real one runs `/sbin/validate_data`, which does
|
||||
not exist outside OpenWrt), everything else is a copy of the production code.
|
||||
|
||||
@@ -368,10 +368,10 @@ harness provides:
|
||||
| Path | Why |
|
||||
| --- | --- |
|
||||
| `scripts/update_resources.sh` | The version query and the download are a GitHub API round-trip plus a jsdelivr fetch, so the verification path is driven by `tests/runtime/test_resource_update.sh` with `wget` / `jsonfilter` / `ucode` / `uci` / `flock` stubbed and the script's absolute paths rewritten into a sandbox: matching digest installs, a mismatch or a missing digest refuses and leaves the installed list and its `.ver` alone. The `*)` usage branch is covered by the shell syntax check. |
|
||||
| `scripts/update_crond.sh` | A fixed list of invocations against hard-coded `/etc/homeproxy/scripts` paths. Covered by the shell syntax check. |
|
||||
| `scripts/update_crond.sh` | A fixed list of invocations against hard-coded `/etc/homeproxy-pro/scripts` paths. Covered by the shell syntax check. |
|
||||
| `scripts/clean_log.sh` | `while true; do sleep 180; …` — testing the rotation needs the loop made injectable first. Covered by the shell syntax check. |
|
||||
| `htdocs/.../view/homeproxy/status.js` | A LuCI view; its behaviour is only observable in a browser. |
|
||||
| `init.d/homeproxy` | procd semantics need a real procd, and `service_started` only behaves correctly when procd really starts the instances — the offline harness models that, but it is a model. PR-05 shrank the file from 517 to 253 lines by moving the dnsmasq / fw4 / tproxy-TUN / service-plumbing into `scripts/runtime/{service,dns,firewall,net}.sh`; the trace test proves the move kept the command-and-file behaviour identical, and the shell syntax check covers every file. The health gate itself was verified on the ImmortalWrt test machine (see the plan's 2.14.8). |
|
||||
| `htdocs/.../view/homeproxy-pro/status.js` | A LuCI view; its behaviour is only observable in a browser. |
|
||||
| `init.d/homeproxy-pro` | procd semantics need a real procd, and `service_started` only behaves correctly when procd really starts the instances — the offline harness models that, but it is a model. PR-05 shrank the file from 517 to 253 lines by moving the dnsmasq / fw4 / tproxy-TUN / service-plumbing into `scripts/runtime/{service,dns,firewall,net}.sh`; the trace test proves the move kept the command-and-file behaviour identical, and the shell syntax check covers every file. The health gate itself was verified on the ImmortalWrt test machine (see the plan's 2.14.8). |
|
||||
|
||||
## Architecture regression coverage
|
||||
|
||||
@@ -388,7 +388,7 @@ relied on. Two tests replaced it:
|
||||
the loader option table, the adapter tables and the golden snapshot all name
|
||||
the same set of protocols.
|
||||
|
||||
`root/etc/init.d/homeproxy` is exercised for what can be checked off-target:
|
||||
`root/etc/init.d/homeproxy-pro` is exercised for what can be checked off-target:
|
||||
`tests/ucode/run.sh` syntax-checks it and the `runtime/` helpers,
|
||||
`tests/runtime/test_config_transaction.sh` covers the transaction semantics, and
|
||||
`tests/runtime/test_runtime_extraction.sh` pins the orchestration order
|
||||
+67
-67
@@ -8,7 +8,7 @@
|
||||
# because a real defect slipped through the whole suite:
|
||||
#
|
||||
# guard 1 the generators read the UCI configuration through
|
||||
# HP_DIR + '/config', i.e. /etc/homeproxy/config/homeproxy - a path
|
||||
# HP_DIR + '/config', i.e. /etc/homeproxy-pro/config/homeproxy-pro - a path
|
||||
# that exists nowhere. uci.load() returned null, every uci.get()
|
||||
# returned null, and the Loader silently produced pure defaults, so
|
||||
# every generated config lacked the main-out, the route/dns finals
|
||||
@@ -35,9 +35,9 @@
|
||||
set -u
|
||||
|
||||
ROOT="$(cd "${1:-$(dirname "$0")/..}" && pwd)"
|
||||
SCRIPTS="$ROOT/root/etc/homeproxy/scripts"
|
||||
RPC="$ROOT/root/usr/share/rpcd/ucode/luci.homeproxy"
|
||||
ACL="$ROOT/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json"
|
||||
SCRIPTS="$ROOT/root/etc/homeproxy-pro/scripts"
|
||||
RPC="$ROOT/root/usr/share/rpcd/ucode/luci.homeproxy-pro"
|
||||
ACL="$ROOT/root/usr/share/rpcd/acl.d/luci-app-homeproxy-pro.json"
|
||||
VIEWS="$ROOT/htdocs/luci-static/resources"
|
||||
RUNTIME="$SCRIPTS/runtime"
|
||||
|
||||
@@ -102,11 +102,11 @@ echo "== guard 1: the generators read the production UCI directory =="
|
||||
|
||||
# The confdir the Loader hands to cursor() must be /etc/config. Anything else
|
||||
# means uci.load() reads a file the package does not ship.
|
||||
if grep -q "^export const UCICONFIG_DIR = '/etc/config';$" "$SCRIPTS/homeproxy.uc"; then
|
||||
if grep -q "^export const UCICONFIG_DIR = '/etc/config';$" "$SCRIPTS/homeproxy-pro.uc"; then
|
||||
pass "UCICONFIG_DIR defaults to /etc/config"
|
||||
else
|
||||
fail "UCICONFIG_DIR does not default to /etc/config"
|
||||
grep -n "UCICONFIG_DIR" "$SCRIPTS/homeproxy.uc" | sed 's/^/ /'
|
||||
grep -n "UCICONFIG_DIR" "$SCRIPTS/homeproxy-pro.uc" | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
for g in generate_client.uc generate_server.uc; do
|
||||
@@ -119,7 +119,7 @@ for g in generate_client.uc generate_server.uc; do
|
||||
done
|
||||
|
||||
# The original mistake, in either spelling: deriving the UCI directory from
|
||||
# HP_DIR. HP_DIR is /etc/homeproxy and holds resources/scripts, not config.
|
||||
# HP_DIR. HP_DIR is /etc/homeproxy-pro and holds resources/scripts, not config.
|
||||
assert_empty "no source derives the UCI dir from HP_DIR" \
|
||||
grep -rn "Loader.load(HP_DIR\|Loader\.load( *HP_DIR" "$SCRIPTS"
|
||||
|
||||
@@ -291,7 +291,7 @@ fi
|
||||
ACL_METHODS="$(awk '
|
||||
/"ubus"[[:space:]]*:/ { inubus = 1; next }
|
||||
inubus && /^[[:space:]]*}/ { inubus = 0; next }
|
||||
inubus && /"luci.homeproxy"[[:space:]]*:/ { inlist = 1; next }
|
||||
inubus && /"luci.homeproxy-pro"[[:space:]]*:/ { inlist = 1; next }
|
||||
inlist && /\]/ { inlist = 0; next }
|
||||
inlist { print }
|
||||
' "$ACL" | grep -oE '"[a-z_]+"' | tr -d '"' | sort -u)"
|
||||
@@ -492,8 +492,8 @@ fi
|
||||
echo
|
||||
echo "== guard 10: nothing installs or removes packages on a target =="
|
||||
|
||||
# On 2026-09-15 an `apk add luci-app-homeproxy` on the test machine rewrote
|
||||
# /etc/config/homeproxy from the feed package and destroyed the node
|
||||
# On 2026-09-15 an `apk add luci-app-homeproxy-pro` on the test machine rewrote
|
||||
# /etc/config/homeproxy-pro from the feed package and destroyed the node
|
||||
# configuration - six nodes plus the dns, server and subscription sections, with
|
||||
# no backup and no snapshot. The suite stages instead, and this is the guard
|
||||
# that keeps it that way: a package-manager *write* is never part of testing.
|
||||
@@ -521,8 +521,8 @@ echo "== guard 11: firewall_post.ut validates every UCI-derived field =="
|
||||
# is an injection surface (closes a set expression, the whole fw4 reload
|
||||
# fails, the router loses its firewall). The defensive helpers exist; the
|
||||
# guard exists so they cannot quietly stop being used.
|
||||
TEMPLATE="$ROOT/root/etc/homeproxy/scripts/firewall_post.ut"
|
||||
UTILS="$ROOT/root/etc/homeproxy/scripts/firewall_utils.uc"
|
||||
TEMPLATE="$ROOT/root/etc/homeproxy-pro/scripts/firewall_post.ut"
|
||||
UTILS="$ROOT/root/etc/homeproxy-pro/scripts/firewall_utils.uc"
|
||||
|
||||
if [ -f "$UTILS" ]; then
|
||||
pass "firewall_utils.uc exists (where the H1 validators live)"
|
||||
@@ -611,12 +611,12 @@ echo "== guard 12: capabilities stay minimal =="
|
||||
# on a process that handles untrusted network traffic is gratuitous attack
|
||||
# surface. Neither is granted anywhere. inheritable is kept empty because
|
||||
# nothing the orchestrator spawns needs to inherit caps.
|
||||
CAPS="$ROOT/root/etc/capabilities/homeproxy.json"
|
||||
CAPS="$ROOT/root/etc/capabilities/homeproxy-pro.json"
|
||||
|
||||
if [ -f "$CAPS" ]; then
|
||||
pass "homeproxy.json exists"
|
||||
pass "homeproxy-pro.json exists"
|
||||
else
|
||||
fail "homeproxy.json is missing"
|
||||
fail "homeproxy-pro.json is missing"
|
||||
fi
|
||||
|
||||
for cap in CAP_SYS_PTRACE CAP_NET_RAW CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_MODULE CAP_SYS_RAWIO; do
|
||||
@@ -639,7 +639,7 @@ done
|
||||
#
|
||||
# So the invariant is "ambient is a subset of inheritable", not "inheritable is
|
||||
# empty". Verified on the test device: with an empty inheritable set
|
||||
# `homeproxy start` exits 1 and sing-box-c never runs; restoring the two
|
||||
# `homeproxy-pro start` exits 1 and sing-box-c never runs; restoring the two
|
||||
# entries makes start/reload/stop pass and the instances come up.
|
||||
AMBIENT="$(awk '/"ambient"/,/]/' "$CAPS" | grep -oE 'CAP_[A-Z_]+' | sort -u)"
|
||||
INHERITABLE="$(awk '/"inheritable"/,/]/' "$CAPS" | grep -oE 'CAP_[A-Z_]+' | sort -u | tr '\n' ' ')"
|
||||
@@ -736,11 +736,11 @@ echo "== guard 14: wGETVerbose redacts the URL at the source =="
|
||||
# silently leaked the subscription token. The fix moved redaction into
|
||||
# wGETVerbose itself, so the returned `error` is safe no matter where the
|
||||
# caller ships it.
|
||||
HOMEPROXY="$SCRIPTS/homeproxy.uc"
|
||||
HOMEPROXY="$SCRIPTS/homeproxy-pro.uc"
|
||||
FETCHER="$SCRIPTS/subscription/fetcher.uc"
|
||||
|
||||
if grep -qE '^export function redactReason\b' "$HOMEPROXY"; then
|
||||
pass "redactReason is exported from homeproxy.uc"
|
||||
pass "redactReason is exported from homeproxy-pro.uc"
|
||||
else
|
||||
fail "redactReason is missing - the H3 redaction has no entry point"
|
||||
fi
|
||||
@@ -785,7 +785,7 @@ echo "== guard 15: every RPC whitelist uses index() === -1 ===="
|
||||
# (double quotes) are caught, as is a membership test split over several lines.
|
||||
# Object key checks (`in {}`), `for ... in ...` over a non-literal and a
|
||||
# mention inside a comment are deliberately not matches.
|
||||
RPC="$ROOT/root/usr/share/rpcd/ucode/luci.homeproxy"
|
||||
RPC="$ROOT/root/usr/share/rpcd/ucode/luci.homeproxy-pro"
|
||||
|
||||
IN_ARR="$(python3 - "$RPC" <<'PY'
|
||||
import pathlib, re, sys
|
||||
@@ -817,9 +817,9 @@ PY
|
||||
if [ "$IN_ARR" = "__SCAN_FAILED__" ]; then
|
||||
fail "the 'in [...]' scan could not run - fix the guard before trusting a pass"
|
||||
elif [ -z "$IN_ARR" ]; then
|
||||
pass "no array-membership 'in [...]' remains in luci.homeproxy"
|
||||
pass "no array-membership 'in [...]' remains in luci.homeproxy-pro"
|
||||
else
|
||||
fail "array-membership 'in [...]' remains in luci.homeproxy - the report's M6 said unify on index():"
|
||||
fail "array-membership 'in [...]' remains in luci.homeproxy-pro - the report's M6 said unify on index():"
|
||||
printf ' %s\n' "$IN_ARR"
|
||||
fi
|
||||
|
||||
@@ -827,9 +827,9 @@ fi
|
||||
# subset checks). Asserted positively so the check cannot pass by finding
|
||||
# nothing (a regex in a comment would otherwise be enough).
|
||||
if grep -qE "index\(\[".*"\], req\.args\?\.type\) === -1" "$RPC"; then
|
||||
pass "luci.homeproxy uses index() === -1 for whitelist checks"
|
||||
pass "luci.homeproxy-pro uses index() === -1 for whitelist checks"
|
||||
else
|
||||
fail "luci.homeproxy no longer uses index() === -1 for whitelist checks - did someone reintroduce the in-style?"
|
||||
fail "luci.homeproxy-pro no longer uses index() === -1 for whitelist checks - did someone reintroduce the in-style?"
|
||||
fi
|
||||
|
||||
echo
|
||||
@@ -864,7 +864,7 @@ SHELL_CALLS="$(grep -rEn '(^|[^A-Za-z_])(system|popen)\(' \
|
||||
UNQUOTED="$(printf '%s\n' "$SHELL_CALLS" | python3 -c '
|
||||
import sys, re
|
||||
# Two styles reach the shell unquoted:
|
||||
# (a) template literal with `${someVar}` - luci.homeproxy
|
||||
# (a) template literal with `${someVar}` - luci.homeproxy-pro
|
||||
# (b) string concatenation `+ someVar +` - the generator scripts
|
||||
# A line is compliant if shellQuote() appears on it; both styles are
|
||||
# caught with one rule because the helper is the same either way.
|
||||
@@ -921,7 +921,7 @@ echo "== guard 17: resource update has multi-mirror fallback =="
|
||||
# for the common case (fastly is up) and useful in the failure case
|
||||
# (gcore, cdn, or raw.githubusercontent.com catches it). The list lives
|
||||
# in the script's MIRRORS variable; the order is part of the contract.
|
||||
UPDATE_SCRIPT="$ROOT/root/etc/homeproxy/scripts/update_resources.sh"
|
||||
UPDATE_SCRIPT="$ROOT/root/etc/homeproxy-pro/scripts/update_resources.sh"
|
||||
|
||||
for mirror in fastly.jsdelivr.net gcore.jsdelivr.net cdn.jsdelivr.net raw.githubusercontent.com; do
|
||||
if grep -qE "\b$mirror\b" "$UPDATE_SCRIPT"; then
|
||||
@@ -953,7 +953,7 @@ fi
|
||||
|
||||
# The RPC must return both version and updated_at. Asserted positively
|
||||
# so the guard cannot pass by finding nothing.
|
||||
LUCI="$ROOT/root/usr/share/rpcd/ucode/luci.homeproxy"
|
||||
LUCI="$ROOT/root/usr/share/rpcd/ucode/luci.homeproxy-pro"
|
||||
if grep -qE "resources_get_version" "$LUCI" && grep -qE "\.updated_at" "$LUCI"; then
|
||||
pass "resources_get_version returns updated_at"
|
||||
else
|
||||
@@ -962,7 +962,7 @@ fi
|
||||
|
||||
# The UI must read and display it. Without this, the RPC field is
|
||||
# unused and the user sees nothing new.
|
||||
STATUS_JS="$ROOT/htdocs/luci-static/resources/view/homeproxy/status.js"
|
||||
STATUS_JS="$ROOT/htdocs/luci-static/resources/view/homeproxy-pro/status.js"
|
||||
if grep -qE "res\.updated_at" "$STATUS_JS"; then
|
||||
pass "status.js renders the last-updated timestamp"
|
||||
else
|
||||
@@ -977,7 +977,7 @@ echo "== guard 19: acllist_write rejects bad lines with a line number =="
|
||||
# bisect by hand. The rejection now names the line, and the UI hint tells
|
||||
# the user what characters are banned. Pinned by the guard so the line
|
||||
# number cannot quietly regress.
|
||||
LUCI="$ROOT/root/usr/share/rpcd/ucode/luci.homeproxy"
|
||||
LUCI="$ROOT/root/usr/share/rpcd/ucode/luci.homeproxy-pro"
|
||||
ACLLIST_BODY="$(awk '/^[[:space:]]+acllist_write:/,/^[[:space:]]+},$/' "$LUCI")"
|
||||
|
||||
if printf '%s' "$ACLLIST_BODY" | grep -qE "line \\\$\\{i \\+ 1\\}"; then
|
||||
@@ -990,7 +990,7 @@ fi
|
||||
# description *before* trying to save. Both proxy_list and direct_list
|
||||
# textareas must carry the hint; assert positively so the guard cannot
|
||||
# pass by finding nothing.
|
||||
ACCESS="$ROOT/htdocs/luci-static/resources/view/homeproxy/client/access.js"
|
||||
ACCESS="$ROOT/htdocs/luci-static/resources/view/homeproxy-pro/client/access.js"
|
||||
PROXY_HINT="$(awk '/_proxy_domain_list/,/description\s*=/' "$ACCESS" \
|
||||
| grep -c 'One domain per line')"
|
||||
DIRECT_HINT="$(awk '/_direct_domain_list/,/description\s*=/' "$ACCESS" \
|
||||
@@ -1031,7 +1031,7 @@ echo "== guard 21: dnsmasq snippet writer is incremental =="
|
||||
# tests/runtime/test_dns_snippets.sh pins the behaviour from the outside;
|
||||
# this guard pins the two structural pieces that make it possible, so a
|
||||
# future edit cannot quietly drop back to unconditional rewrite.
|
||||
DNS_SH="$ROOT/root/etc/homeproxy/scripts/runtime/dns.sh"
|
||||
DNS_SH="$ROOT/root/etc/homeproxy-pro/scripts/runtime/dns.sh"
|
||||
|
||||
if grep -qE '^hp_dnsmasq_render_snippets\(\)' "$DNS_SH"; then
|
||||
pass "dns.sh has a side-effect-free snippet renderer"
|
||||
@@ -1085,11 +1085,11 @@ echo "== guard 22: client tab modules return a LuCI class =="
|
||||
# if (!Class.isSubclass(_class))
|
||||
# error('TypeError', '"%s" factory yields invalid constructor', name);
|
||||
#
|
||||
# The M5 split shipped six view.homeproxy.client.* modules returning a plain
|
||||
# The M5 split shipped six view.homeproxy-pro.client.* modules returning a plain
|
||||
# object (`return { render }`), so every tab threw and no browser could render
|
||||
# the client page. Nothing caught it: the modules parse, and the snapshot
|
||||
# harness has its own loader that happily accepts a plain object.
|
||||
for f in "$VIEWS"/view/homeproxy/client/*.js; do
|
||||
for f in "$VIEWS"/view/homeproxy-pro/client/*.js; do
|
||||
[ -f "$f" ] || continue
|
||||
if grep -qE '^return baseclass\.extend\(' "$f"; then
|
||||
pass "$(basename "$f") returns a LuCI class"
|
||||
@@ -1115,7 +1115,7 @@ echo "== guard 23: an hp method that reads \`this\` is bound to hp =="
|
||||
BOUND_WRONG="$(python3 - "$ROOT" <<'PY'
|
||||
import re, sys, pathlib
|
||||
root = pathlib.Path(sys.argv[1])
|
||||
hp = (root / 'htdocs/luci-static/resources/homeproxy.js').read_text(encoding='utf-8')
|
||||
hp = (root / 'htdocs/luci-static/resources/homeproxy-pro.js').read_text(encoding='utf-8')
|
||||
|
||||
# Top-level method -> does its body read `this`?
|
||||
uses_this, cur, body = set(), None, []
|
||||
@@ -1132,7 +1132,7 @@ for line in hp.split('\n'):
|
||||
|
||||
bad = []
|
||||
for f in sorted((root / 'htdocs').rglob('*.js')):
|
||||
if f.name == 'homeproxy.js':
|
||||
if f.name == 'homeproxy-pro.js':
|
||||
continue
|
||||
for n, line in enumerate(f.read_text(encoding='utf-8').split('\n'), 1):
|
||||
for meth, recv in re.findall(r'L\.bind\(hp\.([A-Za-z_$][\w$]*),\s*([A-Za-z_$][\w$]*)', line):
|
||||
@@ -1210,7 +1210,7 @@ echo "== guard 26: ucode sources carry no PCRE-only regex syntax =="
|
||||
# ucode's regex engine is not PCRE, and it does not reject the unsupported
|
||||
# construct at parse time: `(?:...)` compiles fine and then fails when the
|
||||
# literal is evaluated ("Repetition not preceded by valid expression"). That
|
||||
# is how r11 shipped a luci.homeproxy whose resources_get_version threw on
|
||||
# is how r11 shipped a luci.homeproxy-pro whose resources_get_version threw on
|
||||
# every call - the build, `ucode -c` and every syntax check stayed green while
|
||||
# the resources panel rendered "undefined". One grep keeps it out.
|
||||
PCRE_ONLY="$(grep -rn '(?:' "$ROOT/root" --include='*.uc' --include='*.ut' 2>"/dev/null" \
|
||||
@@ -1295,14 +1295,14 @@ echo
|
||||
echo "== guard 29: the certificate path policy is identical in both layers =="
|
||||
|
||||
# TLS certificate_path / key_path had two different policies: the LuCI form
|
||||
# offered /etc/homeproxy/certs/, /etc/acme/ and /etc/ssl/, while the backend
|
||||
# validator only knew /etc/homeproxy/ and /tmp/homeproxy_. A certificate the
|
||||
# offered /etc/homeproxy-pro/certs/, /etc/acme/ and /etc/ssl/, while the backend
|
||||
# validator only knew /etc/homeproxy-pro/ and /tmp/homeproxy_. A certificate the
|
||||
# user picked from /etc/ssl/ was therefore accepted by the UI, dropped by
|
||||
# buildTLSObject(), and the listener failed with nothing pointing at the path.
|
||||
# Both layers now read the same list - CERT_PATH_ROOTS in homeproxy.uc and
|
||||
# HP_CERT_PATH_ROOTS in homeproxy.js - and this guard keeps the two copies in
|
||||
# Both layers now read the same list - CERT_PATH_ROOTS in homeproxy-pro.uc and
|
||||
# HP_CERT_PATH_ROOTS in homeproxy-pro.js - and this guard keeps the two copies in
|
||||
# step, because nothing else can see across the JS/ucode boundary.
|
||||
CERT_ROOTS="$(python3 - "$SCRIPTS/homeproxy.uc" "$VIEWS/homeproxy.js" <<'PY'
|
||||
CERT_ROOTS="$(python3 - "$SCRIPTS/homeproxy-pro.uc" "$VIEWS/homeproxy-pro.js" <<'PY'
|
||||
import re, sys
|
||||
|
||||
def roots(path, pattern, label):
|
||||
@@ -1313,12 +1313,12 @@ def roots(path, pattern, label):
|
||||
return re.findall(r"'([^']+)'", m.group(1)), None
|
||||
|
||||
problems = []
|
||||
backend, err = roots(sys.argv[1], r'\bCERT_PATH_ROOTS\s*=\s*\[([^\]]*)\]', 'homeproxy.uc')
|
||||
backend, err = roots(sys.argv[1], r'\bCERT_PATH_ROOTS\s*=\s*\[([^\]]*)\]', 'homeproxy-pro.uc')
|
||||
problems += [err] if err else []
|
||||
frontend, err = roots(sys.argv[2], r'\bHP_CERT_PATH_ROOTS\s*=\s*\[([^\]]*)\]', 'homeproxy.js')
|
||||
frontend, err = roots(sys.argv[2], r'\bHP_CERT_PATH_ROOTS\s*=\s*\[([^\]]*)\]', 'homeproxy-pro.js')
|
||||
problems += [err] if err else []
|
||||
if not problems and backend != frontend:
|
||||
problems.append('homeproxy.uc %s != homeproxy.js %s' % (backend, frontend))
|
||||
problems.append('homeproxy-pro.uc %s != homeproxy-pro.js %s' % (backend, frontend))
|
||||
print('\n'.join(problems))
|
||||
PY
|
||||
)" || CERT_ROOTS="__SCAN_FAILED__"
|
||||
@@ -1335,7 +1335,7 @@ echo
|
||||
echo "== guard 30: the ACL grants no file execution =="
|
||||
|
||||
# The subscription button used fs.exec_direct() on
|
||||
# /etc/homeproxy/scripts/update_subscriptions.uc, and that is the only reason
|
||||
# /etc/homeproxy-pro/scripts/update_subscriptions.uc, and that is the only reason
|
||||
# the ACL carried `"exec"` for a root-owned path: a browser session authorised
|
||||
# to execute a file, rather than to call one named method. The updater is a ubus
|
||||
# method now (update_subscriptions), so no path in this ACL may be executable -
|
||||
@@ -1483,7 +1483,7 @@ echo "== guard 34: the bundled resource versions use the writers' format =="
|
||||
# shape is checked with case/parameter expansion rather than a grep interval,
|
||||
# which busybox grep may not support.
|
||||
RES_VER_BAD=""
|
||||
for f in "$ROOT"/root/etc/homeproxy/resources/*.ver; do
|
||||
for f in "$ROOT"/root/etc/homeproxy-pro/resources/*.ver; do
|
||||
[ -f "$f" ] || continue
|
||||
RES_VER="${f##*/}"
|
||||
RES_VER_VALUE="$(cat "$f" 2>"/dev/null")"
|
||||
@@ -1506,7 +1506,7 @@ echo
|
||||
echo "== guard 35: mock copies of redactUrl match the real one =="
|
||||
|
||||
# tests/ucode/mocks/*.uc carry verbatim copies of redactUrl() so the parser and
|
||||
# fetcher tests can run without homeproxy.uc's other dependencies. A copy is
|
||||
# fetcher tests can run without homeproxy-pro.uc's other dependencies. A copy is
|
||||
# only worth having while it is identical: when the real function started
|
||||
# masking the URL path (the subscription token is often the path), both mocks
|
||||
# had to be updated by hand and nothing failed when they were not - the fetcher
|
||||
@@ -1521,7 +1521,7 @@ extract_fn() {
|
||||
REAL_REDACT="$(extract_fn "$HOMEPROXY" redactUrl)"
|
||||
MOCK_DRIFT=""
|
||||
if [ -z "$REAL_REDACT" ]; then
|
||||
fail "redactUrl could not be extracted from homeproxy.uc"
|
||||
fail "redactUrl could not be extracted from homeproxy-pro.uc"
|
||||
else
|
||||
for m in "$ROOT"/tests/ucode/mocks/*.uc; do
|
||||
[ -f "$m" ] || continue
|
||||
@@ -1531,9 +1531,9 @@ else
|
||||
fi
|
||||
done
|
||||
if [ -n "$MOCK_DRIFT" ]; then
|
||||
fail "mock copies of redactUrl have drifted from homeproxy.uc:$MOCK_DRIFT"
|
||||
fail "mock copies of redactUrl have drifted from homeproxy-pro.uc:$MOCK_DRIFT"
|
||||
else
|
||||
pass "every mock's redactUrl is identical to homeproxy.uc's"
|
||||
pass "every mock's redactUrl is identical to homeproxy-pro.uc's"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1555,7 +1555,7 @@ echo "== guard 36: the certificate upload buttons pass the handler's arguments =
|
||||
# argument, or a bare value shifted into the wrong slot, changes the staging
|
||||
# path), and the handler must take exactly one parameter more than the call
|
||||
# passes - the event LuCI adds in front.
|
||||
CERT_HANDLER="$VIEWS/homeproxy.js"
|
||||
CERT_HANDLER="$VIEWS/homeproxy-pro.js"
|
||||
CERT_SITES=0
|
||||
CERT_BAD=""
|
||||
CERT_CALLS="$(mktemp)"
|
||||
@@ -1573,7 +1573,7 @@ elif [ "$CERT_NARGS" != "3" ]; then
|
||||
fail "uploadCertificate takes $CERT_NARGS parameters; the buttons pass the label and the filename, LuCI prepends the event, so it must take 3 (type, filename, ev)"
|
||||
fi
|
||||
|
||||
grep -hoE "L\.bind\(hp\.uploadCertificate[^;]*" "$VIEWS"/view/homeproxy/*.js > "$CERT_CALLS" 2>"/dev/null"
|
||||
grep -hoE "L\.bind\(hp\.uploadCertificate[^;]*" "$VIEWS"/view/homeproxy-pro/*.js > "$CERT_CALLS" 2>"/dev/null"
|
||||
|
||||
while IFS= read -r call; do
|
||||
[ -n "$call" ] || continue
|
||||
@@ -1765,7 +1765,7 @@ echo "== guard 39: sniffer_advanced_mode default stays at '0' =="
|
||||
# (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
|
||||
# package-shipped /etc/config/homeproxy must not bump the default to
|
||||
# package-shipped /etc/config/homeproxy-pro must not bump the default to
|
||||
# '1' on a whim. Two checks:
|
||||
#
|
||||
# (a) the generator's fallback is '0';
|
||||
@@ -1777,11 +1777,11 @@ else
|
||||
fail "sniff rule's 300ms / default-list profile is no longer the safe default"
|
||||
fi
|
||||
|
||||
# The package default lives in root/etc/config/homeproxy. It must be
|
||||
# The package default lives in root/etc/config/homeproxy-pro. It must be
|
||||
# '0' for upgrades to be invisible; new installs also default to the
|
||||
# same until the user opts in.
|
||||
if grep -q "^ option sniffer_advanced_mode '0'" "$ROOT/root/etc/config/homeproxy"; then
|
||||
pass "the package-shipped /etc/config/homeproxy keeps sniffer_advanced_mode '0'"
|
||||
if grep -q "^ option sniffer_advanced_mode '0'" "$ROOT/root/etc/config/homeproxy-pro"; then
|
||||
pass "the package-shipped /etc/config/homeproxy-pro keeps sniffer_advanced_mode '0'"
|
||||
else
|
||||
fail "the package default for sniffer_advanced_mode is no longer '0'; users who"
|
||||
fail "upgraded without touching the option would silently switch to 100ms +"
|
||||
@@ -1808,7 +1808,7 @@ echo "== guard 40: rule/routing rule action enum agrees across UI and generator
|
||||
ACTION_ENUM="$(python3 - "$ROOT" <<'PY' || ACTION_ENUM="__SCAN_FAILED__"
|
||||
import re, sys, pathlib
|
||||
root = pathlib.Path(sys.argv[1])
|
||||
common = (root / 'htdocs/luci-static/resources/view/homeproxy/client/common.js').read_text()
|
||||
common = (root / 'htdocs/luci-static/resources/view/homeproxy-pro/client/common.js').read_text()
|
||||
|
||||
# Find the action ListValue block: from "ss.taboption('field_other', form.ListValue, 'action'"
|
||||
# until 'so.default = 'route'' on the next non-conditional line. Within that
|
||||
@@ -1833,8 +1833,8 @@ def actions(branch):
|
||||
dns_actions = actions(is_dns_m.group(1))
|
||||
routing_actions = actions(is_dns_m.group(2))
|
||||
|
||||
dns_uc = (root / 'root/etc/homeproxy/scripts/generator/dns.uc').read_text()
|
||||
route_uc = (root / 'root/etc/homeproxy/scripts/generator/route.uc').read_text()
|
||||
dns_uc = (root / 'root/etc/homeproxy-pro/scripts/generator/dns.uc').read_text()
|
||||
route_uc = (root / 'root/etc/homeproxy-pro/scripts/generator/route.uc').read_text()
|
||||
|
||||
def gates(text):
|
||||
return set(re.findall(r"cfg\.action === '([^']+)'", text))
|
||||
@@ -1874,12 +1874,12 @@ echo "== guard 41: experimental.cache_file is unconditional in attachExperimenta
|
||||
# `if (routing_mode in ['bypass_mainland_china', 'custom'])`, leaving
|
||||
# gfwlist / proxy_mainland_china / global with cold-start DNS on every
|
||||
# reload. cache_file is a fresh-install no-op and the file is the only
|
||||
# piece of state the generator writes outside /etc/config/homeproxy, so
|
||||
# piece of state the generator writes outside /etc/config/homeproxy-pro, so
|
||||
# gating it on routing_mode buys nothing and lost DNS cache for 3 modes.
|
||||
# The fix removed the gate; this guard pins the unconditional emission so
|
||||
# a future refactor (e.g. adding a 'cache_file.enabled: 0' opt-out) does
|
||||
# not silently re-introduce the routing_mode gate.
|
||||
AE_RESULT="$(python3 - "$ROOT/root/etc/homeproxy/scripts/generator/common.uc" <<'PY' || echo __SCAN_FAILED__
|
||||
AE_RESULT="$(python3 - "$ROOT/root/etc/homeproxy-pro/scripts/generator/common.uc" <<'PY' || echo __SCAN_FAILED__
|
||||
import re, sys, pathlib
|
||||
src = pathlib.Path(sys.argv[1]).read_text()
|
||||
m = re.search(r'^export function attachExperimental\b.*?\n\};',
|
||||
@@ -1930,7 +1930,7 @@ echo "== guard 42: NAPTR_BYPASS_SUFFIXES is the only domain_suffix qtype-35 sour
|
||||
# pins the actual invariant: a constant declaration exists, AND the
|
||||
# qtype-35 DNS rule's domain_suffix field references that constant
|
||||
# (not a hand-rolled literal array, which would re-inline the list).
|
||||
DNS_UC="$ROOT/root/etc/homeproxy/scripts/generator/dns.uc"
|
||||
DNS_UC="$ROOT/root/etc/homeproxy-pro/scripts/generator/dns.uc"
|
||||
NAPTR_CONST="$(grep -nE '^(const|var)\s+NAPTR_BYPASS_SUFFIXES\s*=' "$DNS_UC" || true)"
|
||||
NAPTR_USE="$(grep -nE '^\s*domain_suffix:\s*NAPTR_BYPASS_SUFFIXES\b' "$DNS_UC" || true)"
|
||||
NAPTR_LITERAL_USE="$(grep -nE '^\s*domain_suffix:\s*\[' "$DNS_UC" || true)"
|
||||
@@ -1974,7 +1974,7 @@ echo "== guard 43: the rollback UCI snapshot matches the keys the intercept laye
|
||||
# case; the declared extras below are the only ones
|
||||
# allowed)
|
||||
#
|
||||
# init.d/homeproxy is deliberately NOT in the read set: its config_get calls
|
||||
# init.d/homeproxy-pro is deliberately NOT in the read set: its config_get calls
|
||||
# serve the health gate (health_probe_*) and the cron (subscription.*), which
|
||||
# do not steer the layer. Every key it reads that *does* steer the layer is
|
||||
# also read by firewall_post.ut, so nothing is lost by excluding it.
|
||||
@@ -1986,11 +1986,11 @@ echo "== guard 43: the rollback UCI snapshot matches the keys the intercept laye
|
||||
SNAP_TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$SNAP_TMP"' EXIT INT TERM
|
||||
|
||||
SNAP_POST="$ROOT/root/etc/homeproxy/scripts/firewall_post.ut"
|
||||
SNAP_PRE="$ROOT/root/etc/homeproxy/scripts/firewall_pre.uc"
|
||||
SNAP_NET="$ROOT/root/etc/homeproxy/scripts/runtime/net.sh"
|
||||
SNAP_DNS="$ROOT/root/etc/homeproxy/scripts/runtime/dns.sh"
|
||||
SNAP_SVC="$ROOT/root/etc/homeproxy/scripts/runtime/service.sh"
|
||||
SNAP_POST="$ROOT/root/etc/homeproxy-pro/scripts/firewall_post.ut"
|
||||
SNAP_PRE="$ROOT/root/etc/homeproxy-pro/scripts/firewall_pre.uc"
|
||||
SNAP_NET="$ROOT/root/etc/homeproxy-pro/scripts/runtime/net.sh"
|
||||
SNAP_DNS="$ROOT/root/etc/homeproxy-pro/scripts/runtime/dns.sh"
|
||||
SNAP_SVC="$ROOT/root/etc/homeproxy-pro/scripts/runtime/service.sh"
|
||||
|
||||
for f in "$SNAP_POST" "$SNAP_PRE" "$SNAP_NET" "$SNAP_DNS" "$SNAP_SVC"; do
|
||||
if [ ! -r "$f" ]; then
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
# protocol reachable through a single urltest main node, so one run exercises
|
||||
# all outbound builders (TLS, transport, reality, mux, obfuscation).
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option routing_mode 'bypass_mainland_china'
|
||||
option proxy_mode 'tun'
|
||||
option ipv6_support '0'
|
||||
@@ -23,7 +23,7 @@ config homeproxy 'config'
|
||||
list main_urltest_nodes 'n_socks'
|
||||
list main_urltest_nodes 'n_shadowtls'
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option mixed_port '5330'
|
||||
option dns_port '5333'
|
||||
option tun_name 'singtun0'
|
||||
+3
-3
@@ -12,17 +12,17 @@
|
||||
# __RULESET_DIR__ is substituted by tests/ucode/test_generators.sh with a
|
||||
# directory holding a real compiled .srs file.
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option routing_mode 'custom'
|
||||
option proxy_mode 'redirect_tproxy'
|
||||
option ipv6_support '0'
|
||||
option log_level 'error'
|
||||
|
||||
config homeproxy 'routing'
|
||||
config homeproxy-pro 'routing'
|
||||
option default_outbound 'direct-out'
|
||||
option default_outbound_dns 'default-dns'
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option mixed_port '5330'
|
||||
option dns_port '5333'
|
||||
option redirect_port '5331'
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
# and the service never started. `sing-box check` accepts such a config, so
|
||||
# test_generators.sh inspects the generated JSON instead of trusting check.
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option routing_mode 'bypass_mainland_china'
|
||||
option proxy_mode 'tun'
|
||||
option ipv6_support '0'
|
||||
@@ -25,7 +25,7 @@ config homeproxy 'config'
|
||||
option main_udp_node 'nil'
|
||||
option dns_server '223.5.5.5'
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option mixed_port '5330'
|
||||
option dns_port '5333'
|
||||
option tun_name 'singtun0'
|
||||
+8
-8
@@ -24,7 +24,7 @@
|
||||
# sub-object without depending on the generator's particular routing
|
||||
# mode or proxy mode.
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option routing_mode 'bypass_mainland_china'
|
||||
option proxy_mode 'redirect_tproxy'
|
||||
option ipv6_support '0'
|
||||
@@ -39,7 +39,7 @@ config homeproxy 'config'
|
||||
option github_token ''
|
||||
option cn_ip_fallback '0'
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option mixed_port '5330'
|
||||
option dns_port '5333'
|
||||
option redirect_port '5331'
|
||||
@@ -57,7 +57,7 @@ config homeproxy 'infra'
|
||||
option dns_redirect '1'
|
||||
option udp_timeout ''
|
||||
|
||||
config homeproxy 'dns'
|
||||
config homeproxy-pro 'dns'
|
||||
option default_strategy 'prefer_ipv4'
|
||||
option default_server 'default-dns'
|
||||
option disable_cache '0'
|
||||
@@ -80,7 +80,7 @@ config dns_rule 'dr_hijack'
|
||||
option mode 'default'
|
||||
option action 'hijack-dns'
|
||||
|
||||
config homeproxy 'routing'
|
||||
config homeproxy-pro 'routing'
|
||||
option default_outbound 'direct-out'
|
||||
option default_outbound_dns 'default-dns'
|
||||
option domain_strategy ''
|
||||
@@ -113,13 +113,13 @@ config ruleset 'rs_cn'
|
||||
option download_detour 'main-out'
|
||||
option update_interval '24h'
|
||||
|
||||
config homeproxy 'control'
|
||||
config homeproxy-pro 'control'
|
||||
option bind_interface ''
|
||||
option lan_proxy_mode 'disabled'
|
||||
list wan_proxy_ipv4_ips '91.108.4.0/22'
|
||||
list wan_proxy_ipv6_ips '2001:67c:4e8::/48'
|
||||
|
||||
config homeproxy 'subscription'
|
||||
config homeproxy-pro 'subscription'
|
||||
option auto_update '0'
|
||||
option allow_insecure '0'
|
||||
option packet_encoding 'xudp'
|
||||
@@ -130,7 +130,7 @@ config homeproxy 'subscription'
|
||||
option user_agent 'HomeProxy/1.0'
|
||||
list subscription_url 'https://example.com/sub'
|
||||
|
||||
config homeproxy 'server'
|
||||
config homeproxy-pro 'server'
|
||||
option enabled '1'
|
||||
option log_level 'warn'
|
||||
|
||||
@@ -144,7 +144,7 @@ config server 's_vless'
|
||||
option tls '1'
|
||||
option tls_sni 'server.example.com'
|
||||
|
||||
config homeproxy 'migration'
|
||||
config homeproxy-pro 'migration'
|
||||
option crontab '1'
|
||||
|
||||
config node 'n_anytls'
|
||||
+3
-3
@@ -13,14 +13,14 @@
|
||||
# WireGuard is absent on purpose: it is emitted as a sing-box endpoint, not an
|
||||
# outbound, and is covered by wireguard.uci.
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option routing_mode 'bypass_mainland_china'
|
||||
option proxy_mode 'tun'
|
||||
option ipv6_support '0'
|
||||
option main_node 'n_vless_reality_ws'
|
||||
option main_udp_node 'nil'
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option mixed_port '5330'
|
||||
option dns_port '5333'
|
||||
option tun_name 'singtun0'
|
||||
@@ -237,7 +237,7 @@ config node 'n_ssh'
|
||||
list ssh_priv_key 'MF94IOMsS6yeu1jLGM2kAAAADmhvbWVwcm94eS10ZXN0AQIDBAUGBw=='
|
||||
list ssh_priv_key '-----END OPENSSH PRIVATE KEY-----'
|
||||
option ssh_client_version 'SSH-2.0-OpenSSH_8.9p1'
|
||||
list ssh_host_key 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBjQAO/kXgJXZOUaQ2DZzfPfMF94IOMsS6yeu1jLGM2k homeproxy-test'
|
||||
list ssh_host_key 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBjQAO/kXgJXZOUaQ2DZzfPfMF94IOMsS6yeu1jLGM2k homeproxy-pro-test'
|
||||
list ssh_host_key_algo 'ssh-ed25519'
|
||||
|
||||
config node 'n_direct'
|
||||
+2
-2
@@ -13,7 +13,7 @@
|
||||
# n_broken is a vless node with no uuid on purpose; the test asserts that
|
||||
# generation succeeds and that n_broken is absent from the emitted group.
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option routing_mode 'bypass_mainland_china'
|
||||
option proxy_mode 'tun'
|
||||
option ipv6_support '0'
|
||||
@@ -24,7 +24,7 @@ config homeproxy 'config'
|
||||
list main_urltest_nodes 'n_broken'
|
||||
option dns_server 'https://cloudflare-dns.com/dns-query'
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option mixed_port '5330'
|
||||
option dns_port '5333'
|
||||
option tun_name 'singtun0'
|
||||
+2
-2
@@ -11,7 +11,7 @@
|
||||
# redirect-in/tproxy-in, the dns detours that are gated on self_mark, and the
|
||||
# per-outbound routing_mark itself.
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option routing_mode 'bypass_mainland_china'
|
||||
option proxy_mode 'redirect_tproxy'
|
||||
option ipv6_support '0'
|
||||
@@ -30,7 +30,7 @@ config homeproxy 'config'
|
||||
list main_urltest_nodes 'n_socks'
|
||||
list main_urltest_nodes 'n_shadowtls'
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option mixed_port '5330'
|
||||
option dns_port '5333'
|
||||
option tun_name 'singtun0'
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
# covering the shared TLS/transport builders (ACME provider, ECH key, REALITY,
|
||||
# WebSocket/gRPC/HTTP transports) on the server side.
|
||||
|
||||
config homeproxy 'server'
|
||||
config homeproxy-pro 'server'
|
||||
option enabled '1'
|
||||
option log_level 'warn'
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@
|
||||
# The unit test asserts the key material survives, not just that sing-box
|
||||
# accepts the file.
|
||||
|
||||
config homeproxy 'config'
|
||||
config homeproxy-pro 'config'
|
||||
option routing_mode 'bypass_mainland_china'
|
||||
option proxy_mode 'tun'
|
||||
option ipv6_support '0'
|
||||
@@ -20,7 +20,7 @@ config homeproxy 'config'
|
||||
option main_udp_node 'nil'
|
||||
option dns_server 'https://cloudflare-dns.com/dns-query'
|
||||
|
||||
config homeproxy 'infra'
|
||||
config homeproxy-pro 'infra'
|
||||
option mixed_port '5330'
|
||||
option dns_port '5333'
|
||||
option tun_name 'singtun0'
|
||||
+419
-419
File diff suppressed because it is too large.
Load diff
+96
-96
@@ -1,13 +1,13 @@
|
||||
|
||||
===== scenario: A-tun-bypass-client-only (start) =====
|
||||
uci -q show dhcp.@dnsmasq[0]
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
sing-box version -n
|
||||
uname -r
|
||||
ip route show default
|
||||
ip -6 route show default
|
||||
ifstatus wan
|
||||
ucode -S etc/homeproxy/scripts/generate_client.uc
|
||||
ucode -S etc/homeproxy-pro/scripts/generate_client.uc
|
||||
log Warning: failed to restart dnsmasq, DNS-based routing may be stale.
|
||||
ip tuntap add mode tun user root name singtun0
|
||||
sleep 1s
|
||||
@@ -16,16 +16,16 @@ ip route replace default dev singtun0 table 100
|
||||
ip rule add fwmark 102 lookup 100
|
||||
procd_open_instance sing-box-c
|
||||
procd_set_param command /usr/bin/sing-box
|
||||
procd_append_param command run --config var/run/homeproxy/sing-box-c.json
|
||||
procd_append_param command run --config var/run/homeproxy-pro/sing-box-c.json
|
||||
procd_add_jail sing-box-c log procfs
|
||||
procd_add_jail_mount var/run/homeproxy/sing-box-c.json
|
||||
procd_add_jail_mount_rw var/run/homeproxy/sing-box-c.log
|
||||
procd_add_jail_mount_rw etc/homeproxy/cache.db
|
||||
procd_add_jail_mount etc/homeproxy/certs/
|
||||
procd_add_jail_mount var/run/homeproxy-pro/sing-box-c.json
|
||||
procd_add_jail_mount_rw var/run/homeproxy-pro/sing-box-c.log
|
||||
procd_add_jail_mount_rw etc/homeproxy-pro/cache.db
|
||||
procd_add_jail_mount etc/homeproxy-pro/certs/
|
||||
procd_add_jail_mount /etc/ssl/
|
||||
procd_add_jail_mount /etc/localtime
|
||||
procd_add_jail_mount /etc/TZ
|
||||
procd_set_param capabilities /etc/capabilities/homeproxy.json
|
||||
procd_set_param capabilities /etc/capabilities/homeproxy-pro.json
|
||||
procd_set_param no_new_privs 1
|
||||
procd_set_param user sing-box
|
||||
procd_set_param group sing-box
|
||||
@@ -35,19 +35,19 @@ procd_set_param stderr 1
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
procd_open_instance log-cleaner
|
||||
procd_set_param command etc/homeproxy/scripts/clean_log.sh
|
||||
procd_set_param command etc/homeproxy-pro/scripts/clean_log.sh
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
chown sing-box:sing-box var/run/homeproxy/sing-box-c.json var/run/homeproxy/sing-box-c.log etc/homeproxy/cache.db
|
||||
ucode etc/homeproxy/scripts/firewall_pre.uc
|
||||
utpl -S etc/homeproxy/scripts/firewall_post.ut
|
||||
chown sing-box:sing-box var/run/homeproxy-pro/sing-box-c.json var/run/homeproxy-pro/sing-box-c.log etc/homeproxy-pro/cache.db
|
||||
ucode etc/homeproxy-pro/scripts/firewall_pre.uc
|
||||
utpl -S etc/homeproxy-pro/scripts/firewall_post.ut
|
||||
fw4 reload
|
||||
sing-box version -n
|
||||
log sing-box 1.14.0 started.
|
||||
start rc=127
|
||||
log Warning: failed to drop the auto-update cron entry.
|
||||
log Warning: failed to restart cron.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip rule del fwmark 101 table 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
ip -6 rule del fwmark 101 table 100
|
||||
@@ -105,28 +105,28 @@ nft delete set inet fw4 homeproxy_wan_direct_addr_v6
|
||||
nft flush set inet fw4 homeproxy_routing_port
|
||||
nft delete set inet fw4 homeproxy_routing_port
|
||||
fw4 reload
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy snippets.
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy-pro snippets.
|
||||
log Service stopped.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip link set singtun0 down
|
||||
ip tuntap del mode tun name singtun0
|
||||
stop rc=0
|
||||
file var/run/homeproxy/fw4_forward.nft:
|
||||
file var/run/homeproxy/fw4_input.nft:
|
||||
file var/run/homeproxy/fw4_post.nft:
|
||||
file var/run/homeproxy/homeproxy.log:
|
||||
file var/run/homeproxy/known-good/sing-box-c.json: {"log":{},"inbounds":[{"tag":"dns-in","listen_port":5333},{"tag":"mixed-in","listen_port":5330}],"outbounds":[]}|
|
||||
file etc/homeproxy/cache.db:
|
||||
file var/run/homeproxy-pro/fw4_forward.nft:
|
||||
file var/run/homeproxy-pro/fw4_input.nft:
|
||||
file var/run/homeproxy-pro/fw4_post.nft:
|
||||
file var/run/homeproxy-pro/homeproxy-pro.log:
|
||||
file var/run/homeproxy-pro/known-good/sing-box-c.json: {"log":{},"inbounds":[{"tag":"dns-in","listen_port":5333},{"tag":"mixed-in","listen_port":5330}],"outbounds":[]}|
|
||||
file etc/homeproxy-pro/cache.db:
|
||||
|
||||
===== scenario: B-tproxy-gfwlist-ipv6-with-server (start) =====
|
||||
uci -q show dhcp.@dnsmasq[0]
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
sing-box version -n
|
||||
uname -r
|
||||
ip route show default
|
||||
ip -6 route show default
|
||||
ifstatus wan
|
||||
ucode -S etc/homeproxy/scripts/generate_client.uc
|
||||
ucode -S etc/homeproxy-pro/scripts/generate_client.uc
|
||||
log Warning: failed to drop the previous auto-update cron entry.
|
||||
log Warning: failed to install the auto-update cron entry.
|
||||
log Warning: failed to restart cron.
|
||||
@@ -137,15 +137,15 @@ ip -6 rule add fwmark 101 table 100
|
||||
ip -6 route add local ::/0 dev lo table 100
|
||||
procd_open_instance sing-box-c
|
||||
procd_set_param command /usr/bin/sing-box
|
||||
procd_append_param command run --config var/run/homeproxy/sing-box-c.json
|
||||
procd_append_param command run --config var/run/homeproxy-pro/sing-box-c.json
|
||||
procd_add_jail sing-box-c log procfs
|
||||
procd_add_jail_mount var/run/homeproxy/sing-box-c.json
|
||||
procd_add_jail_mount_rw var/run/homeproxy/sing-box-c.log
|
||||
procd_add_jail_mount etc/homeproxy/certs/
|
||||
procd_add_jail_mount var/run/homeproxy-pro/sing-box-c.json
|
||||
procd_add_jail_mount_rw var/run/homeproxy-pro/sing-box-c.log
|
||||
procd_add_jail_mount etc/homeproxy-pro/certs/
|
||||
procd_add_jail_mount /etc/ssl/
|
||||
procd_add_jail_mount /etc/localtime
|
||||
procd_add_jail_mount /etc/TZ
|
||||
procd_set_param capabilities /etc/capabilities/homeproxy.json
|
||||
procd_set_param capabilities /etc/capabilities/homeproxy-pro.json
|
||||
procd_set_param no_new_privs 1
|
||||
procd_set_param user sing-box
|
||||
procd_set_param group sing-box
|
||||
@@ -155,19 +155,19 @@ procd_set_param stderr 1
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
procd_open_instance log-cleaner
|
||||
procd_set_param command etc/homeproxy/scripts/clean_log.sh
|
||||
procd_set_param command etc/homeproxy-pro/scripts/clean_log.sh
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
chown sing-box:sing-box var/run/homeproxy/sing-box-c.json var/run/homeproxy/sing-box-c.log etc/homeproxy/cache.db
|
||||
ucode etc/homeproxy/scripts/firewall_pre.uc
|
||||
utpl -S etc/homeproxy/scripts/firewall_post.ut
|
||||
chown sing-box:sing-box var/run/homeproxy-pro/sing-box-c.json var/run/homeproxy-pro/sing-box-c.log etc/homeproxy-pro/cache.db
|
||||
ucode etc/homeproxy-pro/scripts/firewall_pre.uc
|
||||
utpl -S etc/homeproxy-pro/scripts/firewall_post.ut
|
||||
fw4 reload
|
||||
sing-box version -n
|
||||
log sing-box 1.14.0 started.
|
||||
start rc=127
|
||||
log Warning: failed to drop the auto-update cron entry.
|
||||
log Warning: failed to restart cron.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip rule del fwmark 101 table 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
ip -6 rule del fwmark 101 table 100
|
||||
@@ -225,27 +225,27 @@ nft delete set inet fw4 homeproxy_wan_direct_addr_v6
|
||||
nft flush set inet fw4 homeproxy_routing_port
|
||||
nft delete set inet fw4 homeproxy_routing_port
|
||||
fw4 reload
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy snippets.
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy-pro snippets.
|
||||
log Service stopped.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip link set singtun0 down
|
||||
ip tuntap del mode tun name singtun0
|
||||
stop rc=0
|
||||
file var/run/homeproxy/fw4_forward.nft:
|
||||
file var/run/homeproxy/fw4_input.nft:
|
||||
file var/run/homeproxy/fw4_post.nft:
|
||||
file var/run/homeproxy/homeproxy.log:
|
||||
file var/run/homeproxy/known-good/sing-box-c.json: {"log":{},"inbounds":[{"tag":"dns-in","listen_port":5333},{"tag":"mixed-in","listen_port":5330}],"outbounds":[]}|
|
||||
file var/run/homeproxy-pro/fw4_forward.nft:
|
||||
file var/run/homeproxy-pro/fw4_input.nft:
|
||||
file var/run/homeproxy-pro/fw4_post.nft:
|
||||
file var/run/homeproxy-pro/homeproxy-pro.log:
|
||||
file var/run/homeproxy-pro/known-good/sing-box-c.json: {"log":{},"inbounds":[{"tag":"dns-in","listen_port":5333},{"tag":"mixed-in","listen_port":5330}],"outbounds":[]}|
|
||||
|
||||
===== scenario: C-nothing-configured (start) =====
|
||||
uci -q show dhcp.@dnsmasq[0]
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
sing-box version -n
|
||||
log Neither client nor server is configured. Service not started.
|
||||
start rc=127
|
||||
log Warning: failed to drop the auto-update cron entry.
|
||||
log Warning: failed to restart cron.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip rule del fwmark 101 table 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
ip -6 rule del fwmark 101 table 100
|
||||
@@ -303,25 +303,25 @@ nft delete set inet fw4 homeproxy_wan_direct_addr_v6
|
||||
nft flush set inet fw4 homeproxy_routing_port
|
||||
nft delete set inet fw4 homeproxy_routing_port
|
||||
fw4 reload
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy snippets.
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy-pro snippets.
|
||||
log Service stopped.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip link set singtun0 down
|
||||
ip tuntap del mode tun name singtun0
|
||||
stop rc=0
|
||||
file var/run/homeproxy/fw4_forward.nft:
|
||||
file var/run/homeproxy/fw4_input.nft:
|
||||
file var/run/homeproxy/fw4_post.nft:
|
||||
file var/run/homeproxy-pro/fw4_forward.nft:
|
||||
file var/run/homeproxy-pro/fw4_input.nft:
|
||||
file var/run/homeproxy-pro/fw4_post.nft:
|
||||
|
||||
===== scenario: D-health-gate-rollback (reload) =====
|
||||
uci -q show dhcp.@dnsmasq[0]
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
sing-box version -n
|
||||
uname -r
|
||||
ip route show default
|
||||
ip -6 route show default
|
||||
ifstatus wan
|
||||
ucode -S etc/homeproxy/scripts/generate_client.uc
|
||||
ucode -S etc/homeproxy-pro/scripts/generate_client.uc
|
||||
log Warning: failed to restart dnsmasq, DNS-based routing may be stale.
|
||||
ip tuntap add mode tun user root name singtun0
|
||||
sleep 1s
|
||||
@@ -330,16 +330,16 @@ ip route replace default dev singtun0 table 100
|
||||
ip rule add fwmark 102 lookup 100
|
||||
procd_open_instance sing-box-c
|
||||
procd_set_param command /usr/bin/sing-box
|
||||
procd_append_param command run --config var/run/homeproxy/sing-box-c.json
|
||||
procd_append_param command run --config var/run/homeproxy-pro/sing-box-c.json
|
||||
procd_add_jail sing-box-c log procfs
|
||||
procd_add_jail_mount var/run/homeproxy/sing-box-c.json
|
||||
procd_add_jail_mount_rw var/run/homeproxy/sing-box-c.log
|
||||
procd_add_jail_mount_rw etc/homeproxy/cache.db
|
||||
procd_add_jail_mount etc/homeproxy/certs/
|
||||
procd_add_jail_mount var/run/homeproxy-pro/sing-box-c.json
|
||||
procd_add_jail_mount_rw var/run/homeproxy-pro/sing-box-c.log
|
||||
procd_add_jail_mount_rw etc/homeproxy-pro/cache.db
|
||||
procd_add_jail_mount etc/homeproxy-pro/certs/
|
||||
procd_add_jail_mount /etc/ssl/
|
||||
procd_add_jail_mount /etc/localtime
|
||||
procd_add_jail_mount /etc/TZ
|
||||
procd_set_param capabilities /etc/capabilities/homeproxy.json
|
||||
procd_set_param capabilities /etc/capabilities/homeproxy-pro.json
|
||||
procd_set_param no_new_privs 1
|
||||
procd_set_param user sing-box
|
||||
procd_set_param group sing-box
|
||||
@@ -349,22 +349,22 @@ procd_set_param stderr 1
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
procd_open_instance log-cleaner
|
||||
procd_set_param command etc/homeproxy/scripts/clean_log.sh
|
||||
procd_set_param command etc/homeproxy-pro/scripts/clean_log.sh
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
chown sing-box:sing-box var/run/homeproxy/sing-box-c.json var/run/homeproxy/sing-box-c.log etc/homeproxy/cache.db
|
||||
ucode etc/homeproxy/scripts/firewall_pre.uc
|
||||
utpl -S etc/homeproxy/scripts/firewall_post.ut
|
||||
chown sing-box:sing-box var/run/homeproxy-pro/sing-box-c.json var/run/homeproxy-pro/sing-box-c.log etc/homeproxy-pro/cache.db
|
||||
ucode etc/homeproxy-pro/scripts/firewall_pre.uc
|
||||
utpl -S etc/homeproxy-pro/scripts/firewall_post.ut
|
||||
fw4 reload
|
||||
sing-box version -n
|
||||
log sing-box 1.14.0 started.
|
||||
prime start rc=127
|
||||
log Reloading service...
|
||||
config_load homeproxy
|
||||
ucode -S etc/homeproxy/scripts/generate_client.uc
|
||||
config_load homeproxy-pro
|
||||
ucode -S etc/homeproxy-pro/scripts/generate_client.uc
|
||||
log Warning: failed to drop the auto-update cron entry.
|
||||
log Warning: failed to restart cron.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip rule del fwmark 101 table 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
ip -6 rule del fwmark 101 table 100
|
||||
@@ -422,18 +422,18 @@ nft delete set inet fw4 homeproxy_wan_direct_addr_v6
|
||||
nft flush set inet fw4 homeproxy_routing_port
|
||||
nft delete set inet fw4 homeproxy_routing_port
|
||||
fw4 reload
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy snippets.
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy-pro snippets.
|
||||
log Service stopped.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip link set singtun0 down
|
||||
ip tuntap del mode tun name singtun0
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
sing-box version -n
|
||||
uname -r
|
||||
ip route show default
|
||||
ip -6 route show default
|
||||
ifstatus wan
|
||||
ucode -S etc/homeproxy/scripts/generate_client.uc
|
||||
ucode -S etc/homeproxy-pro/scripts/generate_client.uc
|
||||
log Warning: failed to restart dnsmasq, DNS-based routing may be stale.
|
||||
ip tuntap add mode tun user root name singtun0
|
||||
sleep 1s
|
||||
@@ -442,16 +442,16 @@ ip route replace default dev singtun0 table 100
|
||||
ip rule add fwmark 102 lookup 100
|
||||
procd_open_instance sing-box-c
|
||||
procd_set_param command /usr/bin/sing-box
|
||||
procd_append_param command run --config var/run/homeproxy/sing-box-c.json
|
||||
procd_append_param command run --config var/run/homeproxy-pro/sing-box-c.json
|
||||
procd_add_jail sing-box-c log procfs
|
||||
procd_add_jail_mount var/run/homeproxy/sing-box-c.json
|
||||
procd_add_jail_mount_rw var/run/homeproxy/sing-box-c.log
|
||||
procd_add_jail_mount_rw etc/homeproxy/cache.db
|
||||
procd_add_jail_mount etc/homeproxy/certs/
|
||||
procd_add_jail_mount var/run/homeproxy-pro/sing-box-c.json
|
||||
procd_add_jail_mount_rw var/run/homeproxy-pro/sing-box-c.log
|
||||
procd_add_jail_mount_rw etc/homeproxy-pro/cache.db
|
||||
procd_add_jail_mount etc/homeproxy-pro/certs/
|
||||
procd_add_jail_mount /etc/ssl/
|
||||
procd_add_jail_mount /etc/localtime
|
||||
procd_add_jail_mount /etc/TZ
|
||||
procd_set_param capabilities /etc/capabilities/homeproxy.json
|
||||
procd_set_param capabilities /etc/capabilities/homeproxy-pro.json
|
||||
procd_set_param no_new_privs 1
|
||||
procd_set_param user sing-box
|
||||
procd_set_param group sing-box
|
||||
@@ -461,52 +461,52 @@ procd_set_param stderr 1
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
procd_open_instance log-cleaner
|
||||
procd_set_param command etc/homeproxy/scripts/clean_log.sh
|
||||
procd_set_param command etc/homeproxy-pro/scripts/clean_log.sh
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
chown sing-box:sing-box var/run/homeproxy/sing-box-c.json var/run/homeproxy/sing-box-c.log etc/homeproxy/cache.db
|
||||
ucode etc/homeproxy/scripts/firewall_pre.uc
|
||||
utpl -S etc/homeproxy/scripts/firewall_post.ut
|
||||
chown sing-box:sing-box var/run/homeproxy-pro/sing-box-c.json var/run/homeproxy-pro/sing-box-c.log etc/homeproxy-pro/cache.db
|
||||
ucode etc/homeproxy-pro/scripts/firewall_pre.uc
|
||||
utpl -S etc/homeproxy-pro/scripts/firewall_post.ut
|
||||
fw4 reload
|
||||
sing-box version -n
|
||||
log sing-box 1.14.0 started.
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
jsonfilter -e @['homeproxy'].instances['sing-box-c']
|
||||
jsonfilter -e @['homeproxy-pro'].instances['sing-box-c']
|
||||
jsonfilter -e @.running
|
||||
sleep 1
|
||||
log Error: sing-box-c did not come up with the new configuration, rolling back.
|
||||
log Error: the failing configuration is already the known-good one; nothing to roll back to.
|
||||
log Error: rollback was not possible; the service may be down. Check var/run/homeproxy/homeproxy.log.
|
||||
log Error: rollback was not possible; the service may be down. Check var/run/homeproxy-pro/homeproxy-pro.log.
|
||||
reload_service rc=1
|
||||
log Warning: failed to drop the auto-update cron entry.
|
||||
log Warning: failed to restart cron.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip rule del fwmark 101 table 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
ip -6 rule del fwmark 101 table 100
|
||||
@@ -564,15 +564,15 @@ nft delete set inet fw4 homeproxy_wan_direct_addr_v6
|
||||
nft flush set inet fw4 homeproxy_routing_port
|
||||
nft delete set inet fw4 homeproxy_routing_port
|
||||
fw4 reload
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy snippets.
|
||||
log Warning: failed to restart dnsmasq after removing the homeproxy-pro snippets.
|
||||
log Service stopped.
|
||||
config_load homeproxy
|
||||
config_load homeproxy-pro
|
||||
ip link set singtun0 down
|
||||
ip tuntap del mode tun name singtun0
|
||||
stop rc=0
|
||||
file var/run/homeproxy/fw4_forward.nft:
|
||||
file var/run/homeproxy/fw4_input.nft:
|
||||
file var/run/homeproxy/fw4_post.nft:
|
||||
file var/run/homeproxy/homeproxy.log:
|
||||
file var/run/homeproxy/known-good/sing-box-c.json: {"log":{},"inbounds":[{"tag":"dns-in","listen_port":5333},{"tag":"mixed-in","listen_port":5399}],"outbounds":[]}|
|
||||
file etc/homeproxy/cache.db:
|
||||
file var/run/homeproxy-pro/fw4_forward.nft:
|
||||
file var/run/homeproxy-pro/fw4_input.nft:
|
||||
file var/run/homeproxy-pro/fw4_post.nft:
|
||||
file var/run/homeproxy-pro/homeproxy-pro.log:
|
||||
file var/run/homeproxy-pro/known-good/sing-box-c.json: {"log":{},"inbounds":[{"tag":"dns-in","listen_port":5333},{"tag":"mixed-in","listen_port":5399}],"outbounds":[]}|
|
||||
file etc/homeproxy-pro/cache.db:
|
||||
+6
-6
@@ -14,8 +14,8 @@
|
||||
* handler's *first* parameter. A handler that declares a leading parameter no
|
||||
* caller passes therefore shifts everything by one: filename becomes the event
|
||||
* object, the path becomes /tmp/homeproxy_cert_[object Event].tmp, and both the
|
||||
* ACL whitelist (acl.d/luci-app-homeproxy.json) and certificate_write
|
||||
* (luci.homeproxy, which reads /tmp/homeproxy_cert_${filename}.tmp) stop
|
||||
* ACL whitelist (acl.d/luci-app-homeproxy-pro.json) and certificate_write
|
||||
* (luci.homeproxy-pro, which reads /tmp/homeproxy_cert_${filename}.tmp) stop
|
||||
* matching. Every upload fails and the UI only shows a generic error.
|
||||
*
|
||||
* The unit under test is the *argument alignment*, not the handler body, so
|
||||
@@ -47,7 +47,7 @@ function check(what, ok, detail) {
|
||||
const VIEWS = [ 'node.js', 'server.js' ];
|
||||
const calls = [];
|
||||
for (const view of VIEWS) {
|
||||
const file = path.join(root, 'htdocs/luci-static/resources/view/homeproxy', view);
|
||||
const file = path.join(root, 'htdocs/luci-static/resources/view/homeproxy-pro', view);
|
||||
const src = fs.readFileSync(file, 'utf8');
|
||||
const re = /L\.bind\(hp\.uploadCertificate[^;]*\)/g;
|
||||
let m;
|
||||
@@ -76,7 +76,7 @@ function boundArgs(text) {
|
||||
|
||||
const parsed = calls.map((c) => ({ ...c, args: boundArgs(c.text) }));
|
||||
|
||||
/* --- load homeproxy.js the way LuCI does ---------------------------------- */
|
||||
/* --- load homeproxy-pro.js the way LuCI does ---------------------------------- */
|
||||
const uploads = [];
|
||||
const rpcCalls = [];
|
||||
|
||||
@@ -101,7 +101,7 @@ const uiStub = {
|
||||
};
|
||||
|
||||
const hp = loadLuciModule(
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy.js'),
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy-pro.js'),
|
||||
{
|
||||
baseclass: { extend: (o) => o },
|
||||
form: { DynamicList: { extend: (o) => o } },
|
||||
@@ -110,7 +110,7 @@ const hp = loadLuciModule(
|
||||
{ L, E: () => null }
|
||||
);
|
||||
|
||||
check('homeproxy.js exposes uploadCertificate', typeof hp.uploadCertificate === 'function',
|
||||
check('homeproxy-pro.js exposes uploadCertificate', typeof hp.uploadCertificate === 'function',
|
||||
typeof hp.uploadCertificate);
|
||||
|
||||
/* --- drive each call site the way form.js does ---------------------------- */
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* homeproxy.js calcStringMD5() must be *the* MD5 of RFC 1321, because its
|
||||
* homeproxy-pro.js calcStringMD5() must be *the* MD5 of RFC 1321, because its
|
||||
* output is compared against a value another language computed.
|
||||
*
|
||||
* The contract, end to end:
|
||||
@@ -11,7 +11,7 @@
|
||||
* -> written to UCI as the node's `grouphash`
|
||||
* repository.uc md5(node.grouphash + label)
|
||||
* -> the UCI section name of a subscription node
|
||||
* view/homeproxy/node.js hp.calcStringMD5(suburl.replace(/#.*$/, ''))
|
||||
* view/homeproxy-pro/node.js hp.calcStringMD5(suburl.replace(/#.*$/, ''))
|
||||
* -> matched against `grouphash` to decide which
|
||||
* subscription tab a section belongs to, and
|
||||
* whether it is a subscription node at all
|
||||
@@ -67,7 +67,7 @@ function check(what, ok, detail) {
|
||||
* inert set tests/frontend-validators.js uses, since the method touches none
|
||||
* of them. */
|
||||
const hp = loadLuciModule(
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy.js'),
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy-pro.js'),
|
||||
{
|
||||
baseclass: { extend: (o) => o },
|
||||
form: { DynamicList: { extend: (o) => o } },
|
||||
+4
-4
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Frontend protocol inventory.
|
||||
*
|
||||
* homeproxy.js carries one ordered `protocols` table and both forms render
|
||||
* homeproxy-pro.js carries one ordered `protocols` table and both forms render
|
||||
* their type picker from it. Before that table existed each form had its own
|
||||
* hand-written value list, and they had drifted in ways nothing could catch:
|
||||
* `snell` had a complete form block, a CREDENTIALS row and a golden outbound
|
||||
@@ -41,14 +41,14 @@ const path = require('path');
|
||||
const { loadLuciModule } = require('./lib/luci-module.js');
|
||||
|
||||
const root = path.resolve(process.argv[2] || '.');
|
||||
const scripts = path.join(root, 'root/etc/homeproxy/scripts');
|
||||
const scripts = path.join(root, 'root/etc/homeproxy-pro/scripts');
|
||||
|
||||
/* The registry is data plus the renderer; loading it through the whole LuCI
|
||||
* mock would test the mock. `protocols` only needs the module to evaluate its
|
||||
* top-level statements, so the imports are stubbed just far enough for that. */
|
||||
function loadHomeproxy() {
|
||||
return loadLuciModule(
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy.js'),
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy-pro.js'),
|
||||
{
|
||||
baseclass: { extend: (o) => o },
|
||||
form: { DynamicList: { extend: (o) => o } },
|
||||
@@ -95,7 +95,7 @@ function check(what, ok, detail) {
|
||||
|
||||
const hp = loadHomeproxy();
|
||||
const protocols = hp.protocols;
|
||||
check('homeproxy.js exports a protocols table', Array.isArray(protocols) && protocols.length > 0);
|
||||
check('homeproxy-pro.js exports a protocols table', Array.isArray(protocols) && protocols.length > 0);
|
||||
if (!Array.isArray(protocols)) {
|
||||
console.error(`\n${checks} checks, ${failures} failures`);
|
||||
process.exit(1);
|
||||
+1
-1
@@ -38,7 +38,7 @@ function check(what, ok, detail) {
|
||||
}
|
||||
|
||||
const hp = loadLuciModule(
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy.js'),
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy-pro.js'),
|
||||
{ baseclass: { extend: (o) => o }, form: { DynamicList: { extend: (o) => o } },
|
||||
fs: {}, rpc: {}, uci: {}, ui: {} });
|
||||
|
||||
+14
-14
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Frontend RPC boundary.
|
||||
*
|
||||
* Every backend call goes through `homeproxy.js`'s `rpcCall()`, which is the
|
||||
* Every backend call goes through `homeproxy-pro.js`'s `rpcCall()`, which is the
|
||||
* one place that knows how to declare an RPC and what to do when the call does
|
||||
* not come back. Before that, nine call sites used
|
||||
* `L.resolveDefault(call(), {})` - which does NOT catch a rejection, it only
|
||||
@@ -28,11 +28,11 @@ const path = require('path');
|
||||
const root = path.resolve(process.argv[2] || '.');
|
||||
const res = path.join(root, 'htdocs/luci-static/resources');
|
||||
const files = [
|
||||
'homeproxy.js',
|
||||
'view/homeproxy/node.js',
|
||||
'view/homeproxy/client.js',
|
||||
'view/homeproxy/server.js',
|
||||
'view/homeproxy/status.js'
|
||||
'homeproxy-pro.js',
|
||||
'view/homeproxy-pro/node.js',
|
||||
'view/homeproxy-pro/client.js',
|
||||
'view/homeproxy-pro/server.js',
|
||||
'view/homeproxy-pro/status.js'
|
||||
];
|
||||
|
||||
let checks = 0, failures = 0;
|
||||
@@ -44,7 +44,7 @@ function check(what, ok, detail) {
|
||||
}
|
||||
|
||||
/* Comments have to go before the pattern checks. A naive grep matched the doc
|
||||
* comment in homeproxy.js that quotes the old `L.resolveDefault(call(), {})`
|
||||
* comment in homeproxy-pro.js that quotes the old `L.resolveDefault(call(), {})`
|
||||
* idiom - which is exactly the trap the plan records for the Architecture
|
||||
* Guard: the check has to talk about code, not about prose describing code.
|
||||
* Only block comments are stripped; a `//` stripper would cut real code after a
|
||||
@@ -60,8 +60,8 @@ for (const f of files)
|
||||
/* 1. One declaration site. */
|
||||
for (const f of files) {
|
||||
const n = (sources[f].match(/\brpc\.declare\s*\(/g) || []).length;
|
||||
if (f === 'homeproxy.js')
|
||||
check('homeproxy.js declares the RPCs', n === 1, `${n} rpc.declare calls, expected 1 (inside rpcCall)`);
|
||||
if (f === 'homeproxy-pro.js')
|
||||
check('homeproxy-pro.js declares the RPCs', n === 1, `${n} rpc.declare calls, expected 1 (inside rpcCall)`);
|
||||
else
|
||||
check(`${f} declares no RPC of its own`, n === 0, `${n} rpc.declare call(s) outside the shared helper`);
|
||||
}
|
||||
@@ -91,9 +91,9 @@ for (const f of files) {
|
||||
}
|
||||
|
||||
/* 3. rpcCall itself has to catch, and has to have a fallback. */
|
||||
const hp = sources['homeproxy.js'];
|
||||
const hp = sources['homeproxy-pro.js'];
|
||||
const helper = /rpcCall\s*\(method,\s*args,\s*options\)\s*\{([\s\S]*?)\n\t\}/.exec(hp);
|
||||
check('homeproxy.js defines rpcCall', !!helper);
|
||||
check('homeproxy-pro.js defines rpcCall', !!helper);
|
||||
if (helper) {
|
||||
check('rpcCall catches a failed call', /\.catch\(/.test(helper[1]));
|
||||
check('rpcCall has a fallback for a failed call', /fallback/.test(helper[1]));
|
||||
@@ -102,14 +102,14 @@ if (helper) {
|
||||
|
||||
/* 4. A view that calls rpcCall must import the module that provides it. */
|
||||
for (const f of files) {
|
||||
if (f === 'homeproxy.js') continue;
|
||||
if (f === 'homeproxy-pro.js') continue;
|
||||
if (!/\bhp\.rpcCall\s*\(/.test(sources[f])) continue;
|
||||
check(`${f} imports homeproxy for hp.rpcCall`, /'require homeproxy as hp';/.test(sources[f]));
|
||||
check(`${f} imports homeproxy-pro for hp.rpcCall`, /'require homeproxy-pro as hp';/.test(sources[f]));
|
||||
}
|
||||
|
||||
/* 5. No file keeps an unused `require rpc` after the conversion. */
|
||||
for (const f of files) {
|
||||
if (f === 'homeproxy.js') continue;
|
||||
if (f === 'homeproxy-pro.js') continue;
|
||||
const requiresRpc = /'require rpc';/.test(sources[f]);
|
||||
const usesRpc = /\brpc\./.test(sources[f]);
|
||||
check(`${f} has no unused 'require rpc'`, !requiresRpc || usesRpc);
|
||||
+3
-3
@@ -43,7 +43,7 @@ function check(what, ok, detail) {
|
||||
* the test can present a hostile label. */
|
||||
let label = null;
|
||||
const hp = loadLuciModule(
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy.js'),
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy-pro.js'),
|
||||
{ baseclass: { extend: (o) => o }, form: { DynamicList: { extend: (o) => o } },
|
||||
fs: {}, rpc: {}, ui: {},
|
||||
uci: { get: (_c, _s, o) => (o === 'label' ? label : null) } });
|
||||
@@ -149,14 +149,14 @@ check('escapeHtml would be unsafe for the modal title (raw tag decoded back)',
|
||||
/* loadModalTitle must use the stripTags-aware escape on the label. */
|
||||
for (const p of ['<img src=x onerror=alert(1)>', '<img src=x onerror=alert(1)>']) {
|
||||
label = p;
|
||||
const title = hp.loadModalTitle('Node', 'Add', 'homeproxy', 'n1');
|
||||
const title = hp.loadModalTitle('Node', 'Add', 'homeproxy-pro', 'n1');
|
||||
check(`loadModalTitle escapes the label ${JSON.stringify(p)}`,
|
||||
!titleBecomesMarkup(title),
|
||||
`title = ${JSON.stringify(title)} -> ${JSON.stringify(stripTags(title))}`);
|
||||
}
|
||||
label = null;
|
||||
check('loadModalTitle falls back to addtitle without a label',
|
||||
hp.loadModalTitle('Node', 'Add', 'homeproxy', 'n1') === 'Add');
|
||||
hp.loadModalTitle('Node', 'Add', 'homeproxy-pro', 'n1') === 'Add');
|
||||
|
||||
/* --- subscriptionInfo: the crash and the tab-title sink ------------------ */
|
||||
|
||||
Loaded 100 of 191 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user