From 82411d92ea222d1993d7bb2ccfa8c46313ce5b23 Mon Sep 17 00:00:00 2001 From: Qiu Yuzhou Date: Thu, 1 Oct 2026 09:33:08 +0800 Subject: [PATCH] docs(design): publish terminal command address design for #72 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grill-with-docs 会话产物:交互设计与测试边界定稿,spec 已发布为 issue #72;CONTEXT.md 增加 Terminal command address 词条。 --- CONTEXT.md | 1 + docs/design/terminal-command-address-spec.md | 83 ++++++++++++++++++++ docs/design/terminal-command-address.md | 45 +++++++++++ 3 files changed, 129 insertions(+) create mode 100644 docs/design/terminal-command-address-spec.md create mode 100644 docs/design/terminal-command-address.md diff --git a/CONTEXT.md b/CONTEXT.md index b11995fd..e4fd1b5a 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -32,6 +32,7 @@ - **Listener mode**: The user-facing choice that determines which address families the local proxy listeners accept: this Mac only (IPv4 loopback), all IPv4 interfaces, all IPv4 and IPv6 interfaces, or all IPv6 interfaces only. One mode applies to both the local SOCKS and HTTP proxy inbounds. _Avoid_: Listen scope. - **HTTP proxy environment command**: An HTTP-only terminal command that sets lowercase `http_proxy` and `https_proxy` for this Mac's local HTTP proxy inbound. It always targets a loopback address for the selected listener mode. - **Terminal proxy environment commands**: Shell-specific commands for zsh/bash and fish that set lowercase `http_proxy` and `https_proxy` through the local HTTP inbound, `all_proxy` through the local SOCKS inbound, and `no_proxy` for `localhost`, `127.0.0.1`, `::1`, and `.local` hostnames. The proxy endpoints use loopback addresses selected by listener mode, and the commands affect child processes that honor these variables. +- **Terminal command address**: The user-selected IP address used by both HTTP and SOCKS endpoints in copied terminal proxy environment commands. It defaults to a compatible loopback address; non-local listener modes also offer addresses of enabled Wi-Fi and Ethernet interfaces in the accepted address families, and the selection lasts only for the current app session without changing listener or system-proxy settings. - **Proxy runtime**: The per-user background host process, independent of the GUI's lifetime, that runs the external tunnel service and loads the optional generated ACL sidecar. - **Legacy configuration**: The server list and preferences persisted by the frozen implementation in `Legacy/`. - **Legacy import**: The user-confirmed, read-only translation of a Legacy configuration's server records into a separate manual group in 2.0. It migrates no preferences, activates no target, and neither starts proxying nor alters the Legacy source. diff --git a/docs/design/terminal-command-address-spec.md b/docs/design/terminal-command-address-spec.md new file mode 100644 index 00000000..38e9616a --- /dev/null +++ b/docs/design/terminal-command-address-spec.md @@ -0,0 +1,83 @@ +## Problem Statement + +首页“复制代理环境变量设置命令”目前只能生成指向回环地址的命令。用户已经选择允许其他设备访问的监听方式时,仍需手动修改命令中的 HTTP、HTTPS 和 SOCKS 地址,也无法从首页辨认本机可用的 Wi-Fi 或 Ethernet 地址。 + +## Solution + +在非“仅本机”监听方式下,为首页命令卡片增加一个单选 IP 下拉框,默认选中兼容的回环地址。候选仅来自 loopback 和当前启用的 Wi-Fi、Ethernet 接口,并按监听地址族过滤。候选显示为 `{IP} - {name}`,名称采用系统接口本地化名称,缺失时使用 BSD 名称。两种 shell 的所有代理端点共用该选择,选择失效时自动回退到回环地址。 + +## User Stories + +1. 作为应用用户,我希望非本机监听时能在首页选择命令地址,以便直接复制包含所需 IP 的命令。 +2. 作为应用用户,我希望仅本机监听时隐藏地址下拉框,以便只看到适用于当前监听方式的操作。 +3. 作为应用用户,我希望默认使用回环地址,以便继续使用现有本机终端操作习惯。 +4. 作为 IPv4 用户,我希望候选仅包含 IPv4 地址,以便避免复制监听器不接受的端点。 +5. 作为双栈用户,我希望同时选择 IPv4 和 IPv6 地址,以便适应目标设备使用的地址族。 +6. 作为双栈用户,我希望两个回环地址都可选且默认使用 127.0.0.1,以便保留熟悉的默认值。 +7. 作为仅 IPv6 用户,我希望默认地址为 ::1 且候选只有 IPv6,以便得到兼容的命令。 +8. 作为 Wi-Fi 用户,我希望候选包含当前启用 Wi-Fi 接口的单播地址,以便通过无线网络使用代理。 +9. 作为 Ethernet 用户,我希望候选包含当前启用 Ethernet 接口的单播地址,以便通过有线网络使用代理。 +10. 作为应用用户,我希望排除 VPN 和其他接口类型,以便候选只包含明确支持的三类接口。 +11. 作为应用用户,我希望每项同时显示 IP 和接口名称,以便辨认地址属于哪个网络接口。 +12. 作为应用用户,我希望接口名称采用系统本地化名称,以便与系统中的接口称呼对应。 +13. 作为应用用户,我希望名称缺失时显示 BSD 接口名,以便仍能辨认候选。 +14. 作为应用用户,我希望回环项置顶、其他项稳定排序并合并同一接口的重复地址,以便快速查找候选。 +15. 作为应用用户,我希望不出现 IPv6 链路本地地址和通配绑定地址,以便避免复制需要额外接口作用域或不能用作目标的地址。 +16. 作为 zsh/bash 用户,我希望复制的 HTTP、HTTPS 和 SOCKS 端点都使用选中地址,以便一次设置全部代理变量。 +17. 作为 fish 用户,我希望复制命令使用同一地址并保持 fish 语法,以便直接在 fish 中执行。 +18. 作为 IPv6 用户,我希望生成的 URL 正确包含地址方括号和端口,以便应用能解析代理端点。 +19. 作为应用用户,我希望选择命令地址不改变监听方式或系统代理,以便该操作仅用于生成命令。 +20. 作为应用用户,我希望离开首页再返回时保留本次选择,以便减少重复操作。 +21. 作为应用用户,我希望应用重启后恢复默认回环地址,以便不沿用先前网络环境的选择。 +22. 作为切换网络的用户,我希望候选随网络变化刷新,以便选择当前接口地址。 +23. 作为切换监听方式的用户,我希望不兼容的选择自动回退,以便界面始终符合已保存的监听配置。 +24. 作为应用用户,我希望复制前重新检查地址并在失效时复制回环命令,以便不复制已消失的地址。 +25. 作为应用用户,我希望无法枚举接口时仍能选择回环并复制,以便基础本机功能保持可用。 +26. 作为应用用户,我希望代理未运行时仍能复制命令,以便预先准备终端配置。 +27. 作为应用用户,我希望复制按钮的提示命令与实际剪贴板内容采用相同选择,以便理解即将复制的内容。 + +## Implementation Decisions + +- 扩展 ProxyControlWorkflow 的 UI-facing 投影与用户操作:提供候选地址、选中地址、选择器可见性,以及基于同一选择生成的两种 shell 命令。呈现层负责下拉框和现有剪贴板副作用,不自行访问底层控制器或拼接命令。 +- 应用会话持有选择,生命周期长于首页视图;不写入持久化设置。地址身份包含 BSD 接口身份与规范化 IP,不以本地化名称作为身份。 +- 一个下拉框放在两个复制按钮上方,仅“仅本机”监听方式隐藏。监听方式与端口取已保存的设置;命令选择不改变代理运行状态、监听设置或系统代理。 +- 默认地址前三种监听方式为 127.0.0.1,仅所有 IPv6 接口为 ::1。双栈提供两个回环地址。IPv4-only、双栈和 IPv6-only 分别过滤对应地址族。 +- 候选仅包含 loopback,以及当前启用 Wi-Fi、Ethernet 接口的单播地址。排除 VPN、其他虚拟接口和其他接口类型,不以接口名前缀或通用链路类型代替严格类型判断。 +- 系统适配器以 SCNetworkInterfaceGetInterfaceType 的 IEEE80211、Ethernet 类型识别非回环接口,以 IFF_LOOPBACK 识别回环。无法确认属于允许类型的非回环接口排除。 +- 本地化接口名称使用 SCNetworkInterfaceGetLocalizedDisplayName,通过 BSD 接口名关联地址事实。缺失时回退 BSD 名;回环默认显示 lo0。名称缺失不会放宽类型过滤。 +- 标签统一采用 `{IP} - {name}`。回环项置顶,双栈时 IPv4 回环先于 IPv6 回环;其他项按接口名称排序,同一接口先 IPv4 后 IPv6,然后按地址稳定排序。同一接口重复 IP 合并,不同接口同 IP 保留接口身份。 +- 不提供手动地址输入,排除 IPv6 链路本地地址和通配地址。允许已通过接口类型过滤的 IPv4 链路本地单播地址。列出地址不代表已验证其他设备到该端点的连通性。 +- 进入首页、网络变化以及复制前刷新;监听方式变化时重新过滤。仍有效的选择保留,否则回退到当前监听方式的默认回环地址。复制前发现失效时,本次复制采用回退后的命令,并更新界面选择。 +- 枚举失败时仅提供兼容回环候选。即使代理未运行也允许复制。提示命令与实际复制内容使用相同的地址选择和生成逻辑;复制前刷新产生的变化同步至提示投影。 +- HTTP、HTTPS 使用已保存的 HTTP 端口,SOCKS 使用已保存的 SOCKS 端口。保持现有变量集合、小写变量名、shell 转义、socks5 协议和 no_proxy 内容;IPv6 URL 使用方括号。 +- 复用现有运行时适配缝提供已保存监听事实。仅新增一个工作流层可注入的本机接口事实来源,统一提供接口地址、类型、名称及变化通知;系统 API 查询细节封装于生产适配器,不再为每个底层 API 增加独立 mock 协议。 +- 无配置迁移或持久化 schema 变更。同步修订现有四种监听方式 ADR 和领域文档中“终端命令始终面向回环”的旧约定;健康检查、系统代理目标继续依据监听方式,不受复制地址影响。 + +## Testing Decisions + +- 主测试入口为 ProxyControlWorkflow 的可观察投影和用户操作。复用现有 FakeProxyRuntime、确定性变化通知与工作流 contract 测试方式,新增一个可编程接口事实替身。替身提供原始接口事实,让生产策略实际完成过滤、排序、名称回退与地址选择;不直接注入已经过滤好的候选。 +- 好的测试断言用户可见候选、选中值、选择器可见性和最终 shell 命令,不断言底层系统 API 调用顺序、内部容器或私有辅助方法。 +- 用四种监听方式验证地址族过滤、默认值、双栈回环项及监听方式切换后的回退。提供 Wi-Fi、Ethernet、loopback、VPN、其他虚拟接口及未知类型事实,验证严格白名单。 +- 验证本地化名称、名称缺失的 BSD 回退、同一接口重复 IP 合并、不同接口同 IP 保留、稳定排序、IPv6 链路本地排除与 IPv4 链路本地允许。 +- 通过选址后执行两种 shell 的命令准备操作,断言 HTTP、HTTPS、SOCKS 共用地址、端口来自已保存值、IPv6 URL 正确且 no_proxy 保持不变。 +- 以同一会话离开再进入首页验证保留;以新会话验证默认值。通过可编程网络变化和复制前刷新覆盖地址消失、地址族不兼容及枚举失败的回环回退。 +- 复用已有运行时命令记录,断言选择和刷新不会启动代理、修改监听设置或修改系统代理;代理未运行时命令仍可准备。 +- 延续现有 workflow 不直接访问剪贴板的边界。工作流测试验证“刷新后准备的复制内容”,以及提示命令与命令准备结果在刷新后的投影一致性。不为纯视图布局引入额外领域测试缝,不进行视觉验收。 +- 在真实 macOS 上通过非视觉验证核实系统接口名称与类型映射、Wi-Fi/Ethernet 候选以及 IPv6 地址事实。替身测试证明策略行为,真实主机的非视觉验证证明系统适配器整合;候选枚举本身不证明跨设备网络可达。 + +## Out of Scope + +- 运行时监听绑定修改、系统代理地址修改或自动启用代理。 +- VPN、其他虚拟接口和其他接口类型的候选支持。 +- IPv6 链路本地地址作用域、手动地址输入、域名或外部公网 IP 发现。 +- 跨应用重启记忆选择、设置迁移、恢复旧的手动 advertised address 配置。 +- 网络可达性检测、认证机制、防火墙配置和复制命令后在目标终端的执行。 +- 额外 shell、代理协议或环境变量语义变更。 +- 视觉验收、截图检查与人工界面布局验收。 + +## Further Notes + +- 设计来自 2026-10-01 的 grill-with-docs 会话。用户已确定交互与边界,最终将候选限制为 Wi-Fi、Ethernet、loopback,覆盖第一轮包含 VPN/虚拟接口的建议。 +- 系统接口名称 API 可返回空值;loopback 无对应的 SystemConfiguration 类型常量,必须单独识别。严格类型白名单与名称回退分别处理。 +- 用户已明确不做视觉验收。测试边界采用工作流外部行为测试和一个接口事实注入缝,系统适配器采用非视觉验证。 +- 作为单一 feature spec issue 发布,标记 ready-for-agent,不创建拆分实施票据。 diff --git a/docs/design/terminal-command-address.md b/docs/design/terminal-command-address.md new file mode 100644 index 00000000..0cd2b63a --- /dev/null +++ b/docs/design/terminal-command-address.md @@ -0,0 +1,45 @@ +# 首页代理环境变量命令地址选择 + +日期:2026-10-01。 +状态:交互设计与测试边界已确定,spec 已发布为 [Issue #72](https://github.com/qiuyuzhou/ShadowsocksX-NG/issues/72),标记 ready-for-agent;尚未实现。 + +## 功能范围 + +首页“复制代理环境变量设置命令”卡片在非“仅本机”监听方式下,在 zsh / bash 和 fish 两个复制按钮上方显示一个单选下拉框。仅本机模式隐藏下拉框并使用 IPv4 回环地址。 + +所选地址同时用于两种 shell 命令的 HTTP、HTTPS 和 SOCKS 端点。端口取已保存的监听设置,现有 shell 语法、变量集合和 no_proxy 内容保持不变。IPv6 在 URL 中使用方括号。选择只改变生成的命令,不改变监听配置或系统代理。 + +## 候选地址 + +仅包含 loopback,以及本机当前启用的 Wi-Fi、Ethernet 接口的单播 IP。VPN、其他虚拟接口和其他接口类型排除;不能仅依据 BSD 接口前缀或通用 Ethernet 链路类型来接受虚拟接口。无法确认是允许类型的非回环接口不纳入列表。 + +不提供手动输入,不提供通配绑定地址,IPv6 链路本地地址排除。IPv4 链路本地地址属于允许接口的单播地址时可以列出。候选列表不表示网络连通性已验证。 + +| 已保存的监听方式 | 地址族 | 默认地址 | +| --- | --- | --- | +| 仅本机 | IPv4 回环;隐藏下拉框 | 127.0.0.1 | +| 所有 IPv4 接口 | IPv4 | 127.0.0.1 | +| 所有 IPv4 与 IPv6 接口 | IPv4、IPv6,包含两个回环地址 | 127.0.0.1 | +| 仅所有 IPv6 接口 | IPv6 | ::1 | + +每项显示 `{IP} - {name}`。接口名称优先取 `SCNetworkInterfaceGetLocalizedDisplayName(interface)`;名称缺失或无法映射到 SystemConfiguration 接口时,回退到 BSD 接口名,但不会因此放宽接口类型过滤。回环项回退名称为 lo0。 + +回环项置顶,双栈时 127.0.0.1 在 ::1 前。其余按接口名称排序,同一接口先 IPv4 后 IPv6,再按地址稳定排序。同一接口的重复 IP 合并;不同接口即使 IP 相同也保留接口身份。 + +## 选择生命周期与刷新 + +选择在本次应用运行期间保留,包括离开首页后返回;不跨应用重启持久化。进入首页、网络变化以及复制前刷新候选地址。监听方式变化时立即重新过滤候选。 + +所选地址消失,或监听方式不再支持该地址时,回退到对应监听方式的默认回环地址。复制前刷新发现地址失效时,本次复制使用回退后的地址,界面与命令一致。 + +地址枚举失败时仅保留兼容的回环项,继续允许复制。代理未运行时也允许复制;生成命令不承诺端点当前可连接。 + +## 实现与验证边界 + +地址枚举、接口名称及类型识别通过可注入的事实来源提供,UI 不自行访问底层控制器拼接命令。应用会话状态持有选中地址的接口身份和地址值,不使用界面显示名称作为身份。 + +需要验证四种监听方式的候选过滤和默认地址、Wi-Fi/Ethernet/loopback 类型过滤、VPN/虚拟接口排除、名称回退、IPv6 URL、两种 shell 的共享选中地址、离开首页后保留、应用重启后重置、网络变化和复制前失效回退,以及枚举失败时的回环退化行为。用户明确不做视觉验收;系统接口映射通过非视觉验证核实。 + +## 现有决策调整 + +此设计扩展 ADR-0015 中“命令始终面向本机回环”的旧约定。实现时同步更新该 ADR 的命令地址段落以及 CONTEXT.md 中对应定义和约束;健康检查和系统代理目标仍由监听方式确定,不受命令地址选择影响。此项是可逆的首页交互调整,不新增 ADR。