dsh-mobile 0.6.8 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,14 @@
2
2
 
3
3
  [English](CADDY_MANAGED.en.md)
4
4
 
5
- > 托管模式的代码和隔离测试已接入;固定组件尚未正式分发,当前安装和连接按钮保持禁用。已有的自备 HTTPS 反向代理模式不受影响。
5
+ > 插件 0.6.9 起,Windows/Linux x64 可按需安装托管 Caddy,由插件管理 HTTPS 与证书。组件来自 DSH Mobile 审核发布的固定构建,不是 Caddy 官方定制下载接口。已有 Cloudflared 等通道和自备反向代理继续可用,无需迁移或升级 Android App。
6
6
 
7
7
  ## 适用条件
8
8
 
9
9
  托管 Caddy 为自建连接管理 HTTPS 代理和证书,不提供内网穿透。你需要自己的域名、由腾讯云 DNSPod 管理的 DNS,以及可从公网访问的入站端口;路由器需把公网端口映射到运行 DSH 的电脑。CGNAT、校园网隔离或没有入站权限的网络不能仅靠 DNS-01 绕过限制,可以使用 Cloudflare Tunnel、cpolar,或已有 VPS 上的 FRP。
10
10
 
11
+ 当前面板仅提供腾讯云 DNSPod 的 DNS-01 配置。使用 Cloudflare DNS 的用户可以继续使用 Cloudflared,无需为本功能迁移 DNS;不要把 Cloudflared 连接器 Token 填入腾讯云密钥栏。
12
+
11
13
  ```text
12
14
  Android App / 浏览器 → 公网 HTTPS 域名(Caddy 终止 TLS)
13
15
  → 127.0.0.1 上的独立 HTTP Gateway
@@ -18,11 +20,15 @@ Android App / 浏览器 → 公网 HTTPS 域名(Caddy 终止 TLS)
18
20
 
19
21
  两者属于「自备反向代理」提供方的不同上游模式。默认仍为自备代理:你维护 Lucky、Nginx 或 Caddy,插件只提供 HTTP 后端。切换为托管模式后,插件拥有一个独立的 Caddy 子进程和回环 Gateway,先停止旧模式再启动新模式;原有代理配置和已配对设备记录保留,不会被托管配置覆盖。
20
22
 
21
- 公网入口、DNS-01 凭据与本机 HTTPS 端口需单独配置。公网 URL 可以使用 443,而路由器转发到本机 8443;「本机 HTTPS 端口」是 Caddy 实际绑定的端口。3080、3443、3444 在此模式中保留,不可选择;其他端口仍需确认未被占用。DNS-01 凭据应使用仅能管理目标 DNS 区域的最小权限账号,不要提供云账号的主密钥。
23
+ 公网入口、DNS-01 凭据与本机 HTTPS 端口需单独配置。公网 URL 可以使用 443,而路由器转发到本机 8443;「本机 HTTPS 端口」是 Caddy 实际绑定的端口,范围为 1024–65535,3080、3443、3444 保留不可选。确认本机端口未占用,并在主机防火墙、路由器或云安全组中放行所需 HTTPS 入站;不要开放回环 HTTP 后端。DNS-01 凭据应使用仅能管理目标 DNS 区域的最小权限账号,不要提供云账号的主密钥。
22
24
 
23
25
  ## 安装、启动与就绪
24
26
 
25
- 官方组件分发本身不启用安装:还需独立验证实际资产,并通过后续独立审核的生产目录 PR 固定来源。完成第二阶段后,安装仍需要你明确确认下载。插件检查完整大小、SHA-256、Caddy 核心版本和 DNS 模块版本;来源不可用或验证失败时不执行文件。组件已安装不等于连接就绪:本地配置完成解析后启动代理,只有公网 HTTPS 证书通过验证,并确认 discovery 属于当前 DSH 实例,才显示已就绪。
27
+ 1. 在电脑端打开 **移动访问 → 远程 → 自建连接 → 自有反向代理**,选择 **托管 Caddy**。
28
+ 2. 点击 **安装托管 Caddy** 并确认下载。插件从固定的 [DSH Mobile 组件分发](https://github.com/saya-ch/dsh-mobile/releases/tag/caddy-component-2.11.7-tencentcloud-0.4.3-r1) 下载,核对完整大小、SHA-256、Caddy 核心与 DNS 模块版本;来源不可用或校验失败时不会执行文件。
29
+ 3. 填写公网 HTTPS 地址,例如 `https://dsh.example.com`,选择腾讯云 DNSPod,填写具有所需区域权限的 `SecretId` 和 `SecretKey`,再设置本机 HTTPS 端口,例如 `8443`。让域名解析到你的公网入口,并按需将公网 TCP 443 映射至电脑的 8443。
30
+ 4. 点击 **保存并启动 Caddy**。组件已安装不等于连接就绪:只有公网 HTTPS 证书通过验证,并确认入口属于当前 DSH 实例后,面板才显示已就绪。
31
+ 5. 生成远程配对二维码,在 Android App 的 **远程访问** 中扫码,或用手机浏览器打开配对链接。用手机流量验证实际页面加载、会话同步与 WebSocket;不要只以电脑端健康检查代替手机访问。
26
32
 
27
33
  首次 DNS-01 签发需要等待 DNS 生效和证书签发。期间电脑保持在线,避免反复重启触发签发限流。托管模式不安装系统服务、不修改 PATH、不设置开机启动、不导入系统证书;Caddy 的管理 API 和自动配置落盘均关闭。
28
34
 
@@ -30,13 +36,13 @@ Android App / 浏览器 → 公网 HTTPS 域名(Caddy 终止 TLS)
30
36
 
