From fa9006297116019b1e13e00563619f2e4d190774 Mon Sep 17 00:00:00 2001 From: Arvin <62139570+wnlen@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:25:17 +0800 Subject: [PATCH] fix: warn when clashon wrapper cannot persist parent shell env --- .env | 18 +- config/subscriptions.yaml | 6 +- docs/codex-bootstrap.md | 689 ++++++++++++++++++++++++++++++++++++++ scripts/core/alias.sh | 8 + scripts/core/clashctl.sh | 9 + scripts/core/common.sh | 3 +- scripts/core/config.sh | 7 +- scripts/core/proxy.sh | 64 ++++ 8 files changed, 782 insertions(+), 22 deletions(-) create mode 100644 docs/codex-bootstrap.md diff --git a/.env b/.env index e253061..a79bb7d 100644 --- a/.env +++ b/.env @@ -1,30 +1,22 @@ # 安装内核 可选:mihomo、clash export KERNEL_TYPE="mihomo" -# 机场订阅 +# 可选订阅(首次安装可留空) export CLASH_SUBSCRIPTION_URL="" -export CLASH_SUBSCRIPTION_FORMAT="convert" -export CLASH_SUBSCRIPTION_UA="clash-verge/v2.4.0" + +# 控制器密钥(默认留空) +export CLASH_CONTROLLER_SECRET="" # 端口 export MIXED_PORT="7890" export EXTERNAL_CONTROLLER="0.0.0.0:9090" export CLASH_DNS_PORT="1053" -# 编译 / 更新策略 +# 订阅更新策略 export CLASH_AUTO_UPDATE_SUBSCRIPTIONS="true" -# 下载与公共入口 -export CLASH_PUBLIC_UI_URL="http://board.zash.run.place" -export CLASH_GH_PROXY="" -export CLASH_GH_PROXY_POOL=$'gh-proxy|https://gh-proxy.org|full\nghfast|https://ghfast.top|hostpath\nghproxy-net|https://ghproxy.net|hostpath' -export CLASH_DOWNLOAD_CONNECT_TIMEOUT="8" -export CLASH_DOWNLOAD_PROBE_TIMEOUT="4" -export CLASH_DOWNLOAD_MAX_TIME="1200" - # 版本 export MIHOMO_VERSION="v1.19.21" export CLASH_VERSION="v1.18.0" export YQ_VERSION="v4.52.4" export SUBCONVERTER_VERSION="v0.9.0" -export CLASH_CONTROLLER_SECRET="" diff --git a/config/subscriptions.yaml b/config/subscriptions.yaml index b3333b9..847c8c9 100644 --- a/config/subscriptions.yaml +++ b/config/subscriptions.yaml @@ -1,9 +1,7 @@ -mode: single -policy: strict active: default -selected: [] + sources: default: type: clash - url: https://msub.xn--m7r52rosihxm.com/api/v1/client/subscribe?token=1bc3d16cc3211a892b2e16bdb72115ec + url: "" enabled: true diff --git a/docs/codex-bootstrap.md b/docs/codex-bootstrap.md new file mode 100644 index 0000000..ebc25b4 --- /dev/null +++ b/docs/codex-bootstrap.md @@ -0,0 +1,689 @@ +# clash-for-linux |Codex 首次投喂文档(Bootstrap) + +> 用途:这是 **第一次喂给 Codex** 的启动文档。 +> 目标不是讲完整历史,而是让 Codex 先建立**正确项目认识、真实主链认知、修改边界和输出契约**。 +> 最高原则:**以当前代码真实行为为准,不以旧设计、旧记忆、旧残留为准。** + +--- + +## 0. 使用方式 + +这份文档的定位不是“每轮任务单”,而是 **Codex 的首次基线输入**。 + +第一次使用 Codex 时,建议只喂三类东西: + +1. **这份 Bootstrap 文档** +2. **当前仓库代码** +3. **本轮任务单** + +不要第一次就把所有历史设计稿、旧方案、推演过程、聊天记录全部塞进去,否则 Codex 很容易被旧信息污染。 + +--- + +## 1. 项目定位 + +你正在维护的是 `clash-for-linux`。 + +它不是单纯的安装脚本集合,也不是节点管理小工具。它当前的真实形态是: + +> 一个以 **active-only 主订阅编译链** 为核心、以 **runtime 后端抽象** 为底座、以 **shell 体验闭环** 和 **状态 / 诊断系统** 为外壳的本地代理运行平台。 + +你的默认目标不是发散式重写,而是: + +1. 顺着当前真实架构继续收敛 +2. 清理旧残留 +3. 降低解释成本 +4. 提高稳定性 +5. 保持主路径简单、优雅、稳定 + +--- + +## 2. 当前最高事实 + +### 2.1 三层架构 + +```text +Control(控制层) + ↓ +Build(配置编译层) + ↓ +Runtime(运行时层) +``` + +### 2.2 当前真实主链 + +当前已不是多订阅 merge 编译,而是: + +```text +active 主订阅 +→ 下载原始订阅 +→ 直接校验 +→ 必要时走一次 subconverter +→ normalize_runtime_config +→ test_runtime_config +→ 输出 runtime/config.yaml +``` + +### 2.3 订阅策略的唯一真相 + +> **多订阅保存,单订阅激活,active-only 编译。** + +含义: + +- 可以保存多个订阅源 +- 任意时刻只有一个 `active` 主订阅 +- `generate_config()` 只处理当前主订阅 +- 不再把多个订阅 merge 进最终运行配置 + +### 2.4 旧残留不是主设计 + +下列内容即使还残留在代码或配置里,也**不是未来设计中心**: + +- `subscriptions.yaml` 中的 `mode` / `selected` +- `.env` 中的 `BUILD_MIN_SUCCESS_SOURCES` +- 旧 merge / repair / explain / helper 时代的兼容逻辑 + +结论: + +> **优先让代码继续向 active-only 收口,而不是把旧 merge 逻辑修得更复杂。** + +--- + +## 3. 协作方式 + +### 3.1 三方分工 + +- 用户 + - 负责真实体验 + - 负责产品裁决 + - 负责判断哪里不顺、哪里不符合预期、哪里应该改 +- ChatGPT + - 负责问题收敛 + - 负责任务翻译 + - 负责边界设计与风险约束 + - 负责决定这轮任务更适合“目标导向型”还是“定点收口型” +- Codex + - 负责搜索代码 + - 负责定位影响链 + - 负责执行真实修改 + - 负责最小闭环验证 + +### 3.2 一条默认原则 + +默认不是: + +- 用户把每个文件、每个函数、每一行都指定死 +- Codex 只负责机械照改 + +也不是: + +- 完全放手让 Codex 自己猜需求、自己决定方向 + +而是: + +> **用户给体验裁决,ChatGPT 给任务框架和边界,Codex 负责自己定位并修改;只有在高风险收尾时,才明确指定它改哪里。** + +--- + +## 4. 任务模式 + +### 4.1 默认优先:目标导向型 + +适用场景: + +- 根因未完全锁定 +- 只知道现象或目标 +- 希望 Codex 发挥搜索与判断能力 +- 当前最怕的是“找不到根因”,不是“它改太多” + +表达方式应以这四项为主: + +- 目标 +- 约束 +- 验收 +- 输出格式 + +不要一上来就指定: + +- 改哪几个文件 +- 删哪几个函数 +- 哪几行必须怎么写 + +### 4.2 高风险收尾:定点收口型 + +适用场景: + +- 根因已经锁定 +- 旧链路已经明确 +- 当前任务重点是防止扩散修改 +- 这轮任务本质是“清残留 / 删死代码 / 做收尾” +- 当前最怕的是“改散、改多、顺手重构” + +只有这类情况下,才应该明确指定: + +- 允许修改哪些文件 +- 哪些函数或旧链路要删 +- 哪些行为绝对不能动 +- 验收 grep / bash -n / status 要怎么做 + +### 4.3 模式切换规则 + +默认先问: + +```text +这轮任务更像“定位问题”,还是“清理残局”? +若更像“定位问题”: +用目标导向型 +若更像“清理残局”: +用定点收口型 +``` + +--- + +## 5. 目录与关键文件职责 + +### 5.1 根目录 + +- `install.sh`:安装入口,负责初始化、依赖准备、命令入口安装、运行后端安装、必要时生成配置、安装后验证、安装摘要 +- `uninstall.sh`:卸载入口,支持普通卸载、`--purge-runtime`、`--dev-reset` +- `.env`:运行参数来源之一,负责内核类型、订阅地址、端口、下载参数、版本等 + +### 5.2 `scripts/core/` + +- `common.sh`:公共能力中心;路径初始化、环境兼容、下载系统、缓存、命令入口安装、shell 注入安装、状态读写 +- `config.sh`:配置编译中心;订阅读取、订阅缓存、active-only 构建主链、运行配置规范化、构建记录、订阅健康、端口裁决 +- `runtime.sh`:运行时能力;yq / mihomo / clash / subconverter 解析与安装、配置回退、Tun 检查 +- `proxy.sh`:controller / proxy 相关能力;代理地址、controller API、策略组读取与切换 +- `clashctl.sh`:主 CLI 控制面;`add/use/select/on/off/status/doctor/ui/tun/config/profile/sub/proxy/update/upgrade/dev` 等命令实现 +- `alias.sh`:shell function 劫持层;提供 `clashctl` / `clashon` / `clashoff` / `clashproxy` / `clashselect` 等体验闭环 +- `update.sh`:Git 更新与运行时依赖同步 + +### 5.3 `scripts/init/` + +- `systemd.sh`:systemd 后端 +- `systemd-user.sh`:user systemd 后端 +- `script.sh`:script fallback 后端 + +### 5.4 `config/` + +- `subscriptions.yaml`:订阅集合;当前真实核心字段是 `active` 和 `sources` +- `template.yaml`:基础模板 +- `mixin.yaml`:运行配置补丁 +- `profiles.yaml`:profile 配置 + +### 5.5 `runtime/` + +`runtime/` 是**唯一运行时容器**,不是源码目录,也不是静态资源目录。 + +典型内容: + +- `runtime/config.yaml` +- `runtime/config.last.yaml` +- `runtime/bin/` +- `runtime/logs/` +- `runtime/cache/` +- `runtime/tmp/` +- `runtime/install.env` +- `runtime/build.env` +- `runtime/runtime-events.env` +- `runtime/tun.env` + +原则: + +> 一切运行态文件尽量收口到 `runtime/`,不要散落回项目根目录。 + +--- + +## 6. CLI 与用户主路径 + +### 6.1 主路径 + +```text +clashctl add +clashctl use +clashctl select +clashctl on +clashctl off +clashctl status +clashctl doctor +``` + +### 6.2 关键命令组 + +当前 CLI 至少包含: + +- `add` / `use` / `ls` / `health` +- `select` / `on` / `off` / `status` / `logs` / `doctor` +- `ui` / `secret` / `tun` +- `config` / `mixin` / `profile` +- `sub` / `proxy` +- `upgrade` / `update` / `dev` + +### 6.3 Shell 体验闭环 + +当前 shell 层不是 alias,而是 function 劫持。 + +必须保持: + +- `clashctl on` 统一走真实主链 +- on 成功后,再为当前 shell 注入 `http_proxy` / `https_proxy` / `all_proxy` / `NO_PROXY` +- off 前先清理 shell 代理,再调用真实 off +- shell 层不抢 runtime / build 的职责 +- shell 被 source 时不自动偷偷执行代理动作 + +结论: + +> **shell 层只负责体验闭环,不负责偷偷维护后台状态。** + +--- + +## 7. Build 规则 + +### 7.1 主函数链 + +围绕以下函数理解构建主链: + +- `resolve_build_sources()` +- `build_runtime_candidate_from_payload()` +- `fetch_subscription_source()` +- `generate_config()` + +### 7.2 必须坚持 + +- 只处理当前 `active` 且 `enabled=true` 的主订阅 +- `clash` 类型先直下、直校验,失败时只允许一次 `subconverter` fallback +- `convert` 类型走转换后再校验 +- 运行时配置必须经过 `normalize_runtime_config()` +- 最终必须经过 `test_runtime_config()` +- 失败必须明确留证据 + +### 7.3 不要再做 + +- 不要恢复多订阅 merge 进 `runtime/config.yaml` +- 不要再引入复杂 repair 流程去修订 YAML 语义 +- 不要再围绕“最少成功源数量”设计主链 +- 不要把旧 `mode / selected` 拉回主逻辑 + +### 7.4 失败策略 + +当前失败模式应保持为: + +> **快速失败 + 明确保留证据** + +而不是: + +> 深度自动修订 + 隐式容错 + 用户看不懂发生了什么 + +--- + +## 8. Runtime 规则 + +### 8.1 后端抽象 + +当前运行后端统一抽象为: + +- `systemd` +- `systemd-user` +- `script` + +统一入口: + +- `service_start` +- `service_stop` +- `service_restart` +- `service_status_text` +- `service_logs` + +### 8.2 运行原则 + +- root + systemd 可用时优先 `systemd` +- 普通用户 + user systemd 可用时走 `systemd-user` +- 否则 fallback 到 `script` + +### 8.3 配置回退 + +如果当前 `runtime/config.yaml` 不可用,允许回退到 `runtime/config.last.yaml`,但必须记录: + +- 是否触发回退 +- 回退时间 +- 回退原因 + +### 8.4 端口原则 + +- 安装期可以裁决端口 +- 运行中尽量不要乱改端口 + +核心原则: + +> **运行中避免再次重写端口,防止搅乱已运行实例。** + +--- + +## 9. 下载系统规则 + +### 9.1 下载层已经是平台能力 + +当前支持: + +- GitHub 镜像池 +- probe + fetch 两阶段 +- 最近失败降权 +- 冷却期 +- 下载成功 / 失败记忆 +- 下载缓存 +- 非 GitHub 直连下载 + +### 9.2 修改下载层时的准则 + +可以做: + +- 提高稳定性 +- 提高清晰度 +- 减少重复代码 +- 强化缓存复用 +- 提升失败可解释性 + +不可以做: + +- 退回到单镜像硬编码 +- 破坏缓存机制 +- 在 install / update / upgrade 各写一套下载逻辑 +- 无依据引入复杂并发下载机制 + +原则: + +> **下载能力统一,不要分散。** + +--- + +## 10. 状态系统规则 + +### 10.1 `status` 的定位 + +`status` 不是简单看 service 是否运行,而是系统聚合判断层。 + +核心状态: + +- `ready` +- `stopped` +- `degraded` +- `broken` + +### 10.2 聚合维度 + +至少参考: + +- runtime +- build +- subscription +- risk +- tun +- shell proxy +- controller reachability + +### 10.3 设计目标 + +用户执行 `clashctl status` 时,应直接得到: + +- 当前是否可用 +- 当前主订阅是谁 +- 当前节点是什么 +- 风险等级 +- 下一步该做什么 + +本质: + +> **面向用户动作决策的解释层。** + +### 10.4 `doctor` 的定位 + +> **status 给结论,doctor 给证据。** + +不要把 `doctor` 变成炫技式日志倾倒。 + +--- + +## 11. Tun 规则 + +Tun 是高级能力,不属于默认主路径。 + +必须保持: + +- Tun 必须显式开启 +- 不能安装时默认偷偷接管 +- `clashctl tun doctor` 应先于 `clashctl tun on` +- 需要考虑 host / container、`/dev/net/tun`、`ip` 命令、内核支持、默认路由是否接管 + +不要做: + +- 安装时自动启用 Tun +- 用模糊逻辑默认打开 Tun +- 在容器高风险环境里静默强开 + +--- + +## 12. install / uninstall 规则 + +### 12.1 install + +`install.sh` 必须保持: + +- 本体薄 +- 主逻辑内化到函数 +- 输出线性、简洁、面向用户 +- 不在顶层堆解释性文字 + +理解安装流程: + +```text +初始化目录 +→ 校验基础依赖 +→ 下载 / 准备 runtime 依赖 +→ 记录安装环境与计划 +→ 安装命令入口与 shell 入口 +→ 安装运行后端 +→ 必要时引导订阅 +→ generate_config +→ post_install_verify +→ print_install_summary +``` + +### 12.2 uninstall + +必须保留三种语义: + +- 普通卸载:保留 runtime 数据 +- `--purge-runtime`:删除 runtime +- `--dev-reset`:清安装状态,但保留订阅与缓存,方便开发调试 + +--- + +## 13. 修改代码时的硬约束 + +### 13.1 先收敛,再扩展 + +如果问题可以通过以下方式解决: + +- 删除旧代码 +- 收紧主链 +- 减少分支 +- 消灭兼容残留 + +那就**不要优先加新层、新抽象、新文件**。 + +### 13.2 不要把旧架构修回主链 + +旧 merge 时代的字段、函数、注释可以识别、可以建议清理,但不要重新扶正成主设计。 + +### 13.3 优先最小修改面 + +除非明确必要,否则: + +- 不随意新增文件 +- 不随意拆太多函数 +- 不随意跨多个模块做风格统一改造 +- 不为了“理论更优雅”改坏当前稳定路径 + +### 13.4 先保用户主路径 + +任何改动,都优先保护: + +```text +install.sh +clashctl add +clashctl use +clashon +clashctl select +clashctl status +clashctl doctor +``` + +### 13.5 不做静默副作用 + +禁止: + +- 自动改订阅 +- 自动启用 Tun +- 自动切换用户关键配置 +- 自动恢复代理环境 +- 自动引入后台行为但不告知用户 + +### 13.6 输出风格 + +当前脚本用户输出风格必须保持: + +- 中文 +- 简洁 +- 线性 +- 图标提示 +- 不用大段技术日志污染主路径 + +### 13.7 运行态文件收口 + +新增运行态文件、缓存、临时文件、状态文件时,优先放到 `runtime/` 下。 + +--- + +## 14. 默认工作流程 + +### 第一步:先确认当前真实行为 + +优先看: + +- 当前命令入口怎么走 +- 当前主函数链怎么串 +- 最终 override 在哪里 +- 当前运行态文件写到哪里 + +### 第二步:找主链,不找边角 + +优先锁定: + +- `install.sh` +- `scripts/core/clashctl.sh` +- `scripts/core/config.sh` +- `scripts/core/common.sh` +- `scripts/core/runtime.sh` +- `scripts/init/*.sh` + +### 第三步:优先做最小闭环修改 + +先问自己: + +- 能否只改 1–2 个文件解决? +- 能否删除旧逻辑而不是新增绕路逻辑? +- 能否不增加新的状态分叉? + +### 第四步:最后才考虑扩展 + +只有当前主链确实无法承载需求时,再考虑: + +- 新函数 +- 新文件 +- 新抽象 + +--- + +## 15. Codex 输出契约(默认) + +### 15.1 执行模式 + +- 默认直接修改代码,不停留在“建议修改”或“展示方案” +- 任务明确时不需要再次征求确认 +- 优先执行,而不是解释 + +### 15.2 默认输出内容 + +未被明确要求展开时,只保留四部分: + +1. 已修改文件列表 +2. 每个文件改动说明(1–2 句) +3. 静态 / 运行验证结果 +4. 剩余问题或风险点 + +### 15.3 禁止输出内容 + +在未被明确要求时,禁止: + +- unified diff +- patch / 补丁块 +- 大段代码 +- 冗长解释 +- 多方案对比 + +### 15.4 例外开放条件 + +只有用户明确提出以下请求时,才允许突破限制: + +- “给我 diff” +- “给我具体代码” +- “展开某个函数实现” +- “详细解释改动” + +### 15.5 修改策略约束 + +- 优先最小修改面(1–2 个文件解决问题) +- 优先删除 / 收口旧逻辑,而不是增加新逻辑 +- 不允许顺手重构无关代码 +- 不跨模块做风格统一改造 + +### 15.6 主路径保护 + +任何修改不得破坏: + +```text +install.sh +clashctl add +clashctl use +clashctl on +clashctl select +clashctl status +clashctl doctor +``` + +--- + +## 16. 每轮任务单应该怎么下 + +这份 Bootstrap 只负责建立基线,**不替代每轮任务单**。 + +每轮任务单必须至少包含: + +- 问题现象 +- 根因判断(如果已知) +- 改动目标 +- 改动边界 +- 明确不要动的地方 +- 验证方式 +- 输出格式 + +如果这轮任务是“定位问题”,优先用目标导向型任务单。 +如果这轮任务是“清理残局”,再用定点收口型任务单。 + +--- + +## 17. 最终目标 + +这个项目后续阶段的重点不是继续堆功能,而是: + +> **清旧架构,压缩主链,降低解释成本,让代码与真实设计彻底一致。** diff --git a/scripts/core/alias.sh b/scripts/core/alias.sh index 38bc083..899db1b 100644 --- a/scripts/core/alias.sh +++ b/scripts/core/alias.sh @@ -74,6 +74,11 @@ _clash_alias_after_on() { _clash_alias_print_sep _clash_alias_proxy_show + if [ "${CLASH_WRAPPER_EXEC:-0}" = "1" ]; then + echo "⚠️ 当前通过独立命令执行,Shell 变量不会自动回写到父终端" + echo '💡 请在当前终端执行:eval "$(clashctl proxy on)"' + fi + echo "👉 下一步:$(_clash_alias_status_next)" } @@ -82,6 +87,9 @@ _clash_alias_after_off() { _clash_alias_print_sep echo "🔴 已关闭代理环境" echo "🧹 当前 Shell 代理变量已清理" + if [ "${CLASH_WRAPPER_EXEC:-0}" = "1" ]; then + echo '💡 若当前终端仍有代理变量,请执行:eval "$(clashctl proxy off)"' + fi echo "🧭 新终端默认代理:已关闭" echo "👉 下一步:clashctl status" } diff --git a/scripts/core/clashctl.sh b/scripts/core/clashctl.sh index a7fa12c..a72b26c 100644 --- a/scripts/core/clashctl.sh +++ b/scripts/core/clashctl.sh @@ -247,10 +247,19 @@ print_on_feedback() { } cmd_on() { + local relay_switch + prepare ensure_on_path_ready service_start + if proxy_controller_reachable 2>/dev/null; then + relay_switch="$(ensure_default_proxy_group_relay_selected 2>/dev/null || true)" + if [ -n "${relay_switch:-}" ]; then + ui_info "检测到策略组存在直连默认项,已自动切换到代理节点" + fi + fi + load_system_state print_on_feedback diff --git a/scripts/core/common.sh b/scripts/core/common.sh index 6146f19..c2fdbc7 100644 --- a/scripts/core/common.sh +++ b/scripts/core/common.sh @@ -2207,6 +2207,7 @@ install_alias_command_wrappers() { cat > "$install_dir/$wrapper_name" </dev/null || return 1 } +proxy_node_is_direct_like() { + local node="$1" + + case "${node:-}" in + DIRECT|REJECT|REJECT-DROP|PASS) + return 0 + ;; + *) + return 1 + ;; + esac +} + +proxy_group_first_relay_node() { + local group="$1" + local node + + [ -n "${group:-}" ] || return 1 + + while IFS= read -r node; do + [ -n "${node:-}" ] || continue + if proxy_node_is_direct_like "$node"; then + continue + fi + echo "$node" + return 0 + done < <(proxy_group_nodes "$group") + + return 1 +} + +ensure_default_proxy_group_relay_selected() { + local group current relay + local switched="" + + while IFS= read -r group; do + [ -n "${group:-}" ] || continue + + current="$(proxy_group_current "$group" 2>/dev/null || true)" + [ -n "${current:-}" ] || continue + + if ! proxy_node_is_direct_like "$current"; then + continue + fi + + relay="$(proxy_group_first_relay_node "$group" 2>/dev/null || true)" + [ -n "${relay:-}" ] || continue + + if [ "$relay" = "$current" ]; then + continue + fi + + proxy_group_select "$group" "$relay" + + if [ -n "${switched:-}" ]; then + switched="${switched},${group}|${current}|${relay}" + else + switched="${group}|${current}|${relay}" + fi + done < <(proxy_group_list) + + [ -n "${switched:-}" ] && echo "$switched" +} + print_proxy_groups_status() { local group current