31
37
  设置与 DNS 凭据原子保存在插件私有目录,Caddyfile 只包含环境变量占位符。子进程只获得必要的 DNS 凭据和运行环境,不继承其他 API 密钥或代理、管理口配置;原始 DNS 模块日志不保存。证书、私钥、配置缓存和运行文件也保存在该私有目录。
32
38
 
33
- 停止访问或切换提供方会等待 Caddy 与 Gateway 退出,但保留设置。确认「移除托管 Caddy 与配置」后删除受管二进制、DNS 凭据、证书私钥、日志与缓存,不删除其他提供方、局域网或设备配对数据。已有手动安装的代理不归插件所有,也不会被插件卸载。
39
+ 停止访问或切换提供方会等待 Caddy 与 Gateway 退出,但保留设置和证书,以便下次连接。确认「移除托管 Caddy 与配置」后删除受管二进制、DNS 凭据、证书私钥、日志与缓存,不删除其他提供方、局域网或设备配对数据。清理失败会报告错误并保留可重试的状态,不会把未完成的删除显示为成功。已有手动安装的代理不归插件所有,也不会被插件卸载。
34
40
 
35
41
  ## 维护者构建与验证
36
42
 
37
43
  以下构建和验证工具需在本仓库源码 checkout 中运行;npm 插件包不包含这些维护脚本,也不需要普通用户自行编译 Caddy。
38
44
 
39
- [构建脚本](<../scripts/build-caddy-component.mjs>) 固定 Go 1.26.6、xcaddy v0.4.7、Caddy v2.11.6 和腾讯云 DNS v0.4.3。[输入锁](<../scripts/caddy-component-lock.json>) 固定源码提交和编译依赖版本/校验和。每次构建使用全新的私有编译缓存及模块缓存,执行 Go 校验和、源码来源与保留模块校验;未知依赖、替换模块、编译器或原生设置漂移会在运行生成的 Caddy 前拒绝。
45
+ [构建脚本](<../scripts/build-caddy-component.mjs>) 固定 Go 1.26.9、xcaddy v0.4.7、Caddy v2.11.7、腾讯云 DNS v0.4.3 及 golang.org/x/net v0.60.0。[输入锁](<../scripts/caddy-component-lock.json>) 固定源码提交和编译依赖版本/校验和。每次构建使用全新的私有编译缓存及模块缓存,执行 Go 校验和、源码来源与保留模块校验;未知依赖、替换模块、编译器或原生设置漂移会在运行生成的 Caddy 前拒绝。
40
46
 
41
47
  选择精确 Go 编译器(可用 `GO_BINARY`),输出目录必须是全新绝对路径:
42
48
 
@@ -48,28 +54,26 @@ node scripts/compare-caddy-builds.mjs --first /absolute/new/first --second /abso
48
54
 
49
55
  Windows 使用原生绝对路径和 `--target win32-x64`。可复现指二进制字节、大小、SHA-256 一致,不保证操作系统镜像或所有元数据/许可证文本逐字一致。[工作流](<../.github/workflows/caddy-component.yml>) 在 Windows/Linux x64 分别独立构建两次,审核完整产物,并使用独立 manifest 测试真正安装后的二进制:默认版本/模块检查、目录提升、无重复下载的重启、损坏下载回滚、TLS/配对/API/WSS、持久设备凭据与停止后清理,保留邻近文件及配对数据。其他架构仍未审核。
50
56
 
51
- PR 和普通手动运行只产生只读审查产物。只有授权的 `abworks-dev/dsh-mobile` fork 上已有 `caddy-component-2.11.6-tencentcloud-0.4.3-review.N` 标签,经明确手动启用且两个原生任务均通过,才发布 **TEST 预发布版**:
57
+ PR 和普通手动运行只产生只读审查产物。只有授权的 `abworks-dev/dsh-mobile` fork 上已有 `caddy-component-2.11.7-tencentcloud-0.4.3-review.N` 标签,经明确手动启用且两个原生任务均通过,才发布 **TEST 预发布版**:
52
58
 
53
59
  ```sh
54
- gh workflow run caddy-component.yml --repo abworks-dev/dsh-mobile --ref caddy-component-2.11.6-tencentcloud-0.4.3-review.1 -f publish_review_prerelease=true
60
+ gh workflow run caddy-component.yml --repo abworks-dev/dsh-mobile --ref caddy-component-2.11.7-tencentcloud-0.4.3-review.1 -f publish_review_prerelease=true
55
61
  ```
56
62
 
57
63
  组件标签不以 `v` 开头,避免触发插件正式发布。独立写权限任务拒绝已有 Release,创建非 latest 的预发布草稿,不覆盖资产;下载所有草稿资产与准备字节逐一对比后才公开。产物包括区分平台的原始二进制、manifest、编译/模块/版本/许可证证据、双构建证明、源码提交/运行标识和 SHA256SUMS。[发布后验证器](<../scripts/verify-caddy-release.mjs>) 用 GitHub 元数据及安装器实际 HTTPS/受限重定向下载链路复核 **所有公开资产**,成功后才输出仅供审核的目录。公开后验证失败会保留已公开的标记预发布版、使任务失败且不输出校验目录,不擅自删除或回滚 Release。CI 产物保留 14 天,Release 资产另行保存。
58
64
 
59
- 公开定制下载端点曾在请求 v2.11.6 时返回 v2.11.7,动态最新版不能成为信任来源。版本化 GitHub URL 仍可被仓库所有者替换;信任边界是审核过的精确大小/哈希,而非 URL 名称或远端自报校验和。fork 的目录只是审核候选,不会自动写入[生产组件表](<../src/caddy-component.ts>);正式安装仍等待维护者发布并审核官方分发。
60
-
61
- ### 官方分发需要独立的两阶段授权
65
+ 公开定制下载端点曾在请求 v2.11.6 时返回 v2.11.7,动态最新版不能成为信任来源。版本化 GitHub URL 仍可被仓库所有者替换;信任边界是审核过的精确大小/哈希,而非 URL 名称或远端自报校验和。fork 的目录只是审核候选,不会自动写入[生产组件表](<../src/caddy-component.ts>)。
62
66
 
63
- 本次仅准备发布通道,不执行发布。第一阶段要求维护者审核工作流 PR,并合入经过审核、受保护的 `main`。维护者必须预先配置 `caddy-component-release`:必需审核者规则、非空审核者列表和 **禁止自我审核**;管理员不得绕过环境批准。GitHub Actions 在唯一发布任务开始前提供真实人工批准;脚本另外读取实际环境策略,在首次修改 Release 前拒绝缺失、格式错误或未保护的环境。环境名称、main 祖先比较、合成策略测试都不能证明人工审核或批准。
67
+ ### 正式分发与安装开放
64
68
 
65
- 官方发布仅允许 **公开** 的 `saya-ch/dsh-mobile` 仓库和已有的固定非 `v` 稳定标签 `caddy-component-2.11.6-tencentcloud-0.4.3`。两个发布输入均默认 false;同时启用会在原生构建前明确失败。错误事件、分支、仓库或通道/标签身份不能发布。发布任务先检查 `/commits/TAG` 的 SHA 与 Actions `GITHUB_SHA` 完全一致,且 `/compare/main...SHA` 状态必须为 `identical` 或 `behind`(只证明属于 main/祖先关系,**不证明代码经过审核**)。准备资产和源码元数据使用同一个 SHA、同一次运行中的两平台原生构建,不复用旧运行的产物。
69
+ 正式组件由有仓库发布权限的维护者确认候选后手动发布,单维护者即可完成;不要求第二个人批准,也不要求预先配置 GitHub Environment。两个发布输入默认 false,PR 和普通构建不会发布。贡献者的实测可以按其版本、环境和实际验证范围作为验收依据,不要求在维护者本机重复搭建同一环境;不得将隔离测试或旧候选结果写成新二进制的公网签发、续期验收。
66
70
 
67
- 发布任务仍仅有 `contents: write`,API 使用 `GITHUB_TOKEN`,公开资产不带令牌或 API 请求头。[GitHub Get an environment 文档](<https://docs.github.com/en/rest/deployments/environments?apiVersion=2022-11-28#get-an-environment>) 通常要求 Actions read,但明确允许公开资源在没有该权限时读取。此处只采用公开资源例外作为前提,不声称已验证真实令牌能力:带认证请求的 401/403/404/5xx 或网络失败均在创建 Release 前关闭路径;不重试未认证请求、不提升权限、不增加发布密钥。维护者需确认仓库公开、端点可读以及环境保护配置;认证读取失败应报告缺少读取能力,不放宽策略。fork 审查通道使用 `caddy-component-review`,不要求官方环境策略。
71
+ 官方发布仅允许公开的 `saya-ch/dsh-mobile` 仓库和版本绑定的非 `v` 标签 `caddy-component-2.11.7-tencentcloud-0.4.3-r1`。维护者在已审核的 main 提交上创建该标签,然后手动运行现有工作流,显式设置 `publish_official_release=true`,保持 review 输入 false。已创建的标签不移动或覆盖;本次发布准备修正使用新的固定 `-r1` 修订标签,原标签保留。发布器检查标签 SHA 与本次 Actions SHA 相同、提交属于 main,并要求同一次运行的 Windows/Linux 原生双构建、测试和插件构建通过。错误事件、分支、仓库、互斥输入、失败原生任务或不匹配标签均拒绝发布。
68
72
 
69
- 满足以上前提并得到明确发布授权后(不是本次准备任务),维护者才可在已有稳定标签上手动启用 `publish_official_release=true`,保持 review 输入 false。仍只使用现有 Windows/Linux x64 原生矩阵:各独立构建两次,验证原生安装/TLS/API/WSS/不导入信任,并运行完整测试与构建。通用发布器使用 `--draft --prerelease=false --latest=false`,拒绝已占用 Release,不更新、覆盖、删除或破坏性重试;上传并下载 **全部 16 个资产**、逐字节比较,再以 `--draft=false --prerelease=false --latest=false` 公开。fork 仍保留预发布且非 latest 的标志。官方验证允许 `/releases/latest` 的 404(确实没有 latest),但拒绝该组件成为 latest,以及其他错误或格式错误的成功响应。组件不替换 latest 插件,不自动发布 npm/APK。公开后验证失败保留公开 Release、任务失败、不产生候选,不进行破坏性回滚。
73
+ 唯一发布任务使用 `contents: write` 和现有 `GITHUB_TOKEN`,创建非 latest 的正式组件分发,不发布 npm/APK。已占用的 Release 不覆盖;全部 16 个草稿资产下载后逐字节比较,再公开并通过安装器实际 HTTPS 下载链路独立复核公开元数据、大小和哈希。验证失败保留实际已发布状态并使任务失败,不擅自删除或破坏性重试。
70
74
 
71
- prepare/verify CLI 原有严格签名默认 review;只有可选尾参数 `--channel official`(或 `--channel review`)选择共享的精确身份校验。官方 `COMPONENT-RELEASE.json` 使用 `managed-caddy-component-official`;全部资产的公开元数据/大小/哈希及实际 HTTPS/受限重定向检查通过后,才生成 `official-candidate-not-production`。两通道索引与候选均保持 `productionCatalogEnabled: false`,输出有界、独占创建且不覆盖。
75
+ 组件更新分两阶段:先发布并验证官方分发,再通过单独审核的插件目录变更,把实际公开二进制的来源、大小和 SHA-256 固定到 `CADDY_COMPONENT_RELEASES`。0.6.9 开放 Windows/Linux x64 的固定目录;将来发布新的组件资产不会自动更换用户运行的版本。
72
76
 
73
- 第二阶段在 **以后** 进行:独立验证实际发布的官方字节,再提交单独审核的目录 PR,将精确来源/大小/哈希硬编码到 `CADDY_COMPONENT_RELEASES`。本次准备中它仍严格为 `Object.freeze({})`。远端候选 JSON 不是运行时信任或配置。仅支持 Windows/Linux x64、腾讯云 DNS;不要把私有预览替换为目录为空的官方插件包后期待托管重启正常工作。人工真实网络证据与最终候选 CI 分开记录;隔离测试或该人工证据都不证明证书续期通过。
77
+ prepare/verify CLI 默认 review;尾参数 `--channel official` 选择官方身份校验。官方验证成功后生成 `official-candidate-not-production`,仍保持 `productionCatalogEnabled: false`:该 JSON 是审核输入,不是运行时配置,也不会自行启用安装。贡献者的实网反馈按其版本与部署范围采用。
74
78
 
75
- 隔离测试仅用回环随机端口、私有内部 CA 和 `skip_install_trust`,不修改系统信任、生产 DNS 或现有代理。**不等于真实公网 DNS-01/ACME 签发或公网路由验收**;后者需要另行授权专用测试域名、最小权限 DNS 凭据和可达入口。
79
+ 隔离验证使用回环随机端口、私有内部 CA 与 `skip_install_trust`,不修改系统信任、生产 DNS 或现有代理。本次固定组件尚未取得另行的公网 DNS-01/ACME 签发与长周期续期验收;证书签发、续期、公网可达和实际页面/API/流传递应分别验证,不能由隔离 CI 或旧候选的实测替代。
package/docs/README.md CHANGED
@@ -12,20 +12,20 @@
12
12
  | 在已有 VPS 上新部署 FRP 与 HTTPS 入口 | [自建 FRP](SELF_HOSTED_FRP.md) | [Self-hosted FRP](SELF_HOSTED_FRP.en.md) |
13
13
  | 复用已经运行的 frps,不自动修改服务器 | [接入既有 frps](ATTACH_EXISTING_FRPS.md) | [Existing frps](ATTACH_EXISTING_FRPS.en.md) |
14
14
  | 复用 Lucky、Nginx 或 Caddy 的公开 HTTPS 入口 | [自有反向代理](SELF_HOSTED_ORIGIN.md) | [Own reverse proxy](SELF_HOSTED_ORIGIN.en.md) |
15
+ | 由插件安装 Caddy 并管理 HTTPS 证书(0.6.9 起) | [托管 Caddy](CADDY_MANAGED.md) | [Managed Caddy](CADDY_MANAGED.en.md) |
15
16
  | 页面加载慢、实时连接反复重连或长操作超时 | [慢链路与重连](SLOW_CONNECTIONS.md) | [Slow links and reconnection](SLOW_CONNECTIONS.en.md) |
16
17
  | 保留电脑皮肤,排查移动页面的背景与可读性 | [皮肤与移动布局](SKIN_COMPATIBILITY.md) | [Skins and mobile layout](SKIN_COMPATIBILITY.en.md) |
17
18
 
18
19
  在 Android App 中连接公开隧道或自建入口时请选择 **远程访问**。公开受信任证书也可用手机浏览器配对;既有 frps 的自签入口需要 **0.4.6 或更高版本 App** 固定远程 CA。各通道仍要求 DSH Mobile 配对,不能直接暴露普通 DSH 或私有 HTTP 后端。
19
20
 
20
- ## 0.6.0 新增功能 / New features
21
+ ## 页面与网络设置 / Page and network settings
21
22
 
22
- 以下指南介绍 0.6.0 的模块管理与局域网高级设置,以及尚未开放安装的托管 Caddy 模式。
23
+ 按需选择移动页面模块和受信任的局域网来源;这些设置不会创建 VPN,也不会自动刷新当前会话。
23
24
 
24
25
  | 功能 | 中文 | English | 使用状态 |
25
26
  | --- | --- | --- | --- |
26
27
  | 为移动页面选择加载模块 | [模块设置](CLIENT_MODULES.md) | [Module settings](CLIENT_MODULES.en.md) | 电脑默认与设备覆盖分别保存,不自动刷新会话 |
27
28
  | 允许受控路由网络访问 LAN Gateway | [额外可信网段](TRUSTED_NETWORKS.md) | [Trusted networks](TRUSTED_NETWORKS.en.md) | 不建立 VPN,不自动扩大防火墙规则 |
28
- | 由插件管理 Caddy HTTPS 上游 | [托管 Caddy](CADDY_MANAGED.md) | [Managed Caddy](CADDY_MANAGED.en.md) | 尚无可信固定二进制分发,安装与连接仍禁用 |
29
29
 
30
30
  ## 开发与安全 / Development and security
31
31
 
@@ -29,7 +29,7 @@ The publicly trusted entry in this guide requires Android app 0.3.3 or later for
29
29
 
30
30
  Before deployment, the panel lists every VPS change. It installs Caddy from its official APT source; public IPv4 mode additionally installs a Python virtual environment, Certbot and the renewal timer. It creates the frps service/configuration, Caddy snippet and one import line, and allows the FRP control port plus 80/tcp and 443/tcp when UFW is active. An existing `dsh-mobile` system user is reused and retained during cleanup; a user created by this deployment is removed during cleanup. An existing DSH Mobile import is normalized to one line. Merge custom sites manually; the version-specific behavior is described below.
31
31
 
32
- The unreleased correction recognizes a replaceable default Caddyfile by its complete file hash. A default site with additional sites, or a file carrying only the legacy management marker, requires manual merging. The currently published plugin can misidentify such files during automatic deployment; use manual deployment and retain a backup when custom sites exist. Remove legacy whole-file DSH Mobile sites manually during cleanup while preserving other content.
32
+ Since plugin 0.6.4, automatic deployment recognizes a replaceable default Caddyfile by its complete file hash. A default site with additional sites, or a file carrying only the legacy management marker, requires manual merging. Retain a backup when custom sites exist and do not replace the entire file with the generated template. Remove legacy whole-file DSH Mobile sites manually during cleanup while preserving other content.
33
33
 
34
34
  ## Verify the host keys every time
35
35
 
@@ -33,7 +33,7 @@
33
33
 
34
34
  点部署按钮前,面板会列出自动部署将在 VPS 上做的全部改动:从官方 APT 源安装 Caddy;仅在公网 IPv4 模式安装 Python venv、Certbot 和证书续期定时器;创建 `dsh-mobile` 系统用户、frps 配置与服务、Caddy 片段和主文件里的一行 import;如果 UFW 已启用,则放行 FRP 控制端口与 80/443。已存在的系统用户会复用并在清理时保留,本次部署创建的用户则会在清理时删除。主 Caddyfile 已有 DSH Mobile import 时,只整理该行;已有自定义站点时请手动合并,版本差异见下方说明。
35
35
 
36
- 待发布修正使用完整默认文件哈希识别可替换的 Caddyfile;保留默认站点后追加其他站点的文件,以及只有旧版管理标记的文件,都需要手动合并。当前已发布插件的自动部署尚有误判覆盖风险,已有自定义站点时请使用手动部署并保留备份。清理旧版整文件配置时也应自行移除 DSH Mobile 站点,保留其他内容。
36
+ 从插件 0.6.4 起,自动部署使用完整默认文件哈希识别可替换的 Caddyfile;保留默认站点后追加其他站点的文件,以及只有旧版管理标记的文件,都需要手动合并。已有自定义站点时保留备份,不要用生成模板替换整个文件。清理旧版整文件配置时也应自行移除 DSH Mobile 站点,保留其他内容。
37
37
 
38
38
  ## 主机指纹核对(每次必做)
39
39
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  > This provider ships from **0.4.2** onward.
6
6
 
7
+ This guide covers the default **external proxy** mode, where you manage public HTTPS. To have the plugin install and manage Caddy and certificates instead, choose [Managed Caddy](CADDY_MANAGED.en.md) in the same panel. Its configuration and readiness checks are separate.
8
+
7
9
  ## When to use it
8
10
 
9
11
  Use an existing Lucky, Nginx or Caddy HTTPS proxy rather than a tunnel. Under **Mobile Access → Remote → Self-hosted connection → Own reverse proxy**, DSH Mobile creates a separate private HTTP backend with device authentication:
@@ -51,7 +53,7 @@ For a separate proxy host, configure routing and a firewall rule limited to its
51
53
 
52
54
  ## State and phone verification
53
55
 
54
- **Backend listening proves the local listener only, not public HTTPS, certificates or WebSocket connectivity.** This provider's diagnostics do not probe the public origin.
56
+ **Backend listening proves the local listener only, not public HTTPS, certificates or WebSocket connectivity.** External-proxy diagnostics do not probe the public origin.
55
57
 
56
58
  Generate a remote pairing QR code and use **Remote access** in Android app **0.4.0 or later**, not Local network. A publicly trusted origin also supports mobile-browser pairing. Verify from the intended external network:
57
59
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  > 本功能自 **0.4.2** 起随包发布。
6
6
 
7
+ 本页介绍默认的**自备代理**模式,由你维护公网 HTTPS 代理。希望插件按需安装并管理 Caddy 与证书时,请使用同一面板中的[托管 Caddy](CADDY_MANAGED.md)模式;两者的配置与就绪检查不同。
8
+
7
9
  ## 适用场景与安全边界
8
10
 
9
11
  你已有 Lucky、Nginx、Caddy 等 HTTPS 反向代理,不需要 Funnel、cpolar 或 FRP 隧道。进入电脑端 **移动访问 → 远程 → 自建连接 → 自有反向代理**,让插件提供一个独立的、带设备配对认证的私有 HTTP 后端:
@@ -17,7 +19,7 @@
17
19
  - **只有第一段允许暴露公网。HTTP 后端不能做公网端口映射。** HTTP 段仅适用于可信私网;局域网其他设备上的代理也会接触明文流量。
18
20
  - 后端不是裸 DSH:仍执行设备配对、认证、归一化后精确匹配的 Host/Origin 校验、CSRF、Secure Cookie 和 WebSocket 路径策略。没有配对的访问不能直接进入 DSH。**不要用浏览器直接打开这个明文 HTTP 后端做验证**:它只支持由指定 HTTPS 公网地址代理的请求;直接打开时 Host 不匹配,Cookie 与 CSRF 流程也可能失效。
19
21
  - 不要把公网反代目标改成 DSH 的 WebServer 端口(默认 3080),也不要反代现有 LAN HTTPS 3443。新后端默认 3444,**明确拒绝 3443 和 3080**,并拒绝 1024 以下需要特权的端口。注意 3444 同时是 cloudflared 命名隧道的默认转发端口:同一时刻只有一个远程提供方能运行,但切换到本提供方时请确认该端口已释放。DSH 与 LAN 的原有设置不变。
20
- - 公网 TLS、证书续期、DNS、路由和代理由你管理。插件不会安装隧道组件、部署 VPS、修改代理/防火墙/路由器,也不会为该提供方发起公网探测。
22
+ - 公网 TLS、证书续期、DNS、路由和代理由你管理。插件不会安装隧道组件、部署 VPS、修改代理/防火墙/路由器,也不会在自备代理模式中主动探测公网入口。
21
23
 
22
24
  ## 填写四项设置
23
25
 
@@ -52,7 +54,7 @@ CIDR 限制针对 TCP 连接的**直接来源地址**,不是手机 IP,也不
52
54
 
53
55
  ## 状态与手机验证
54
56
 
55
- **“后端已监听”只证明本地 HTTP 监听成功,不代表公网 HTTPS、证书或 WebSocket 已成功。** 本提供方的连接诊断也只报告本地状态,不自动向公网地址探测。
57
+ **“后端已监听”只证明本地 HTTP 监听成功,不代表公网 HTTPS、证书或 WebSocket 已成功。** 自备代理模式的连接诊断也只报告本地状态,不自动向公网地址探测。
56
58
 
57
59
  在电脑端点击 **生成远程配对二维码**,再在 Android App **0.4.0 或更高版本**的 **远程访问** 流程扫码(不要使用 LAN 扫码流程)。现场验证中 0.3.16 会拒绝自有域名配对;现有自有域名逻辑保留 HTTPS 自定义端口,无需为此功能修改 APK。
58
60
 
package/lib/client.js CHANGED
@@ -587,7 +587,7 @@ window.__ModuleLoader__.load({
587
587
  trustedNetworksFailed: "Could not read this setting. Retry after reopening this section.",
588
588
  trustedNetworksInvalid: "Could not save. Enter valid network CIDRs, for example 11.24.0.0/24, and check that DSH can write its private setup file.",
589
589
  cpolarDownloadFailed: "Could not download cpolar from its official site. Check this computer’s network or proxy, then retry.",
590
- caddyUnavailable: "The fixed Caddy component is not published for this candidate. Keep using an existing reverse proxy or another remote provider; unverified downloads will not run.",
590
+ caddyUnavailable: "Managed Caddy is not available for this system or architecture. Use an existing reverse proxy or another remote provider; unverified downloads will not run.",
591
591
  caddyVerified: "Managed HTTPS entry verified for this computer.",
592
592
  caddyInstalled: "Caddy component installed; the connection is not yet verified.",
593
593
  caddyConnectionFailed: "Managed HTTPS connection was not established. Check the domain, public inbound access, port mapping and DNS credentials.",
@@ -617,7 +617,7 @@ window.__ModuleLoader__.load({
617
617
  trustedNetworksFailed: "Impossibile leggere questa impostazione. Riapri la sezione e riprova.",
618
618
  trustedNetworksInvalid: "Salvataggio non riuscito. Inserisci CIDR validi, ad esempio 11.24.0.0/24, e verifica che DSH possa scrivere il file privato di configurazione.",
619
619
  cpolarDownloadFailed: "Impossibile scaricare cpolar dal sito ufficiale. Controlla rete o proxy del computer e riprova.",
620
- caddyUnavailable: "Il componente Caddy fisso non è pubblicato per questa versione candidata. Usa il tuo proxy o un altro provider; i download non verificati non vengono eseguiti.",
620
+ caddyUnavailable: "Caddy gestito non è disponibile per questo sistema o architettura. Usa il tuo proxy o un altro provider; i download non verificati non vengono eseguiti.",
621
621
  caddyVerified: "Ingresso HTTPS gestito verificato per questo computer.",
622
622
  caddyInstalled: "Componente Caddy installato; connessione non ancora verificata.",
623
623
  caddyConnectionFailed: "Connessione HTTPS gestita non stabilita. Controlla dominio, accesso pubblico, porte e credenziali DNS.",
@@ -638,7 +638,7 @@ window.__ModuleLoader__.load({
638
638
  });
639
639
  Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
640
640
  cpolarDownloadFailed: "无法从 cpolar 官网下载组件。请检查这台电脑的网络或代理,然后重试。",
641
- caddyUnavailable: "本次候选尚未发布固定 Caddy 组件。请继续使用已有反向代理或其他远程方式;不会执行未经校验的下载。",
641
+ caddyUnavailable: "当前系统或架构暂不支持托管 Caddy。请使用已有反向代理或其他远程方式;不会执行未经校验的下载。",
642
642
  caddyVerified: "托管 HTTPS 入口已验证为这台电脑。",
643
643
  caddyInstalled: "Caddy 组件已安装,远程连接尚未验证。",
644
644
  caddyConnectionFailed: "托管 HTTPS 连接未建立,请检查域名、公网入站、端口映射和 DNS 凭据。",
@@ -1236,12 +1236,12 @@ window.__ModuleLoader__.load({
1236
1236
  caddyModeExternal: "I already run my own reverse proxy",
1237
1237
  caddySectionTitle: "Managed Caddy upstream",
1238
1238
  caddyInstall: "Install managed Caddy",
1239
- caddyInstallNote: "Downloaded from the official Caddy custom build (caddy core + Tencent Cloud DNS plugin for DNS-01), verified by checksum, stored in the plugin private directory. No system service, PATH entry, or startup item is added.",
1239
+ caddyInstallNote: "Downloads the pinned Caddy core + Tencent Cloud DNS build published by DSH Mobile, verifies its size and SHA-256, and stores it in the plugin private directory. No system service, PATH entry, or startup item is added.",
1240
1240
  caddyDomain: "Public domain",
1241
1241
  caddyDnsProvider: "DNS provider (DNS-01)",
1242
1242
  caddySecretId: "Tencent Cloud SecretId",
1243
1243
  caddySecretKey: "Tencent Cloud SecretKey",
1244
- caddyListenPort: "Public HTTPS port",
1244
+ caddyListenPort: "Local HTTPS port",
1245
1245
  caddySaveConnect: "Save and start Caddy",
1246
1246
  caddyPurge: "Remove managed Caddy and settings",
1247
1247
  caddyReady: "Managed Caddy is running; certificates are issued and renewed automatically.",
@@ -1295,12 +1295,12 @@ window.__ModuleLoader__.load({
1295
1295
  caddyModeExternal: "Uso già il mio reverse proxy",
1296
1296
  caddySectionTitle: "Upstream Caddy gestito",
1297
1297
  caddyInstall: "Installa Caddy gestito",
1298
- caddyInstallNote: "Scaricato dalla build personalizzata ufficiale di Caddy (core + plugin DNS Tencent Cloud per DNS-01), verificato tramite checksum e conservato nella directory privata del plugin. Non aggiunge servizi di sistema, voci PATH o avvio automatico.",
1298
+ caddyInstallNote: "Scarica la build Caddy + DNS Tencent Cloud a versione fissa pubblicata da DSH Mobile, verifica dimensione e SHA-256 e la conserva nella directory privata del plugin. Non aggiunge servizi di sistema, voci PATH o avvio automatico.",
1299
1299
  caddyDomain: "Dominio pubblico",
1300
1300
  caddyDnsProvider: "Provider DNS (DNS-01)",
1301
1301
  caddySecretId: "SecretId Tencent Cloud",
1302
1302
  caddySecretKey: "SecretKey Tencent Cloud",
1303
- caddyListenPort: "Porta HTTPS pubblica",
1303
+ caddyListenPort: "Porta HTTPS locale",
1304
1304
  caddySaveConnect: "Salva e avvia Caddy",
1305
1305
  caddyPurge: "Rimuovi Caddy gestito e impostazioni",
1306
1306
  caddyReady: "Caddy gestito in esecuzione; i certificati vengono emessi e rinnovati automaticamente.",
@@ -1354,12 +1354,12 @@ window.__ModuleLoader__.load({
1354
1354
  caddyModeExternal: "我已自行配置反向代理",
1355
1355
  caddySectionTitle: "托管 Caddy 上游",
1356
1356
  caddyInstall: "安装托管 Caddy",
1357
- caddyInstallNote: "从 Caddy 官方定制构建下载(caddy 核心 + 腾讯云 DNS 插件,用于 DNS-01),经校验和验证后保存在插件私有目录。不添加系统服务、PATH 条目或开机启动项。",
1357
+ caddyInstallNote: "下载 DSH Mobile 发布的固定 Caddy 核心 + 腾讯云 DNS 构建,校验精确大小和 SHA-256 后保存在插件私有目录。不添加系统服务、PATH 条目或开机启动项。",
1358
1358
  caddyDomain: "公网域名",
1359
1359
  caddyDnsProvider: "DNS 提供商(DNS-01)",
1360
1360
  caddySecretId: "腾讯云 SecretId",
1361
1361
  caddySecretKey: "腾讯云 SecretKey",
1362
- caddyListenPort: "公网 HTTPS 端口",
1362
+ caddyListenPort: "本机 HTTPS 端口",
1363
1363
  caddySaveConnect: "保存并启动 Caddy",
1364
1364
  caddyPurge: "移除托管 Caddy 与配置",
1365
1365
  caddyReady: "托管 Caddy 正在运行;证书会自动签发并续期。",
@@ -7564,9 +7564,60 @@ window.__ModuleLoader__.load({
7564
7564
  /** Resolve the next-step text for a remote-controller diagnostic. */
7565
7565
  function diagnosticControllerAction(code, locale, fallback) {
7566
7566
  if (typeof code !== "string") return fallback;
7567
- const key = DIAGNOSTIC_CONTROLLER_ACTION_OVERRIDES[code] ?? (DIAGNOSTIC_CONTROLLER_ACTION_CODES.has(code) ? REMOTE_ERROR_MESSAGE_KEYS[code] : void 0);
7567
+ const key = (Object.prototype.hasOwnProperty.call(DIAGNOSTIC_CONTROLLER_ACTION_OVERRIDES, code) ? DIAGNOSTIC_CONTROLLER_ACTION_OVERRIDES[code] : void 0) ?? (DIAGNOSTIC_CONTROLLER_ACTION_CODES.has(code) ? REMOTE_ERROR_MESSAGE_KEYS[code] : void 0);
7568
7568
  return key === void 0 ? fallback : controlTranslator(locale)(key);
7569
7569
  }
7570
+ /**
7571
+ * Localize a diagnostic entry, retaining Host text for unknown diagnostic reasons.
7572
+ * @param entry Diagnostic fields received from the control API.
7573
+ * @param locale Language of the mounted control panel.
7574
+ * @param versions Optional version fields from the diagnostic response.
7575
+ * @returns Detail and next-step text shared by the visible card and copied report.
7576
+ */
7577
+ function localizedDiagnosticEntryCopy(entry, locale, versions) {
7578
+ const t = controlTranslator(locale);
7579
+ const localeTag = locale === "it" ? "it-IT" : locale === "zh" ? "zh-CN" : "en-US";
7580
+ const serverCopy = diagnosticServerCopy(entry);
7581
+ const reason = typeof entry.reason === "string" ? entry.reason : "";
7582
+ const facts = entry.facts !== null && typeof entry.facts === "object" ? entry.facts : {};
7583
+ const copyReason = facts.viaProxy === true && (reason === "remote-ready" || reason === "remote-rate-limited") ? `${reason}-via-proxy` : reason;
7584
+ const catalog = DIAGNOSTIC_REASON_MESSAGES[locale];
7585
+ const templates = Object.prototype.hasOwnProperty.call(catalog, copyReason) ? catalog[copyReason] : void 0;
7586
+ if (templates === void 0) return serverCopy;
7587
+ const providerNames = {
7588
+ tailscale: "Tailscale Funnel",
7589
+ cpolar: "cpolar",
7590
+ cloudflared: "cloudflared",
7591
+ frp: t("frpName"),
7592
+ origin: t("originName")
7593
+ };
7594
+ const providerId = typeof facts.provider === "string" ? facts.provider : "";
7595
+ const controllerCode = typeof facts.controllerCode === "string" ? facts.controllerCode : "";
7596
+ const controllerKey = Object.prototype.hasOwnProperty.call(REMOTE_ERROR_MESSAGE_KEYS, controllerCode) ? REMOTE_ERROR_MESSAGE_KEYS[controllerCode] : void 0;
7597
+ const values = {
7598
+ provider: providerNames[providerId] ?? "",
7599
+ latencyMs: typeof facts.latencyMs === "number" && Number.isFinite(facts.latencyMs) ? new Intl.NumberFormat(localeTag).format(facts.latencyMs) : "",
7600
+ interfaceName: typeof facts.interfaceName === "string" ? facts.interfaceName : "",
7601
+ endpointSuffix: typeof facts.endpointSuffix === "string" ? facts.endpointSuffix : "",
7602
+ controllerCode
7603
+ };
7604
+ const interpolate = (template) => template.replace(/\{(\w+)\}/gu, (_match, key) => values[key] ?? "");
7605
+ let detail = reason === "remote-controller-error" && controllerCode === "" ? t("remoteError") : interpolate(templates[0]);
7606
+ let action = interpolate(templates[1]);
7607
+ if (reason === "versions-current") {
7608
+ const fields = versions !== null && typeof versions === "object" ? versions : {};
7609
+ if ([
7610
+ fields.plugin,
7611
+ fields.dsh,
7612
+ fields.minimumAndroidApp
7613
+ ].every((value) => typeof value === "string")) detail += ` plugin ${String(fields.plugin)}, DSH ${String(fields.dsh)}, Android ${String(fields.minimumAndroidApp)}.`;
7614
+ }
7615
+ if (reason === "remote-controller-error") action = diagnosticControllerAction(controllerCode, locale, controllerKey === void 0 ? action : t(controllerKey));
7616
+ return {
7617
+ detail,
7618
+ action
7619
+ };
7620
+ }
7570
7621
  function installControl() {
7571
7622
  /** The table above, translated, for the status repaint. */
7572
7623
  const translatedRemoteErrors = () => Object.fromEntries(Object.entries(REMOTE_ERROR_MESSAGE_KEYS).map(([code, key]) => [code, t(key)]));
@@ -10697,46 +10748,7 @@ window.__ModuleLoader__.load({
10697
10748
  "phone-network": "diagnosticLabelPhone"
10698
10749
  };
10699
10750
  const statusOf = (entry) => normalizeDiagnosticStatus(entry.status);
10700
- const localizedEntryCopy = (entry) => {
10701
- const serverCopy = diagnosticServerCopy(entry);
10702
- const reason = typeof entry.reason === "string" ? entry.reason : "";
10703
- const facts = entry.facts !== null && typeof entry.facts === "object" ? entry.facts : {};
10704
- const copyReason = facts.viaProxy === true && (reason === "remote-ready" || reason === "remote-rate-limited") ? `${reason}-via-proxy` : reason;
10705
- const templates = DIAGNOSTIC_REASON_MESSAGES[locale][copyReason];
10706
- if (templates === void 0) return serverCopy;
10707
- const providerNames = {
10708
- tailscale: "Tailscale Funnel",
10709
- cpolar: "cpolar",
10710
- cloudflared: "cloudflared",
10711
- frp: t("frpName"),
10712
- origin: t("originName")
10713
- };
10714
- const providerId = typeof facts.provider === "string" ? facts.provider : "";
10715
- const controllerKey = typeof facts.controllerCode === "string" ? REMOTE_ERROR_MESSAGE_KEYS[facts.controllerCode] : void 0;
10716
- const values = {
10717
- provider: providerNames[providerId] ?? "",
10718
- latencyMs: typeof facts.latencyMs === "number" && Number.isFinite(facts.latencyMs) ? new Intl.NumberFormat(localeTag).format(facts.latencyMs) : "",
10719
- interfaceName: typeof facts.interfaceName === "string" ? facts.interfaceName : "",
10720
- endpointSuffix: typeof facts.endpointSuffix === "string" ? facts.endpointSuffix : "",
10721
- controllerCode: controllerKey === void 0 ? "" : t(controllerKey)
10722
- };
10723
- const interpolate = (template) => template.replace(/\{(\w+)\}/gu, (_match, key) => values[key] ?? "");
10724
- let detail = interpolate(templates[0]);
10725
- let action = interpolate(templates[1]);
10726
- if (reason === "versions-current") {
10727
- const versions = data.versions !== null && typeof data.versions === "object" ? data.versions : {};
10728
- if ([
10729
- versions.plugin,
10730
- versions.dsh,
10731
- versions.minimumAndroidApp
10732
- ].every((value) => typeof value === "string")) detail += ` plugin ${String(versions.plugin)}, DSH ${String(versions.dsh)}, Android ${String(versions.minimumAndroidApp)}.`;
10733
- }
10734
- if (reason === "remote-controller-error") action = diagnosticControllerAction(facts.controllerCode, locale, action);
10735
- return {
10736
- detail,
10737
- action
10738
- };
10739
- };
10751
+ const localizedEntryCopy = (entry) => localizedDiagnosticEntryCopy(entry, locale, data.versions);
10740
10752
  const appendGroup = (label, groupEntries) => {
10741
10753
  if (groupEntries.length === 0) return;
10742
10754
  const group = element("section", "dsh-mobile-control__diagnostic-group");
@@ -12368,6 +12380,7 @@ window.__ModuleLoader__.load({
12368
12380
  exports.installCustomAssets = installCustomAssets;
12369
12381
  exports.installDshLanguageBoundSurface = installDshLanguageBoundSurface;
12370
12382
  exports.installMobileNativeBack = installMobileNativeBack;
12383
+ exports.localizedDiagnosticEntryCopy = localizedDiagnosticEntryCopy;
12371
12384
  exports.mobileBackBlockedMessage = mobileBackBlockedMessage;
12372
12385
  exports.normalizeDiagnosticOverall = normalizeDiagnosticOverall;
12373
12386
  exports.normalizeDiagnosticStatus = normalizeDiagnosticStatus;