diff --git a/.github/workflows/runner-verify.yml b/.github/workflows/runner-verify.yml index 538027c..175aa02 100644 --- a/.github/workflows/runner-verify.yml +++ b/.github/workflows/runner-verify.yml @@ -1,11 +1,12 @@ -name: Setup Verification (Runner Direct) +name: E2E Full Install -# 直接在 GitHub runner 上运行 setup.sh,无需 Docker 构建 -# 目的:快速验证 setup.sh 在干净 Ubuntu 环境中能正常执行 +# 端到端完整安装测试:在干净 GitHub runner 上一键运行 setup-new.sh +# 覆盖 Layer 0 (bootstrap) + Layer 1 (deploy + install) + Layer 2 (post) +# 并验证所有关键工具是否正确安装 # # 对比 Docker 构建方式: # - Docker 构建平均耗时:~32 分钟(成功)/ ~6 分钟(失败) -# - Runner 直接运行预计:~15-20 分钟(跳过镜像构建,runner 已有基础工具) +# - Runner 直接运行预计:~10-15 分钟(跳过镜像构建,runner 已有基础工具) on: push: @@ -24,7 +25,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] fail-fast: false - name: Verify setup.sh on ${{ matrix.os }} + name: E2E full install on ${{ matrix.os }} runs-on: ${{ matrix.os }} timeout-minutes: ${{ matrix.os == 'macos-latest' && 120 || 60 }} permissions: @@ -46,14 +47,17 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Run setup.sh + - name: Disable GitHub mirrors for CI + run: perl -pi -e 's/^github_mirrors/#github_mirrors/' manifest.toml + + - name: Run full install (setup-new.sh) run: | - chmod +x ./setup.sh - ./setup.sh + chmod +x ./setup-new.sh + ./setup-new.sh - - name: Verify installation + - name: Verify all installed tools run: | - # 加载 dotfiles 环境变量(setup.sh 已部署符号链接) + # 加载 dotfiles 环境变量(setup-new.sh 已部署符号链接) export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH" if [ -f "./scripts/verify-docker-build.sh" ]; then @@ -70,3 +74,9 @@ jobs: fi done fi + + - name: Verify mise [env] in interactive shell + run: | + export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH" + # 模拟新登录交互式 shell,验证 [env] 变量是否被正确注入 + zsh -lic 'echo "STEP_API_KEY=${STEP_API_KEY:-(未设置)}"' diff --git a/.github/workflows/tool-installer-verify.yml b/.github/workflows/tool-installer-verify.yml new file mode 100644 index 0000000..e5aa931 --- /dev/null +++ b/.github/workflows/tool-installer-verify.yml @@ -0,0 +1,349 @@ +name: tool-installer Migration Verify + +on: + push: + branches: + - 'feat/tool-installer-migration' + pull_request: + branches: + - 'feat/tool-installer-migration' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + # ── Dry-run(快速,无网络)── + dry-run: + name: Dry-run (all modules) + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Dry-run dev (full) + run: python3 vendor/tool-installer install dev --dry-run + - name: Dry-run each module + run: | + for module in cargo-binstall build-base cargo-tools shell-tools editors languages lsp-servers extras; do + echo "--- $module ---" + python3 vendor/tool-installer install "$module" --dry-run + done + + # ── Bootstrap ── + bootstrap: + name: Bootstrap + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + - name: Run Layer 0 (tool-installer binary only) + run: bash setup-new.sh --bootstrap + - name: Verify + run: | + ~/.local/bin/tool-installer --help + ~/.local/bin/tool-installer install dev --dry-run + + # ── build-base: Rust 工具链 ── + apply-build-base: + name: "Apply: build-base (Rust)" + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v6 + - uses: Swatinem/rust-cache@v2 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install build-base + run: python3 vendor/tool-installer install build-base + - name: Verify + run: | + rustc --version + cargo --version + rustfmt --version + clippy-driver --version + + # ── cargo-binstall: 预编译安装器 ── + apply-cargo-binstall: + name: "Apply: cargo-binstall" + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install cargo-binstall + run: python3 vendor/tool-installer install cargo-binstall + - name: Verify + run: | + export PATH="$HOME/.local/bin:$PATH" + cargo-binstall -V + + # ── cargo-tools: 28 个 Rust 工具 ── + apply-cargo-tools: + name: "Apply: cargo-tools (28 crates)" + needs: [apply-cargo-binstall, apply-build-base] + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - uses: actions/checkout@v6 + - uses: Swatinem/rust-cache@v2 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Clean stale cargo install metadata + run: rm -f ~/.cargo/.crates.toml ~/.cargo/.crates2.json + - name: Install cargo-tools (needs cargo-binstall + build-base) + run: | + export PATH="$HOME/.local/bin:$PATH" + python3 vendor/tool-installer install cargo-binstall + python3 vendor/tool-installer install build-base + python3 vendor/tool-installer install cargo-tools + - name: Verify key tools + run: | + export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH" + bat --version + eza --version + fd --version || fdfind --version + tokei --version + zoxide --version + mdbook --version + mise --version + uv --version + navi --version || true + gitui --version || true + kondo --help >/dev/null + jaq --version + nu --version + cargo-audit --version + macchina --version || true + tree-sitter --version + + # ── languages: go, node, pnpm, zig, yazi, gopls ── + apply-languages: + name: "Apply: languages (mise)" + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install mise + run: | + curl https://mise.run | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Install and verify languages + run: | + export PATH="$HOME/.local/bin:$PATH" + # Trust and install + mise trust + python3 vendor/tool-installer install languages + # Add mise shims to PATH and verify + export PATH="$(mise where)/shims:$HOME/.local/share/mise/shims:$PATH" + mise list + go version || true + node --version || true + pnpm --version || true + zig version || true + + # ── shell-tools: starship ── + apply-shell-tools: + name: "Apply: shell-tools" + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install shell-tools + run: | + export PATH="$HOME/.local/bin:$PATH" + mkdir -p ~/.local/bin + python3 vendor/tool-installer install shell-tools + - name: Verify + run: | + export PATH="$HOME/.local/bin:$PATH" + starship -V + + # ── editors: neovim, helix, zellij, yq ── + # 全部是 github-release,不需要额外 manager + apply-editors: + name: "Apply: editors" + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install editors + run: | + export PATH="$HOME/.local/bin:$PATH" + mkdir -p ~/.local/bin + python3 vendor/tool-installer install editors + - name: Verify + run: | + export PATH="$HOME/.local/bin:$PATH" + nvim --version | head -1 + hx --version + zellij --version + yq --version + + # ── lsp-servers: npm/uv/github-release LSP ── + # 依赖 node(来自 languages),需要等 languages 完成 + # CI 中单独安装 node 作为前置 + apply-lsp-servers: + name: "Apply: lsp-servers" + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install uv (for pyright) + run: pip install uv + - name: Install npm LSP servers directly + run: | + npm install -g typescript-language-server typescript yaml-language-server bash-language-server + - name: Install remaining LSP servers via tool-installer + run: | + export PATH="$HOME/.local/bin:$PATH" + mkdir -p ~/.local/bin + python3 vendor/tool-installer install lsp-servers + - name: Verify + run: | + export PATH="$HOME/.local/bin:$PATH" + typescript-language-server --version + pyright --version || true + yaml-language-server --version || true + bash-language-server --version + marksman --version || true + zls --version || true + lua-language-server --version || true + + # ── extras: xdotter ── + apply-extras: + name: "Apply: extras" + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install extras + run: | + export PATH="$HOME/.local/bin:$PATH" + mkdir -p ~/.local/bin + python3 vendor/tool-installer install extras + - name: Verify + run: | + export PATH="$HOME/.local/bin:$PATH" + xd --version || true + + # ── 全量安装验证(等所有模块通过后)── + full-install: + name: "Apply: dev (full) — ${{ matrix.os }}" + needs: [dry-run, bootstrap, apply-cargo-binstall, apply-build-base, apply-cargo-tools, apply-shell-tools, apply-editors, apply-languages, apply-lsp-servers, apply-extras] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 150 + steps: + - uses: actions/checkout@v6 + - uses: Swatinem/rust-cache@v2 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Clean stale cargo install metadata + run: rm -f ~/.cargo/.crates.toml ~/.cargo/.crates2.json + - name: Install mise (prerequisite) + run: | + curl https://mise.run | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install uv + run: pip install uv + - name: Pre-install npm LSP servers + run: npm install -g typescript-language-server typescript yaml-language-server bash-language-server + - name: Full install + run: | + export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH" + python3 vendor/tool-installer install dev + - name: Comprehensive verification + run: | + export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH" + echo "=== Rust ===" + rustc --version && cargo --version + echo "=== Editors ===" + nvim --version | head -1 + hx --version + zellij --version + yq --version + echo "=== Cargo Tools ===" + bat --version && eza --version && fd --version + tokei --version && zoxide --version + nu --version && uv --version && mise --version + echo "=== Shell Tools ===" + starship -V + echo "=== Languages ===" + go version 2>/dev/null || echo "go: skipped" + node --version 2>/dev/null || echo "node: skipped" + echo "=== LSP ===" + typescript-language-server --version + bash-language-server --version + echo "=== All passed ===" + + # ── 重复安装验证:在已安装过的环境中再次执行 install dev ── + # 验证 version_probe 等缓存/跳过逻辑的正确性,避免 + # "首次安装成功但重复安装报错" 的问题(如 zls version_probe regex) + re-install: + name: "Re-install: dev (idempotency) — ${{ matrix.os }}" + needs: [full-install] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + steps: + - uses: actions/checkout@v6 + - uses: Swatinem/rust-cache@v2 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Clean stale cargo install metadata + run: rm -f ~/.cargo/.crates.toml ~/.cargo/.crates2.json + - name: Install mise (prerequisite) + run: | + curl https://mise.run | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install uv + run: pip install uv + - name: Pre-install npm LSP servers + run: npm install -g typescript-language-server typescript yaml-language-server bash-language-server + - name: Second install (should skip all satisfied tools) + run: | + export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH" + python3 vendor/tool-installer install dev diff --git a/Dockerfile b/Dockerfile index 13f0a1e..728bcfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,12 +59,13 @@ ENV CARGO_BUILD_JOBS=${BUILD_JOBS} # apt 网络波动:重试 5 次、单次超时 300s,减少偶发超时失败 COPY ./apt-retry.conf /etc/apt/apt.conf.d/99-retry-timeout.conf -# Rust 镜像源由 setup.sh 内 deploy_dotfiles 统一软链接 ~/.cargo/config.toml,此处不预 COPY 避免路径冲突 +# Rust 镜像源由 setup-new.sh 内 do_deploy (xd deploy) 统一软链接 ~/.cargo/config.toml,此处不预 COPY 避免路径冲突 # 安装 ca-certificates: # - 国内 (USE_CHINA_MIRROR=1): 先用 HTTP 清华源安装,再切回 HTTPS # - 海外 (USE_CHINA_MIRROR=0): 直接用默认源(已有系统 CA 证书) -RUN if [ "${USE_CHINA_MIRROR}" = "1" ]; then \ +RUN rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources 2>/dev/null; true && \ + if [ "${USE_CHINA_MIRROR}" = "1" ]; then \ echo 'deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble main restricted universe multiverse' > /etc/apt/sources.list && \ echo 'deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble-updates main restricted universe multiverse' >> /etc/apt/sources.list && \ echo 'deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble-security main restricted universe multiverse' >> /etc/apt/sources.list; \ @@ -118,14 +119,14 @@ ENV CARGO_INSTALL_STRICT=${CARGO_INSTALL_STRICT} # 执行一键配置脚本 # 此步骤会安装所有依赖、语言环境与工具 # cargo 编译受 CARGO_BUILD_JOBS 限制,避免内存耗尽 -# CI=true: 触发 setup.sh 中的 rsproxy 自适应(Docker 构建在 GitHub runner 上,位于国外) +# CI=true: 供 tool-installer / setup-new.sh 内部识别 CI 环境 # --mount=secret: token 仅在本层可见,不会写入镜像层 RUN --mount=type=secret,id=github_token,required=false \ export CI=true && \ if [ -f /run/secrets/github_token ]; then \ export GITHUB_TOKEN=$(cat /run/secrets/github_token); \ fi && \ - chmod +x ./setup.sh && ./setup.sh && \ + chmod +x ./setup-new.sh && ./setup-new.sh && \ rm -rf \ ~/.cargo/registry/src \ ~/.cargo/registry/cache \ diff --git a/docs/ci-issue-tracker.md b/docs/ci-issue-tracker.md new file mode 100644 index 0000000..fc8a8d8 --- /dev/null +++ b/docs/ci-issue-tracker.md @@ -0,0 +1,109 @@ +# CI 问题追踪记录 + +> 本文档记录 `feat/tool-installer-migration` 分支在 CI 和本地测试中发现的问题及修复措施。 +> 目标:**保证所有工具的安装都能完成**,不因可预见的错误中断。 + +--- + +## 1. zls version_probe 正则不匹配 + +| 项目 | 内容 | +|------|------| +| **发现时间** | 2026-06-10 | +| **发现场景** | root 用户本地执行 `setup-new.sh`,已有 `~/.local/bin/zls` | +| **错误信息** | `❌ Error: Check failed for zls with manager github-release` | +| **根因** | `zls --version` 输出是 `0.15.1`(纯版本号),但 manifest 正则是 `zls (?P...)`,期望带 `zls ` 前缀,永远匹配不上 | +| **为什么 CI 没发现** | CI runner 是全新环境,`~/.local/bin/zls` 不存在 → version_probe 不被调用 → 直接安装,跳过检查 | +| **修复** | `aca1be9` → manifest 中 `[zls.*.version_probe].regex` 改为 `(?P[0-9]+\\.[0-9]+\\.[0-9]+)` | +| **防护措施** | CI 新增 `re-install` job(`aca1be9`),在已安装环境中第二次执行 `install dev`,强制触发所有 version_probe | + +--- + +## 2. `local` 关键字在脚本顶层使用 + +| 项目 | 内容 | +|------|------| +| **发现时间** | 2026-06-10 | +| **发现场景** | 本地执行 `setup-new.sh` 调用 `install-fonts.sh` | +| **错误信息** | `local: can only be used in a function` → `❌ Error: Script failed for fonts` | +| **根因** | `install-fonts.sh` 顶层代码使用 `local` 声明变量,`set -u` 启用后报错 | +| **修复** | `e297bf1` → 移除 `scripts/install-fonts.sh` 中的 `local` 关键字 | +| **防护措施** | 所有脚本使用 `bash -n` 语法检查 | + +--- + +## 3. GitHub mirrors SHA256 校验失败不 fallback + +| 项目 | 内容 | +|------|------| +| **发现时间** | 2026-06-10 | +| **发现场景** | 本地安装 zls(走 mirror 下载) | +| **错误信息** | `❌ Error: Check failed for zls with manager github-release` | +| **根因** | mirror 返回的内容可能与官方不同 → SHA256 不匹配 → `_verify_checksum` 抛 `InstallationError` → **不会** fallback 到官方源 | +| **当前状态** | **已设计方案,代码未实现** | +| **设计方案** | 见 `project_tool_installer_mirror_fallback` memory:`sources` 配置、SHA256 失败触发 fallback | +| **临时措施** | 安装前手动禁用 mirrors:`perl -pi -e 's/^github_mirrors/#github_mirrors/' manifest.toml`,装完恢复 | +| **TODO** | 实现 `_download_asset` 内嵌 SHA256 校验逻辑,不匹配则继续下一个 URL | + +--- + +## 4. `sudo_run` NOPASSWD-only 设计 + +| 项目 | 内容 | +|------|------| +| **发现时间** | 2026-06-09 | +| **发现场景** | 非 root 用户执行 `setup-new.sh`(无 NOPASSWD) | +| **错误信息** | `⚠️ 需要 sudo 权限,但当前用户无 NOPASSWD 配置` | +| **根因** | `sudo_run` 使用 `sudo -n true` 检测,只允许 NOPASSWD | +| **修复** | `b269bbd` → 与 main 分支一致:`if root: direct; else: sudo` | + +--- + +## 5. `cp` 遇到符号链接报 "same file" + +| 项目 | 内容 | +|------|------| +| **发现时间** | 2026-06-09 | +| **发现场景** | 非 root 用户执行 `setup-new.sh` bootstrap | +| **错误信息** | `cp: 'vendor/tool-installer' and '~/.local/bin/tool-installer' are the same file` | +| **根因** | 目标文件已是源文件的符号链接 | +| **修复** | `7d0e928` → `cp + chmod` 改为 `install -m 755` | + +--- + +## 6. cargo config 安装时残留修改 + +| 项目 | 内容 | +|------|------| +| **发现时间** | 2026-06-10 | +| **发现场景** | 安装后 `langs/rust/cargo/config.toml` 被修改 | +| **根因** | `setup-new.sh` 的 `do_install()` 临时禁用 sccache/wild linker,但某些情况下未恢复 | +| **影响** | git 工作树出现意外修改,需手动 `git checkout` 恢复 | +| **状态** | 已观察,未修复(当前恢复逻辑看起来正确,需进一步确认) | + +--- + +## 所有 SHA256 校验状态(2026-06-10 验证) + +| 工具 | 版本 | SHA256 状态 | +|------|------|-------------| +| neovim | v0.12.2 | ✅ | +| helix | 25.07.1 | ✅ | +| zellij | v0.44.3 | ✅ | +| yq | v4.53.3 | ✅ | +| starship | v1.25.1 | ✅ | +| xdotter | v0.5.2 | ✅ | +| cargo-binstall | v1.19.1 | ✅ | +| zola | v0.22.1 | ✅ | +| marksman | 2026-02-08 | ✅ | +| zls | 0.15.1 | ✅ | +| lua-lsp | 3.17.1 | ✅ | + +--- + +## 待完成 + +- [ ] **#2 最高优先级**:实现 GitHub mirrors SHA256 fallback 逻辑 + - `_download_asset()` 内嵌 SHA256 校验 + - 校验失败 → 继续下一个 URL + - 支持 `sources` 配置项 diff --git a/docs/tool-installer-migration-plan.md b/docs/tool-installer-migration-plan.md new file mode 100644 index 0000000..7b1127c --- /dev/null +++ b/docs/tool-installer-migration-plan.md @@ -0,0 +1,220 @@ +# 工具安装管理迁移方案 + +**文档版本:** 1.0 +**更新日期:** 2026-06-04 +**目的:** 将工具安装从 monolithic shell 函数迁移到声明式三层架构,明确 vendor 策略和各层职责边界 + +--- + +## 1. 背景 + +当前 `setup.sh` + `shells/common/install-functions.sh` 中硬编码了 45+ 个工具的 shell 安装逻辑,存在以下问题: + +- **难以维护**:新增/修改工具需要写 shell 函数,重复代码多 +- **跨平台困难**:Linux/macOS 分支逻辑散落在各处 +- **版本管理混乱**:`latest` 和固定版本混用,不可复现 +- **CI 脆弱**:网络波动导致安装失败,无统一 fallback 策略 + +--- + +## 2. 目标架构 + +迁移到 **三层声明式架构**,由 `setup-new.sh` 统一编排: + +``` +┌─────────────────────────────────────────┐ +│ setup-new.sh(编排入口) │ +│ do_bootstrap → do_deploy → do_install │ +└─────────────────────────────────────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ +┌─────────┐ ┌──────────┐ ┌──────────┐ +│ Layer 0 │ │ Layer 1 │ │ Layer 2 │ +│Bootstrap│ │tool- │ │ Post │ +│ │ │installer │ │ Scripts │ +└─────────┘ └──────────┘ └──────────┘ +``` + +### 2.1 Layer 0: Bootstrap + +**职责:** 安装 tool-installer 本身及其绝对必要的前置依赖 + +**包含:** +- 系统包(python3, curl, gh, build-essential 等) +- GitHub CLI 登录(环境变量或交互式) +- **tool-installer 本体**(Python zipapp,从 vendor 复制到 `~/.local/bin`) + +**明确不包含:** +- ❌ cargo-binstall 或其他任何工具的二进制 +- ❌ Rust 工具链 +- ❌ 任何可由 Layer 1 自行获取的工具 + +### 2.2 Layer 1: 声明式工具安装 + +**职责:** 读取声明式配置,安装所有开发工具 + +**输入:** +- `tools.toml` — 定义模块、分组、依赖关系 +- `manifest.toml` — 定义每个工具的安装策略(manager、版本、参数) + +**支持的 Manager 类型:** + +| Manager | 说明 | 示例工具 | +|---------|------|----------| +| `github-release` | 下载预编译 release | neovim, helix, starship | +| `cargo-install` | Cargo 安装(支持 binstall_first)| bat, eza, fd-find | +| `mise` | 语言运行时版本管理 | go, node, zig | +| `npm` / `pip` | 包管理器 | LSP 服务器 | +| `script` | 自定义脚本 | rustup-install | + +**关键设计:** +- `binstall_first = true` 时,优先尝试 `cargo-binstall` 下载预编译二进制,失败自动 fallback 到 `cargo install` +- 所有版本必须 pin,禁止 `latest` +- 跨平台差异在 `manifest.toml` 中用 `[tool.linux]` / `[tool.macos]` 声明,不写死逻辑 + +### 2.3 Layer 2: 后置配置 + +**职责:** 依赖 Layer 1 已安装工具的后续配置 + +**包含:** +- LSP 服务器配置 +- 字体安装 +- 其他需要工具已存在才能执行的配置 + +--- + +## 3. Vendor 策略 + +### 3.1 准入条件 + +Vendor 目录只存放满足以下条件之一的资源: + +| 条件 | 说明 | 示例 | +|------|------|------| +| **自定义工具** | 无标准分发渠道,必须自行构建/打包 | `tool-installer`(Python zipapp)| +| **供应链安全关键脚本** | 需要人工审查,避免 `curl \| sh` | `rustup-init.sh` | +| **自举依赖** | 上层工具依赖它才能工作,且无法通过上层工具自身获取 | `tool-installer` 本身 | + +### 3.2 明确不 Vendor 的内容 + +以下类型**禁止**放入 vendor: + +- ❌ **主流生态工具的二进制**(cargo-binstall, xdotter 等) + - 这些工具有标准分发渠道(GitHub releases, crates.io) + - 应由 Layer 1 的对应 manager 自行获取 + - Vendor 二进制增加维护负担(跨平台、版本更新、架构兼容) + +- ❌ **可由 tool-installer 自行下载的工具** + - tool-installer 的 `github-release` manager 已支持镜像回退 + - `_download_binstall` 已实现 cargo-binstall 的自举下载 + - 预装这些工具会掩盖 tool-installer 自身路径的 bug + +- ❌ **临时 workaround** + - 网络问题的修复应在工具内部解决(timeout、retry、镜像 fallback) + - 不应通过 vendor 二进制绕过 + +### 3.3 当前 Vendor 清单 + +| 文件 | 类型 | 准入理由 | 状态 | +|------|------|----------|------| +| `vendor/tool-installer` | 自定义 zipapp | 无标准分发渠道,Layer 0 必须预装 | ✅ 保留 | +| `vendor/rustup-init.sh` | 审查脚本 | 供应链安全,避免 `curl \| sh` | ✅ 保留 | +| `vendor/cargo-binstall` | 主流二进制 | **违反策略**,应由 tool-installer 自行获取 | ❌ 移除 | +| `vendor/xdotter` | 主流二进制 | **违反策略**,应由 github-release manager 获取 | ❌ 移除 | + +--- + +## 4. 网络问题处理原则 + +### 4.1 分层责任 + +| 层级 | 责任 | +|------|------| +| Layer 0 | 保证 tool-installer 可安装,不处理工具下载 | +| Layer 1 | 负责所有工具的网络获取,内置 retry、timeout、镜像 fallback | +| Manager 内部 | 每个 manager 实现自己的网络容错(如 github-release 的 mirror 列表) | + +### 4.2 cargo-binstall 的自举 + +tool-installer 的 `cargo-install` manager 已实现 `_ensure_binstall`: + +1. 检查 PATH 中的 `cargo-binstall` +2. 检查 `~/.cargo/bin/cargo-binstall` +3. 尝试从 GitHub releases 下载 +4. 以上全部失败 → fallback 到 `cargo install cargo-binstall` + +**Layer 0 不应干预此流程。**预装 cargo-binstall 不仅多余,还会: +- 掩盖 `_ensure_binstall` 的验证 bug +- 导致测试环境无法覆盖真实 fallback 路径 +- 增加 vendor 维护负担 + +--- + +## 5. 实施步骤 + +### Phase 1: 修复当前分支的阻塞问题 + +1. **移除 vendor/cargo-binstall** + - `git rm vendor/cargo-binstall` + - 从 `layer0-bootstrap.sh` 删除 cargo-binstall 复制逻辑 + +2. **修复 tool-installer 的 binstall 验证 bug** + - `tool_installer/managers/commands.py` line 439 + - `[binary, "--version"]` → `[binary, "-V"]` + - 重新打包 `vendor/tool-installer` + +3. **验证 CI 通过** + - 确保 `binstall_first = true` 生效 + - cargo 工具从预编译二进制安装,不再源码编译 + +### Phase 2: 清理违规 vendor + +1. **评估 vendor/xdotter** + - xdotter 已有 GitHub release manager 配置 + - 检查 Docker build 是否仍需要 vendor fallback + - 如不需要,移除 + +2. **更新 vendor/README.md** + - 明确准入条件和禁止清单 + - 更新当前 vendor 清单 + +### Phase 3: 文档补全 + +1. 本文档作为设计基线 +2. 更新 `CONTRIBUTING.md` 中关于 vendor 的指引 +3. `CHANGELOG.md` 记录迁移完成 + +--- + +## 6. 验证方式 + +| 验证项 | 命令/方法 | +|--------|-----------| +| 工作树无未提交更改 | `git status` | +| 无违规 vendor 二进制 | `ls vendor/` 只有 `tool-installer` + `rustup-init.sh` | +| CI 通过(ubuntu + macos)| `gh run list --branch feat/tool-installer-migration` | +| cargo 工具使用预编译 | 日志中无大量 `Compiling` 输出,安装时间 < 5 分钟 | +| binstall_first 生效 | 日志中出现 `cargo-binstall` 下载/安装输出 | + +--- + +## 7. 附录:当前问题复盘 + +### 7.1 CI 超时事件(Run 26939026915) + +**现象:** `setup-new.sh` 在 ubuntu-latest 上运行 1 小时后超时取消。 + +**表面原因:** 29 个 cargo 工具全部从源码编译,`CARGO_BUILD_JOBS=2` 下耗时过长。 + +**根因链:** +1. `layer0-bootstrap.sh` vendor 了 cargo-binstall 到 `~/.cargo/bin` +2. tool-installer 的 `_ensure_binstall` 尝试验证 `~/.cargo/bin/cargo-binstall` +3. 验证调用 `cargo-binstall --version`,但 vendor 的 v1.19.1 不支持无参 `--version` +4. 验证失败 → tool-installer 认为 cargo-binstall 不可用 +5. 所有 `binstall_first = true` 失效 → 全部 fallback 到 `cargo install` 源码编译 + +**修复方向:** +- 移除 vendor cargo-binstall(本就不该存在) +- 修复 tool-installer 验证参数(`--version` → `-V`) +- 让 tool-installer 的自举机制正常工作 diff --git a/langs/rust/cargo/config.toml b/langs/rust/cargo/config.toml index 7abd440..5b5d1e1 100644 --- a/langs/rust/cargo/config.toml +++ b/langs/rust/cargo/config.toml @@ -60,12 +60,12 @@ git-fetch-with-cli = true [target.x86_64-unknown-linux-gnu] linker = "clang" -rustflags = ["-C", "link-arg=--ld-path=wild"] +rustflags = ["-C", "link-arg=--ld-path=ld"] # ========== 编译加速 ========== [build] -rustc-wrapper = "sccache" +#rustc-wrapper = "sccache" # temporarily disabled during install # ========== Profile 优化 ========== diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..47a76c8 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,956 @@ +# Manifest for dotfiles development environment +# Migrated from: dotfiles/shells/common/install-functions.sh +# Tool count: 45+ tools across 6 manager types + +# ============================================================ +# GitHub release manager configuration +# ============================================================ + +[_github-release] +github_mirrors = ["https://ghfast.top", "https://mirror.ghproxy.com"] +timeout = 120 +retry = 3 + +# ============================================================ +# Experience / optional tools +# ============================================================ + +[wezterm] +[wezterm.linux] +manager = "script" +path = "scripts/install-wezterm.sh" +[wezterm.macos] +manager = "script" +path = "scripts/install-wezterm.sh" + +# ============================================================ +# System packages (managed by tool-installer) +# ============================================================ + +[system-packages] +[system-packages.linux] +manager = "script" +path = "scripts/install-system-packages.sh" +[system-packages.macos] +manager = "script" +path = "scripts/install-system-packages.sh" + +# ============================================================ +# Fonts +# ============================================================ + +[fonts] +[fonts.linux] +manager = "script" +path = "scripts/install-fonts.sh" +[fonts.macos] +manager = "script" +path = "scripts/install-fonts.sh" + +# ============================================================ +# Languages & Toolchains +# ============================================================ + +# 安装 rustup 本身(vendor 脚本,已审查) +[rustup-install] +[rustup-install.linux] +manager = "script" +path = "scripts/install-rustup.sh" +[rustup-install.macos] +manager = "script" +path = "scripts/install-rustup.sh" + +# 通过 mise 安装语言运行时(在 cargo-tools 之后执行) +[mise-install] +[mise-install.linux] +manager = "script" +path = "scripts/mise-install.sh" +[mise-install.macos] +manager = "script" +path = "scripts/mise-install.sh" + + +# 配置 Rust 工具链(rustup manager) +[rust] +[rust.linux] +manager = "rustup" +components = ["clippy", "rustfmt", "rust-analyzer"] +profile = "minimal" +set_default = true +[rust.macos] +manager = "rustup" +components = ["clippy", "rustfmt", "rust-analyzer"] +profile = "minimal" +set_default = true + + +# ============================================================ +# Editors +# ============================================================ + +[neovim] +[neovim.linux] +manager = "github-release" +repo = "neovim/neovim" +asset = "nvim-linux-x86_64.tar.gz" +bin = "nvim-linux-x86_64/bin/nvim" +install_name = "nvim" +[neovim.linux.version_probe] +command = ["{bin}", "--version"] +regex = "^NVIM v(?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[neovim.linux.x86_64] +sha256 = "31cf85945cb600d96cdf69f88bc68bec814acbff50863c5546adef3a1bcef260" + + +[neovim.macos] +manager = "github-release" +repo = "neovim/neovim" +asset = "nvim-macos-arm64.tar.gz" +bin = "nvim-macos-arm64/bin/nvim" +install_name = "nvim" +[neovim.macos.version_probe] +command = ["{bin}", "--version"] +regex = "^NVIM v(?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[neovim.macos.aarch64] +sha256 = "eeddee1009734f9071266e6b1b8a70308cb60cbcc45f5e1c1023adc471450fee" + +[neovim.macos.x86_64] +asset = "nvim-macos-x86_64.tar.gz" +bin = "nvim-macos-x86_64/bin/nvim" +sha256 = "d6fb1dcb2f92543dec7dc86f2c50dc4c3aa6a801f5c67d09bba5d8d157440a9a" + +[neovim.linux.aarch64] +asset = "nvim-linux-arm64.tar.gz" +bin = "nvim-linux-arm64/bin/nvim" +sha256 = "f697d4e4582b6e4b5c3c26e76e06ce26efa08ba1768e03fd2733fcc422bb0490" + +[helix] +[helix.linux] +manager = "github-release" +repo = "helix-editor/helix" +asset = "helix-{version}-{arch}-linux.tar.xz" +bin = "helix-{version}-{arch}-linux/hx" +install_name = "hx" +[helix.linux.version_probe] +command = ["{bin}", "--version"] +regex = "^helix (?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[helix.linux.x86_64] +sha256 = "3f08e63ecd388fff657ad39722f88bb03dcf326f1f2da2700d99e1dc40ab2e8b" + + +[helix.macos] +manager = "github-release" +repo = "helix-editor/helix" +asset = "helix-{version}-aarch64-macos.tar.xz" +bin = "helix-{version}-aarch64-macos/hx" +install_name = "hx" +[helix.macos.version_probe] +command = ["{bin}", "--version"] +regex = "^helix (?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[helix.macos.aarch64] +sha256 = "00b1651b4fdbbe0a2ae981c8e76b858bd26a7c33f5b3583f3b6bb9137d54f1ff" + +[helix.macos.x86_64] +asset = "helix-{version}-x86_64-macos.tar.xz" +bin = "helix-{version}-x86_64-macos/hx" + +[helix.linux.aarch64] +sha256 = "ce23fa8d395e633e3e54c052012f11965d91d8d5c2bfa659685f50430b4f8175" + +[zellij] +[zellij.linux] +manager = "github-release" +repo = "zellij-org/zellij" +asset = "zellij-{arch}-unknown-linux-musl.tar.gz" +bin = "zellij" +[zellij.linux.version_probe] +command = ["{bin}", "--version"] +regex = "^zellij (?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[zellij.linux.x86_64] +sha256 = "0f7c346788627f506c0a28296517768633cff24fc822a739f8264b640ecad751" + + +[zellij.macos] +manager = "github-release" +repo = "zellij-org/zellij" +asset = "zellij-aarch64-apple-darwin.tar.gz" +bin = "zellij" +[zellij.macos.version_probe] +command = ["{bin}", "--version"] +regex = "^zellij (?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[zellij.macos.aarch64] +sha256 = "b6acf83a7739cf5f0f4e9bd47709642d4d98acbbf8c34d4a12c6e706f531da61" + +[zellij.macos.x86_64] +asset = "zellij-x86_64-apple-darwin.tar.gz" +sha256 = "TODO" + +[zellij.linux.aarch64] +sha256 = "6f028bb569d29be968c961249c5f80d5336ad4ad4b3cd79af8e32afab57b0948" + +# ============================================================ +# GitHub Release Tools +# ============================================================ + +[yq] +[yq.linux] +manager = "github-release" +repo = "mikefarah/yq" +asset = "yq_linux_amd64.tar.gz" +bin = "yq_linux_amd64" +install_name = "yq" +[yq.linux.version_probe] +command = ["{bin}", "--version"] +regex = "version v?(?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[yq.linux.x86_64] +sha256 = "b4077cab0f9ee5ce8381e602d090daa69a0afb7e57eb9a5b20e9cb416d7f6794" + + +[yq.macos] +manager = "github-release" +repo = "mikefarah/yq" +asset = "yq_darwin_arm64" +bin = "yq_darwin_arm64" +install_name = "yq" +[yq.macos.version_probe] +command = ["{bin}", "--version"] +regex = "version v?(?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[yq.macos.aarch64] +sha256 = "877de31753a4dd2401aa048937aa9a7fc4d5f6ce858cf31508c5802954297213" + +[yq.macos.x86_64] +asset = "yq_darwin_amd64" +bin = "yq_darwin_amd64" +sha256 = "TODO" + +[yq.linux.aarch64] +asset = "yq_linux_arm64.tar.gz" +bin = "yq_linux_arm64" +sha256 = "10a4a2093090363a00b55ad52e132a082f9652970cb8f1ad35a1ae048b917e6e" + +[marksman] +[marksman.linux] +manager = "github-release" +repo = "artempyanykh/marksman" +asset = "marksman-linux-x64" +bin = "marksman-linux-x64" +install_name = "marksman" + +[marksman.linux.x86_64] +sha256 = "be5098e8213219269c47fc0d916a66fa31ce0602ec967475c722260aabf26087" + + +[marksman.macos] +manager = "github-release" +repo = "artempyanykh/marksman" +asset = "marksman-macos" +bin = "marksman-macos" +install_name = "marksman" + +[marksman.macos.aarch64] +sha256 = "6a801c17b5ac0dba69787c5282b3b3bd416e66c96253fae098d311c6bbd1833b" + +[marksman.macos.x86_64] +sha256 = "6a801c17b5ac0dba69787c5282b3b3bd416e66c96253fae098d311c6bbd1833b" + +[marksman.linux.aarch64] +asset = "marksman-linux-arm64" +bin = "marksman-linux-arm64" +sha256 = "db8e124527f7f8048e3e6c91821b9c52ef173d92c01e47d221bf1337afd962fb" + +[zls] +[zls.linux] +manager = "github-release" +repo = "zigtools/zls" +asset = "zls-{arch}-linux.tar.xz" +bin = "zls" +install_name = "zls" +[zls.linux.version_probe] +command = ["{bin}", "--version"] +regex = "(?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[zls.linux.x86_64] +sha256 = "3bb38f522cb23213e8c075ac6b170273fe49b4274b8c12b034cc496407400067" + + +[zls.macos] +manager = "github-release" +repo = "zigtools/zls" +asset = "zls-aarch64-macos.tar.xz" +bin = "zls" +install_name = "zls" +[zls.macos.version_probe] +command = ["{bin}", "--version"] +regex = "(?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[zls.macos.aarch64] +sha256 = "a6b3f1b10d77f37f3b9d962093f030334b083f48eb2607a4b3ccb72de2958133" + +[zls.macos.x86_64] +asset = "zls-aarch64-macos.tar.xz" +sha256 = "b93ec549f8558a7e85984a840e9276d274f1059b54ade4254296ef4982958359" + +[zls.linux.aarch64] +sha256 = "a2daa860a0e0cd1410491ff9703c6aaca96defd833b88af6a9811d6ff04fc13b" + +[lua-lsp] +[lua-lsp.linux] +manager = "script" +path = "scripts/install-lua-lsp.sh" +[lua-lsp.macos] +manager = "script" +path = "scripts/install-lua-lsp.sh" + +[xdotter] +[xdotter.linux] +manager = "github-release" +repo = "CNCSMonster/xdotter" +asset = "xd-x86_64-unknown-linux-musl" +bin = "xd-x86_64-unknown-linux-musl" +install_name = "xd" + +[xdotter.linux.x86_64] +sha256 = "1c549083a47f02a1e7f6a89dc9189f77440d801448e78f6b98d4e080f63f3d1c" + + +[xdotter.macos] +manager = "github-release" +repo = "CNCSMonster/xdotter" +asset = "xd-arm64-apple-darwin" +bin = "xd-arm64-apple-darwin" +install_name = "xd" + +[xdotter.macos.aarch64] +sha256 = "1c767f90daf5a78f0788a45724aa2a006db0b1e002937108f8d1be12688c64df" + +[xdotter.macos.x86_64] +asset = "xd-x86_64-apple-darwin" +sha256 = "TODO" + +[xdotter.linux.aarch64] +asset = "xd-aarch64-unknown-linux-gnu" +bin = "xd-aarch64-unknown-linux-gnu" +sha256 = "1c549083a47f02a1e7f6a89dc9189f77440d801448e78f6b98d4e080f63f3d1c" + +[cargo-binstall] +[cargo-binstall.linux] +manager = "github-release" +repo = "cargo-bins/cargo-binstall" +asset = "cargo-binstall-x86_64-unknown-linux-musl.tgz" +bin = "cargo-binstall" +install_name = "cargo-binstall" + +[cargo-binstall.linux.x86_64] +sha256 = "4a50fcf01418862e2fa8e4076cb6cb80ff4061b0c0b1464e71a63ce01ee29bde" + + +[cargo-binstall.macos] +manager = "github-release" +repo = "cargo-bins/cargo-binstall" +asset = "cargo-binstall-aarch64-apple-darwin.zip" +bin = "cargo-binstall" +install_name = "cargo-binstall" + +[cargo-binstall.macos.aarch64] +sha256 = "bf9da6a27e432784f361cfbc70a6d04e548abc548470ae9a7587c3cffb8fb0a7" + +[cargo-binstall.macos.x86_64] +asset = "cargo-binstall-x86_64-apple-darwin.zip" +sha256 = "TODO" + +[cargo-binstall.linux.aarch64] +asset = "cargo-binstall-aarch64-unknown-linux-musl.tgz" +sha256 = "2001eee8da26705ad9627e57a25c23eb4639647521205f3e4a7b4e09d067d199" + +# ============================================================ +# CLI Tools (cargo-install, locked) +# ============================================================ + +[bat] +[bat.linux] +manager = "cargo-install" +pkg = "bat" +locked = true +binstall_first = true + +[bat.macos] +manager = "cargo-install" +pkg = "bat" +locked = true +binstall_first = true + +[eza] +[eza.linux] +manager = "cargo-install" +pkg = "eza" +locked = true +binstall_first = true + +[eza.macos] +manager = "cargo-install" +pkg = "eza" +locked = true +binstall_first = true + +[fd-find] +[fd-find.linux] +manager = "cargo-install" +pkg = "fd-find" +bin = "fd" +locked = true +binstall_first = true + +[fd-find.macos] +manager = "cargo-install" +pkg = "fd-find" +bin = "fd" +locked = true +binstall_first = true + +[starship] +[starship.linux] +manager = "github-release" +repo = "starship/starship" +asset = "starship-x86_64-unknown-linux-gnu.tar.gz" +bin = "starship" +[starship.linux.version_probe] +command = ["{bin}", "--version"] +regex = "^starship (?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[starship.linux.x86_64] +sha256 = "4488c11ca632327d1f1f16fb2f102c0646094c35479cd5435991385da43c61ac" + + +[starship.macos] +manager = "github-release" +repo = "starship/starship" +asset = "starship-aarch64-apple-darwin.tar.gz" +bin = "starship" +[starship.macos.version_probe] +command = ["{bin}", "--version"] +regex = "^starship (?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[starship.macos.aarch64] +sha256 = "1062a2363489b9335529b83204472f02633c08fc3609f1b325be5eba36feb631" + +[starship.macos.x86_64] +asset = "starship-x86_64-apple-darwin.tar.gz" +sha256 = "TODO" + +[starship.linux.aarch64] +asset = "starship-aarch64-unknown-linux-musl.tar.gz" +sha256 = "56b9ff412bbf374d29b99e5ac09a849124cb37a0a13121e8470df32de53c1ea6" + +[zoxide] +[zoxide.linux] +manager = "cargo-install" +pkg = "zoxide" +locked = true +binstall_first = true + +[zoxide.macos] +manager = "cargo-install" +pkg = "zoxide" +locked = true +binstall_first = true + +[tokei] +[tokei.linux] +manager = "cargo-install" +pkg = "tokei" +locked = true +binstall_first = true + +[tokei.macos] +manager = "cargo-install" +pkg = "tokei" +locked = true +binstall_first = true + +[navi] +[navi.linux] +manager = "cargo-install" +pkg = "navi" +locked = true +binstall_first = true + +[navi.macos] +manager = "cargo-install" +pkg = "navi" +locked = true +binstall_first = true + +[gitui] +[gitui.linux] +manager = "cargo-install" +pkg = "gitui" +locked = true +binstall_first = true + +[gitui.macos] +manager = "cargo-install" +pkg = "gitui" +locked = true +binstall_first = true + +[kondo] +[kondo.linux] +manager = "cargo-install" +pkg = "kondo" +locked = true +binstall_first = true + +[kondo.macos] +manager = "cargo-install" +pkg = "kondo" +locked = true +binstall_first = true + +[jaq] +[jaq.linux] +manager = "cargo-install" +pkg = "jaq" +locked = true +binstall_first = true + +[jaq.macos] +manager = "cargo-install" +pkg = "jaq" +locked = true +binstall_first = true + +[mdbook] +[mdbook.linux] +manager = "cargo-install" +pkg = "mdbook" +locked = true +binstall_first = true + +[mdbook.macos] +manager = "cargo-install" +pkg = "mdbook" +locked = true +binstall_first = true + +[mdbook-mermaid] +[mdbook-mermaid.linux] +manager = "cargo-install" +pkg = "mdbook-mermaid" +locked = true +binstall_first = true + +[mdbook-mermaid.macos] +manager = "cargo-install" +pkg = "mdbook-mermaid" +locked = true +binstall_first = true + +[gen-mdbook-summary] +[gen-mdbook-summary.linux] +manager = "cargo-install" +pkg = "gen-mdbook-summary" +bin = "gms" +locked = true +binstall_first = true + +[gen-mdbook-summary.macos] +manager = "cargo-install" +pkg = "gen-mdbook-summary" +bin = "gms" +locked = true +binstall_first = true + +[tree-sitter-cli] +[tree-sitter-cli.linux] +manager = "cargo-install" +pkg = "tree-sitter-cli" +bin = "tree-sitter" +locked = true +binstall_first = true +binstall_retry = 2 +binstall_fallback_compile = true + +[tree-sitter-cli.macos] +manager = "cargo-install" +pkg = "tree-sitter-cli" +bin = "tree-sitter" +locked = true +binstall_first = true +binstall_retry = 2 +binstall_fallback_compile = true + +[tree-sitter-grep] +[tree-sitter-grep.linux] +manager = "cargo-install" +pkg = "tree-sitter-grep" +locked = true +binstall_first = true +binstall_retry = 2 +binstall_fallback_compile = true + +[tree-sitter-grep.macos] +manager = "cargo-install" +pkg = "tree-sitter-grep" +locked = true +binstall_first = true +binstall_retry = 2 +binstall_fallback_compile = true + +[tree-sitter-show-ast] +[tree-sitter-show-ast.linux] +manager = "cargo-install" +pkg = "tree-sitter-show-ast" +locked = true +binstall_first = true +binstall_retry = 2 +binstall_fallback_compile = true + +[tree-sitter-show-ast.macos] +manager = "cargo-install" +pkg = "tree-sitter-show-ast" +locked = true +binstall_first = true +binstall_retry = 2 +binstall_fallback_compile = true + +[macchina] +[macchina.linux] +manager = "cargo-install" +pkg = "macchina" +locked = true +binstall_first = true + +[macchina.macos] +manager = "cargo-install" +pkg = "macchina" +locked = true +binstall_first = true + +[conceal] +[conceal.linux] +manager = "cargo-install" +pkg = "conceal" +locked = true +binstall_first = true + +[conceal.macos] +manager = "cargo-install" +pkg = "conceal" +locked = true +binstall_first = true + +[rust-script] +[rust-script.linux] +manager = "cargo-install" +pkg = "rust-script" +locked = true +binstall_first = true + +[rust-script.macos] +manager = "cargo-install" +pkg = "rust-script" +locked = true +binstall_first = true + +[parallel-disk-usage] +[parallel-disk-usage.linux] +manager = "cargo-install" +pkg = "parallel-disk-usage" +bin = "pdu" +locked = true +binstall_first = true + +[parallel-disk-usage.macos] +manager = "cargo-install" +pkg = "parallel-disk-usage" +bin = "pdu" +locked = true +binstall_first = true + +[nu] +[nu.linux] +manager = "cargo-install" +pkg = "nu" +locked = true +binstall_first = true + +[nu.macos] +manager = "cargo-install" +pkg = "nu" +locked = true +binstall_first = true + +[uv] +[uv.linux] +manager = "script" +path = "scripts/install-uv.sh" + +[uv.macos] +manager = "script" +path = "scripts/install-uv.sh" + +[mise] +[mise.linux] +manager = "cargo-install" +pkg = "mise" +locked = true +binstall_first = true + +[mise.macos] +manager = "cargo-install" +pkg = "mise" +locked = true +binstall_first = true + +[cargo-audit] +[cargo-audit.linux] +manager = "cargo-install" +pkg = "cargo-audit" +locked = true +binstall_first = true + +[cargo-audit.macos] +manager = "cargo-install" +pkg = "cargo-audit" +locked = true +binstall_first = true + +[cargo-fuzz] +[cargo-fuzz.linux] +manager = "cargo-install" +pkg = "cargo-fuzz" +locked = true +binstall_first = true + +[cargo-fuzz.macos] +manager = "cargo-install" +pkg = "cargo-fuzz" +locked = true +binstall_first = true + +[grcov] +[grcov.linux] +manager = "cargo-install" +pkg = "grcov" +locked = true +binstall_first = true + +[grcov.macos] +manager = "cargo-install" +pkg = "grcov" +locked = true +binstall_first = true + +[cargo-tarpaulin] +[cargo-tarpaulin.linux] +manager = "cargo-install" +pkg = "cargo-tarpaulin" +locked = true +binstall_first = true + +[cargo-tarpaulin.macos] +manager = "cargo-install" +pkg = "cargo-tarpaulin" +locked = true +binstall_first = true + +[sccache] +[sccache.linux] +manager = "cargo-install" +pkg = "sccache" +locked = true +binstall_first = true + +[sccache.macos] +manager = "cargo-install" +pkg = "sccache" +locked = true +binstall_first = true + +[wild-linker] +[wild-linker.linux] +manager = "cargo-install" +pkg = "wild-linker" +bin = "wild" +locked = true +binstall_first = true + +[wild-linker.macos] +manager = "cargo-install" +pkg = "wild-linker" +bin = "wild" +locked = true +binstall_first = true + +[zola] +[zola.linux] +manager = "github-release" +repo = "getzola/zola" +asset = "zola-{version}-x86_64-unknown-linux-musl.tar.gz" +bin = "zola" +install_name = "zola" +[zola.linux.version_probe] +command = ["{bin}", "--version"] +regex = "zola (?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[zola.linux.x86_64] +sha256 = "227df99b664421240a8ba77747067c51259b08159125d5603763b3b173b9a881" + + +[zola.macos] +manager = "github-release" +repo = "getzola/zola" +asset = "zola-{version}-aarch64-apple-darwin.tar.gz" +bin = "zola" +install_name = "zola" +[zola.macos.version_probe] +command = ["{bin}", "--version"] +regex = "zola (?P[0-9]+\\.[0-9]+\\.[0-9]+)" + +[zola.macos.aarch64] +sha256 = "46ac45a9e7628dba8593b124ee8794f4f9aa1c6b569918ecd4bbc5d0be190515" + +[zola.macos.x86_64] +asset = "zola-{version}-x86_64-apple-darwin.tar.gz" +sha256 = "TODO" + +[zola.linux.aarch64] +asset = "zola-v0.22.1-aarch64-unknown-linux-gnu.tar.gz" +sha256 = "8af437ec6352f33ccd24d7a1cfcb54a3db95d3ce376dc69525b4ef3fb6b8c1d1" + +# ============================================================ +# Languages & Runtimes (via mise) +# ============================================================ + +[go] +[go.linux] +manager = "mise" +plugin = "go" + +[go.macos] +manager = "mise" +plugin = "go" + +[node] +[node.linux] +manager = "mise" +plugin = "node" + +[node.macos] +manager = "mise" +plugin = "node" + +[pnpm] +[pnpm.linux] +manager = "mise" +plugin = "pnpm" + +[pnpm.macos] +manager = "mise" +plugin = "pnpm" + +[zig] +[zig.linux] +manager = "mise" +plugin = "zig" + +[zig.macos] +manager = "mise" +plugin = "zig" + +[yazi] +[yazi.linux] +manager = "mise" +plugin = "yazi" + +[yazi.macos] +manager = "mise" +plugin = "yazi" + +[gopls] +[gopls.linux] +manager = "mise" +plugin = "go:golang.org/x/tools/gopls" + +# ============================================================ +# Node.js Global Tools (npm-global) +# ============================================================ + +[gopls.macos] +manager = "mise" +plugin = "go:golang.org/x/tools/gopls" + +# ============================================================ +# Node.js Global Tools (npm-global) +# ============================================================ + +[typescript-lsp] +[typescript-lsp.linux] +manager = "npm-global" +pkg = "typescript-language-server" +bin = "typescript-language-server" + +[typescript-lsp.macos] +manager = "npm-global" +pkg = "typescript-language-server" +bin = "typescript-language-server" + +[yaml-lsp] +[yaml-lsp.linux] +manager = "npm-global" +pkg = "yaml-language-server" +bin = "yaml-language-server" + +[yaml-lsp.macos] +manager = "npm-global" +pkg = "yaml-language-server" +bin = "yaml-language-server" + +[bash-lsp] +[bash-lsp.linux] +manager = "npm-global" +pkg = "bash-language-server" +bin = "bash-language-server" + +[bash-lsp.macos] +manager = "npm-global" +pkg = "bash-language-server" +bin = "bash-language-server" + +[qwen-code] +[qwen-code.linux] +manager = "npm-global" +pkg = "@qwen-code/qwen-code" +bin = "qwen" + +# ============================================================ +# Python Tools (uv-tool) +# ============================================================ + +[qwen-code.macos] +manager = "npm-global" +pkg = "@qwen-code/qwen-code" +bin = "qwen" + +# ============================================================ +# Python Tools (uv-tool) +# ============================================================ + +[pyright] +[pyright.linux] +manager = "uv-tool" +pkg = "pyright" +bin = "pyright" + +[pyright.macos] +manager = "uv-tool" +pkg = "pyright" +bin = "pyright" diff --git a/mise/config.toml b/mise/config.toml index a061024..b7f4995 100644 --- a/mise/config.toml +++ b/mise/config.toml @@ -1,10 +1,10 @@ [tools] go = "1.23.4" -node = "latest" +node = "22.22.3" pnpm = "10.20.0" yazi = "26.1.22" -zig = "latest" -"go:golang.org/x/tools/gopls" = "latest" +zig = "0.16.0" +"go:golang.org/x/tools/gopls" = "0.22.0" [settings] # 网络重试配置:Docker 构建时网络不稳定,需要重试 diff --git a/scripts/install-fonts.sh b/scripts/install-fonts.sh new file mode 100755 index 0000000..e727c31 --- /dev/null +++ b/scripts/install-fonts.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# Font installation script for tool-installer (script manager) +# Installs system fonts + FiraCode Nerd Font +# Always exits 0; prints warnings on failure instead of failing. +set -uo pipefail + +OS="$(uname -s)" + +if [[ "$OS" == "Darwin" ]]; then + if command -v brew &>/dev/null; then + echo "通过 Homebrew 安装字体..." + brew install --cask --yes \ + font-jetbrains-mono \ + font-fira-code \ + font-fira-code-nerd-font \ + font-noto-sans-cjk \ + font-noto-color-emoji \ + 2>/dev/null || echo "⚠️ 部分字体可能已安装,继续..." + else + echo "⚠️ Homebrew 未安装,跳过字体安装" + fi + exit 0 +fi + +if [[ "$OS" != "Linux" ]]; then + echo "不支持的系统: $OS" + exit 0 +fi + +is_interactive_tty() { + [ -t 1 ] +} + +wait_for_dpkg_lock() { + local max_wait=120 + local waited=0 + while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || \ + fuser /var/lib/apt/lists/lock >/dev/null 2>&1 || \ + fuser /var/lib/dpkg/lock >/dev/null 2>&1 || \ + fuser /var/cache/apt/archives/lock >/dev/null 2>&1; do + if [ $waited -ge $max_wait ]; then + echo "⚠️ dpkg 锁等待超时,跳过系统字体包安装" + return 1 + fi + sleep 5 + waited=$((waited + 5)) + done + return 0 +} + +install_system_fonts() { + wait_for_dpkg_lock || return 0 + + if [ "$EUID" -eq 0 ]; then + apt-get install -y --no-install-recommends \ + fontconfig \ + fonts-noto-cjk \ + fonts-noto-color-emoji \ + fonts-jetbrains-mono \ + fonts-dejavu-core || echo "⚠️ 系统字体包安装失败" + elif is_interactive_tty; then + echo "🔐 安装系统字体包需要 sudo 权限..." + sudo apt-get install -y --no-install-recommends \ + fontconfig \ + fonts-noto-cjk \ + fonts-noto-color-emoji \ + fonts-jetbrains-mono \ + fonts-dejavu-core || echo "⚠️ 系统字体包安装失败" + else + echo "⚠️ 非交互环境,跳过系统字体包安装(避免 sudo 密码输入挂起)" + fi +} + +install_fira_code() { + local user_font_dir="$HOME/.local/share/fonts/FiraCode-Nerd-Font" + + if fc-list 2>/dev/null | grep -qi "FiraCode.*Nerd"; then + echo "FiraCode Nerd Font 已安装,跳过" + return 0 + fi + + echo "安装 FiraCode Nerd Font -> $user_font_dir ..." + local fira_version="v3.4.0" + local fira_asset="FiraCode.zip" + local fira_repo="ryanoasis/nerd-fonts" + local fira_tmp="/tmp/FiraCode-Nerd-Font-${UID:-$$}.zip" + + rm -f "$fira_tmp" + + local downloaded=false + for mirror in "https://ghfast.top/https://github.com/${fira_repo}/releases/download/${fira_version}/${fira_asset}" \ + "https://mirror.ghproxy.com/https://github.com/${fira_repo}/releases/download/${fira_version}/${fira_asset}" \ + "https://github.com/${fira_repo}/releases/download/${fira_version}/${fira_asset}"; do + echo "尝试下载: $mirror" + if wget --tries=2 --timeout=180 --connect-timeout=15 "$mirror" -O "$fira_tmp" 2>/dev/null; then + downloaded=true + break + fi + echo "⚠️ 该镜像失败,尝试下一个..." + done + + if [ "$downloaded" != true ]; then + echo "⚠️ FiraCode Nerd Font 下载失败,跳过" + return 0 + fi + + mkdir -p "$user_font_dir" + if command -v unzip &>/dev/null; then + unzip -o "$fira_tmp" -d "$user_font_dir" 2>/dev/null || echo "⚠️ FiraCode 解压失败" + else + if [ "$EUID" -eq 0 ]; then + apt-get install -y unzip >/dev/null 2>&1 || true + elif is_interactive_tty; then + echo "🔐 安装 unzip 需要 sudo 权限..." + sudo apt-get install -y unzip >/dev/null 2>&1 || true + fi + if command -v unzip &>/dev/null; then + unzip -o "$fira_tmp" -d "$user_font_dir" 2>/dev/null || echo "⚠️ FiraCode 解压失败" + else + echo "⚠️ 无 unzip,尝试 Python 解压..." + python3 -c "import zipfile; zipfile.ZipFile('$fira_tmp').extractall('$user_font_dir')" 2>/dev/null || echo "⚠️ FiraCode 解压失败" + fi + fi + rm -f "$fira_tmp" + + if command -v fc-cache &>/dev/null; then + fc-cache -f "$user_font_dir" 2>/dev/null || true + fi +} + +install_system_fonts +install_fira_code + +exit 0 diff --git a/scripts/install-lua-lsp.sh b/scripts/install-lua-lsp.sh new file mode 100755 index 0000000..b1fd59f --- /dev/null +++ b/scripts/install-lua-lsp.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Lua language server installer for tool-installer (script manager) +# Downloads the full GitHub release archive and keeps the directory structure, +# because lua-language-server's bin/lua-language-server is a launcher script +# that depends on main.lua and script/ at runtime. +set -uo pipefail + +VERSION="${TOOL_INSTALLER_VERSION:-3.17.1}" +OS="$(uname -s)" +ARCH="$(uname -m)" + +case "$OS" in + Linux) + case "$ARCH" in + x86_64) ASSET="lua-language-server-${VERSION}-linux-x64.tar.gz" ;; + aarch64) ASSET="lua-language-server-${VERSION}-linux-arm64.tar.gz" ;; + *) + echo "⚠️ 不支持的架构: $ARCH" + exit 0 + ;; + esac + ;; + Darwin) + case "$ARCH" in + arm64) ASSET="lua-language-server-${VERSION}-darwin-arm64.tar.gz" ;; + x86_64) ASSET="lua-language-server-${VERSION}-darwin-x64.tar.gz" ;; + *) + echo "⚠️ 不支持的架构: $ARCH" + exit 0 + ;; + esac + ;; + *) + echo "⚠️ 不支持的操作系统: $OS" + exit 0 + ;; +esac + +INSTALL_DIR="$HOME/.local/share/lua-language-server" +BIN_DIR="$HOME/.local/bin" +BIN_PATH="$BIN_DIR/lua-language-server" + +mkdir -p "$INSTALL_DIR" "$BIN_DIR" + +# 幂等检查:已存在且能正常返回版本则跳过 +if [ -x "$BIN_PATH" ]; then + if "$BIN_PATH" --version >/dev/null 2>&1; then + echo "lua-language-server 已安装且可用,跳过" + exit 0 + fi + echo "检测到旧的 lua-language-server 不完整,重新安装..." +fi + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +echo "下载 lua-language-server ${VERSION} (${ASSET})..." + +downloaded=false +for mirror in "https://ghfast.top/https://github.com/LuaLS/lua-language-server/releases/download/${VERSION}/${ASSET}" \ + "https://mirror.ghproxy.com/https://github.com/LuaLS/lua-language-server/releases/download/${VERSION}/${ASSET}" \ + "https://github.com/LuaLS/lua-language-server/releases/download/${VERSION}/${ASSET}"; do + echo "尝试下载: $mirror" + if wget --tries=2 --timeout=180 --connect-timeout=15 "$mirror" -O "$TMP_DIR/$ASSET" 2>/dev/null; then + downloaded=true + break + fi + echo "⚠️ 该镜像失败,尝试下一个..." +done + +if [ "$downloaded" != true ]; then + echo "⚠️ lua-language-server 下载失败,跳过" + exit 0 +fi + +# 清理旧安装,然后完整解压 +rm -rf "$INSTALL_DIR" +mkdir -p "$INSTALL_DIR" +if ! tar -xzf "$TMP_DIR/$ASSET" -C "$INSTALL_DIR"; then + echo "⚠️ lua-language-server 解压失败,跳过" + exit 0 +fi + +# 创建包装脚本,指向实际 bin +cat > "$BIN_PATH" </dev/null; then + echo "安装 Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || { + echo "⚠️ Homebrew 安装失败" + exit 0 + } + fi + echo "通过 Homebrew 安装基础系统包..." + brew install --yes python3 gh fzf ripgrep tree git || echo "⚠️ 部分包安装失败" + exit 0 +fi + +if [[ "$OS" == "Linux" ]]; then + echo "检查并安装基础系统包..." + missing=() + for pkg in python3 curl gh build-essential pkg-config libssl-dev \ + libbz2-dev libreadline-dev libsqlite3-dev liblzma-dev \ + fzf ripgrep zsh tree git htop; do + command -v "$pkg" &>/dev/null || dpkg -s "$pkg" &>/dev/null || missing+=("$pkg") + done + if [ ${#missing[@]} -eq 0 ]; then + echo "✅ 基础系统包已就绪" + exit 0 + fi + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update -qq || echo "⚠️ apt-get update 失败" + sudo apt-get install -y "${missing[@]}" || echo "⚠️ 部分包安装失败" + exit 0 +fi + +echo "不支持的系统: $OS" +exit 0 diff --git a/scripts/install-uv.sh b/scripts/install-uv.sh new file mode 100755 index 0000000..5fa6e7a --- /dev/null +++ b/scripts/install-uv.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# 安装 uv (Python 包管理器) +# 使用官方推荐的安装方式:从 astral.sh CDN 下载预编译二进制 +# 不依赖 GitHub,适合国内网络环境 +set -eo pipefail + +# 检查是否已安装 +if command -v uv &>/dev/null; then + INSTALLED_VER=$(uv --version 2>&1 | head -1 || echo "") + if [ -n "$INSTALLED_VER" ]; then + echo "uv 已安装:$INSTALLED_VER,跳过" + exit 0 + fi +fi + +echo "安装 uv(从 astral.sh 下载)..." +mkdir -p ~/.cargo/bin + +# 官方安装脚本(会检测架构和操作系统) +curl -LsSf https://astral.sh/uv/install.sh 2>/dev/null | sh + +# 验证安装结果 +if command -v uv &>/dev/null; then + echo "✅ uv 安装成功:$(uv --version)" +else + # 安装脚本可能放到了 ~/.cargo/bin,确认 + if [ -x ~/.cargo/bin/uv ]; then + echo "✅ uv 安装成功:$(~/.cargo/bin/uv --version)" + else + echo "❌ uv 安装失败" + exit 1 + fi +fi diff --git a/scripts/install-wezterm.sh b/scripts/install-wezterm.sh new file mode 100755 index 0000000..4bf7252 --- /dev/null +++ b/scripts/install-wezterm.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# WezTerm installation script for tool-installer (script manager) +# Handles apt.fury.io source on Linux, brew cask on macOS +set -euo pipefail + +OS="$(uname -s)" +ARCH="$(uname -m)" + +if [[ "$OS" == "Darwin" ]]; then + if ! command -v brew &>/dev/null; then + echo "Homebrew 未安装,跳过 WezTerm" + exit 0 + fi + echo "通过 Homebrew cask 安装 WezTerm..." + brew install --cask wezterm 2>/dev/null || echo "⚠️ WezTerm 安装失败,跳过" + exit 0 +fi + +if [[ "$OS" == "Linux" ]]; then + if command -v wezterm &>/dev/null || command -v wezterm-gui &>/dev/null; then + echo "WezTerm 已安装,跳过" + exit 0 + fi + + echo "添加 WezTerm apt 源..." + curl -fsSL --retry 3 --connect-timeout 15 https://apt.fury.io/wez/gpg.key | \ + sudo gpg --yes --dearmor -o /usr/share/keyrings/wezterm-fury.gpg 2>/dev/null || { + echo "⚠️ 添加 WezTerm GPG 密钥失败,跳过" + exit 0 + } + + echo 'deb [signed-by=/usr/share/keyrings/wezterm-fury.gpg] https://apt.fury.io/wez/ * *' | \ + sudo tee /etc/apt/sources.list.d/wezterm.list >/dev/null 2>&1 || { + echo "⚠️ 添加 WezTerm apt 源失败,跳过" + exit 0 + } + + sudo apt-get update -qq || true + sudo apt-get install -y wezterm 2>/dev/null || echo "⚠️ WezTerm 安装失败,跳过" + exit 0 +fi + +echo "不支持的系统: $OS" +exit 0 diff --git a/scripts/layer2-post.sh b/scripts/layer2-post.sh new file mode 100755 index 0000000..b50b0d9 --- /dev/null +++ b/scripts/layer2-post.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# Layer 2: 后置脚本 — 依赖 Layer 1 安装的工具 +set -eo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# GitHub mirrors (same as install-functions.sh) +GITHUB_MIRRORS="${GITHUB_MIRRORS:-https://ghfast.top https://mirror.ghproxy.com}" + +# ── 2a: Helix runtime(需要先安装 helix)── +install_helix_runtime() { + echo "==========================================" + echo "Layer 2: 安装 Helix runtime..." + echo "==========================================" + + if ! command -v hx &>/dev/null; then + echo "⚠️ helix 未安装,跳过 runtime 安装" + return 0 + fi + + local hx_version + hx_version=$(hx --version | head -1 | awk '{print $2}') + local hx_config_dir="${HOME}/.config/helix" + local hx_runtime_dir="${hx_config_dir}/runtime" + + mkdir -p "${hx_runtime_dir}" + + # 下载 themes 和 queries + local repo_url="https://github.com/helix-editor/helix" + local tmp_dir + tmp_dir=$(mktemp -d) + + echo "下载 Helix runtime (themes/queries/tutor)..." + local download_ok=false + + # Try gh API tarball first + local tarball_url="" + if command -v gh &>/dev/null; then + tarball_url=$(gh api "repos/helix-editor/helix/tarball/${hx_version}" \ + --jq '.tarball_url' 2>/dev/null) || true + fi + + if [[ -n "${tarball_url}" ]]; then + if curl -fsSL --retry 3 --retry-delay 5 --connect-timeout 30 --max-time 300 \ + "${tarball_url}" | tar xz -C "${tmp_dir}" --strip-components=1 2>/dev/null; then + download_ok=true + fi + fi + + # Fallback: try GitHub mirror then direct + if [ "${download_ok}" = false ]; then + local direct_url="${repo_url}/archive/refs/tags/${hx_version}.tar.gz" + for mirror in $GITHUB_MIRRORS; do + if curl -fsSL --retry 3 --retry-delay 5 --connect-timeout 30 --max-time 300 \ + "${mirror}/${direct_url}" | tar xz -C "${tmp_dir}" --strip-components=1 2>/dev/null; then + download_ok=true + break + fi + done + fi + + if [ "${download_ok}" = false ]; then + curl -fsSL --retry 3 --retry-delay 5 --connect-timeout 30 --max-time 300 \ + "${repo_url}/archive/refs/tags/${hx_version}.tar.gz" | \ + tar xz -C "${tmp_dir}" --strip-components=1 2>/dev/null || true + fi + + # 复制 runtime 文件 + cp -r "${tmp_dir}/runtime/themes" "${hx_runtime_dir}/" 2>/dev/null || true + cp -r "${tmp_dir}/runtime/queries" "${hx_runtime_dir}/" 2>/dev/null || true + cp -r "${tmp_dir}/runtime/tutor" "${hx_runtime_dir}/" 2>/dev/null || true + rm -rf "${tmp_dir}" + + echo "✅ Helix runtime 安装完成" +} + +# ── 2b: Yazi 插件 ── +install_yazi_plugins() { + echo "==========================================" + echo "Layer 2: 安装 Yazi 插件..." + echo "==========================================" + + if ! command -v ya &>/dev/null; then + echo "⚠️ yazi 未安装,跳过插件安装" + return 0 + fi + + # 保留现有插件安装逻辑 + if [ -f "${PROJECT_DIR}/shells/common/install-functions.sh" ]; then + source "${PROJECT_DIR}/shells/common/install-functions.sh" + install-yazi-plugins + else + echo "⚠️ install-functions.sh 不存在,跳过" + fi +} + +# ── 2c: LLVM / clangd ── +install_llvm() { + echo "==========================================" + echo "Layer 2: 安装 LLVM / clangd..." + echo "==========================================" + + if [[ "$(uname -s)" == "Darwin" ]]; then + echo "macOS: 跳过 llvmup(LLVM 由 Homebrew 提供)" + return 0 + fi + + local llvmup="${SCRIPT_DIR}/llvmup" + if [ ! -f "$llvmup" ]; then + echo "⚠️ llvmup 脚本不存在,跳过 LLVM 安装" + return 0 + fi + + # Wait for dpkg lock (unattended-upgrade may be running) + echo "等待 dpkg 锁释放..." + local max_wait=300 + local waited=0 + while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do + if [ $waited -ge $max_wait ]; then + echo "⚠️ dpkg 锁等待超时(${max_wait}s),跳过 LLVM 安装" + return 0 + fi + sleep 5 + waited=$((waited + 5)) + done + + chmod +x "$llvmup" + if "$llvmup" default 22; then + echo "✅ LLVM 22 / clangd 安装完成" + else + echo "⚠️ LLVM 22 安装失败,跳过" + return 0 + fi +} + +# ── 2d: 字体缓存刷新 ── +refresh_fonts() { + echo "==========================================" + echo "Layer 2: 刷新字体缓存..." + echo "==========================================" + + if command -v fc-cache &>/dev/null; then + fc-cache -f + echo "✅ 字体缓存已刷新" + else + echo "⚠️ fc-cache 不存在,跳过" + fi +} + +# ── 入口 ── +main() { + install_helix_runtime + install_yazi_plugins + install_llvm + refresh_fonts + echo "" + echo "✅ Layer 2 (后置脚本) 完成" +} + +main "$@" diff --git a/scripts/llvmup b/scripts/llvmup new file mode 100755 index 0000000..4c6019e --- /dev/null +++ b/scripts/llvmup @@ -0,0 +1,293 @@ +#!/usr/bin/env bash + +# llvmup - LLVM 版本管理工具 +# +# 用法:llvmup [arguments] +# +# 可用命令: +# list, ls, l 列出已安装的 LLVM 版本(* 表示当前使用) +# ls-remote 列出可安装的 LLVM 版本 +# use 切换到指定版本的 LLVM +# install 安装指定版本的 LLVM +# default 安装并切换到指定版本的 LLVM +# remove 删除指定版本的 LLVM +# help, h 显示此帮助信息 +# +# 示例: +# llvmup list # 查看已安装的版本 +# llvmup ls-remote # 查看可安装的版本 +# llvmup install 18 # 安装 LLVM 18 +# llvmup use 18 # 切换到 LLVM 18 +# llvmup default 18 # 安装并切换到 LLVM 18 +# llvmup remove 18 # 删除 LLVM 18 + +set -eo pipefail + +APP_NAME="llvmup" +LLVM_SCRIPT="https://apt.llvm.org/llvm.sh" +function sudo_run(){ + # 如果当前用户是root用户, 则直接运行命令 + if [ "$EUID" -eq 0 ]; then + "$@" + else + sudo "$@" + fi +} + +function prepare_for_install(){ + # 检查lsb_release命令是否存在 + if ! command -v lsb_release >/dev/null 2>&1; then + sudo_run apt-get update + sudo_run apt-get install -y lsb-release + fi +} + +function list-llvm-versions() { +python3 << 'EOF' +import os +import sys + +def main(): + # 可以从命令行参数获取路径 + lib_path = sys.argv[1] if len(sys.argv) > 1 else "/usr/lib" + + # list files in specified path + files = os.listdir(lib_path) + # filter files, only retain file matching llvm- + files = filter(lambda x: x.startswith("llvm-"), files) + # map files to version number + versions = list(map(lambda x: x[5:], files)) + + # check if exists /usr/lib/llvm, and if it is a symlink and point to where + used = None + llvm_link_path = os.path.join(lib_path, "llvm") + if os.path.exists(llvm_link_path): + if os.path.islink(llvm_link_path): + point_to = os.readlink(llvm_link_path) + if point_to.startswith(os.path.join(lib_path, "llvm-")): + used = point_to[len(os.path.join(lib_path, "llvm-")):] + + print("Installed LLVM versions:") + for version in versions: + if used is not None and used == version: + print(f" {version}* (Currently used)") + else: + print(f" {version}") + +if __name__ == "__main__": + main() +EOF +} + +function install-llvm-version() { + if [ -z "$1" ]; then + echo "用法: $APP_NAME install " + exit 1 + fi + LLVM_VERSION=$1 + LLVM_PATH="/usr/lib/llvm-${LLVM_VERSION}" + + # 检查是否已安装 + if [ -d "${LLVM_PATH}" ]; then + echo "LLVM ${LLVM_VERSION} 已经安装,跳过安装步骤" + echo "如果需要重新安装,请先运行 '$APP_NAME remove ${LLVM_VERSION}'" + echo "如果想要切换到该版本,请运行 '$APP_NAME use ${LLVM_VERSION}'" + return + fi + prepare_for_install + + # 优先使用 Ubuntu 官方源(速度快,使用镜像) + # Ubuntu 24.04 官方源支持 LLVM 14-20 + if apt-cache show llvm-${LLVM_VERSION} 2>/dev/null | grep -q "Package: llvm-${LLVM_VERSION}"; then + echo "使用 Ubuntu 官方源安装 LLVM ${LLVM_VERSION}..." + sudo_run apt-get update + sudo_run apt-get install -y \ + llvm-${LLVM_VERSION} \ + clang-${LLVM_VERSION} \ + clangd-${LLVM_VERSION} \ + lldb-${LLVM_VERSION} \ + lld-${LLVM_VERSION} \ + llvm-${LLVM_VERSION}-dev \ + clang-tools-${LLVM_VERSION} + + # 验证安装 + if [ -f "${LLVM_PATH}/bin/clang" ]; then + echo "已成功安装 LLVM ${LLVM_VERSION} (来自 Ubuntu 官方源)" + return 0 + else + echo "警告: Ubuntu 官方源安装可能不完整,尝试 apt.llvm.org..." + fi + fi + + # 回退到 apt.llvm.org(支持版本 17-22+) + echo "Ubuntu 官方源无 LLVM ${LLVM_VERSION},使用 apt.llvm.org..." + INSTALL_DIR="${HOME}/.local/install-llvm" + mkdir -p "${INSTALL_DIR}" + LLVM_SCRIPT_PATH="${INSTALL_DIR}/llvm.sh" + wget -O "${LLVM_SCRIPT_PATH}" $LLVM_SCRIPT --tries=5 --timeout=60 --connect-timeout=30 + chmod +x "${LLVM_SCRIPT_PATH}" + # 添加 clangd 包到安装列表 + sudo_run "${LLVM_SCRIPT_PATH}" "${LLVM_VERSION}" clangd + + # 等待安装完成,验证文件存在 + if [ -f "${LLVM_PATH}/bin/clang" ]; then + echo "已成功安装 LLVM ${LLVM_VERSION} (来自 apt.llvm.org)" + else + echo "错误: clang 未找到,安装可能失败" >&2 + exit 1 + fi +} + +function remove-llvm-version() { + if [ -z "$1" ]; then + echo "用法: $APP_NAME remove " + exit 1 + fi + LLVM_VERSION=$1 + LLVM_PATH="/usr/lib/llvm-${LLVM_VERSION}" + + if [ -d "${LLVM_PATH}" ]; then + sudo_run rm -rf "${LLVM_PATH}" + echo "已删除 LLVM ${LLVM_VERSION}" + else + echo "错误: LLVM ${LLVM_VERSION} 未安装" >&2 + exit 1 + fi +} + +function use-llvm-version() { + if [ -z "$1" ]; then + echo "用法: $APP_NAME use " + exit 1 + fi + VERSION=$1 + LLVM_PATH="/usr/lib/llvm-${VERSION}" + + # 如果目标版本不存在,先安装 + if [ ! -d "${LLVM_PATH}" ]; then + echo "LLVM ${VERSION} 未安装,正在自动安装..." + install-llvm-version "$VERSION" + fi + + sudo_run rm /usr/lib/llvm || true + sudo_run ln -s /usr/lib/llvm-$VERSION /usr/lib/llvm + echo "已切换到 LLVM ${VERSION}" +} + +function print-usage() { + cat << USAGE +llvmup - LLVM 版本管理工具 + +用法:$APP_NAME [arguments] + +可用命令: + list, ls, l 列出已安装的 LLVM 版本(* 表示当前使用) + ls-remote 列出可安装的 LLVM 版本 + use 切换到指定版本的 LLVM + install 安装指定版本的 LLVM + default 安装并切换到指定版本的 LLVM + remove 删除指定版本的 LLVM + help, h 显示此帮助信息 + +示例: + $APP_NAME list # 查看已安装的版本 + $APP_NAME ls-remote # 查看可安装的版本 + $APP_NAME install 18 # 安装 LLVM 18 + $APP_NAME use 18 # 切换到 LLVM 18 + $APP_NAME default 18 # 安装并切换到 LLVM 18 + $APP_NAME remove 18 # 删除 LLVM 18 +USAGE + exit 0 +} + + +# 函数:列出可安装的 LLVM 版本 +# 标记哪些版本可以从 Ubuntu 官方源安装(速度快) +function list-remote-versions() { + echo "可安装的 LLVM 版本:" + echo "" + echo " [U] = Ubuntu 官方源 (速度快,推荐)" + echo " [L] = apt.llvm.org (最新版本)" + echo "" + + # 检查 Ubuntu 官方源支持的版本 + local ubuntu_versions=() + for v in 14 15 16 17 18 19 20; do + if apt-cache show llvm-${v} 2>/dev/null | grep -q "Package: llvm-${v}"; then + ubuntu_versions+=("$v") + fi + done + + # 从 apt.llvm.org 获取支持的版本 + local llvm_org_versions=() + local CACHE_DIR="${HOME}/.local/llvmup" + local CACHE_FILE="${CACHE_DIR}/remote_versions" + local CACHE_EXPIRATION_SECONDS=7200 # 2小时 + + mkdir -p "${CACHE_DIR}" + + # 检查缓存文件是否存在且未过期 + if [ -f "${CACHE_FILE}" ]; then + local file_mod_time=$(stat -c %Y "${CACHE_FILE}") + local current_time=$(date +%s) + if (( current_time - file_mod_time < CACHE_EXPIRATION_SECONDS )); then + mapfile -t llvm_org_versions < "${CACHE_FILE}" + fi + fi + + # 如果缓存无效,从 apt.llvm.org 获取 + if [ ${#llvm_org_versions[@]} -eq 0 ]; then + local file_path="/tmp/llvm_temp.sh" + wget -O "$file_path" $LLVM_SCRIPT > /dev/null 2>&1 + + if [ -f "$file_path" ]; then + while IFS= read -r line; do + if [[ "$line" =~ ^[[:space:]]*\+?[[:space:]]*LLVM_VERSION_PATTERNS\[([0-9]+)\]=(.*)$ ]]; then + local version_num="${BASH_REMATCH[1]}" + if [ -n "$version_num" ] && [ "$version_num" -ge 9 ] && [ "$version_num" -le 22 ]; then + llvm_org_versions+=("$version_num") + fi + fi + done < "$file_path" + fi + + # 缓存结果 + printf "%s\n" "${llvm_org_versions[@]}" > "${CACHE_FILE}" 2>/dev/null || true + fi + + # 合并并显示所有版本 + local all_versions=$(echo -e "${ubuntu_versions[*]} ${llvm_org_versions[*]}" | tr ' ' '\n' | sort -n | uniq) + + for v in $all_versions; do + local marker="[L]" + if [[ " ${ubuntu_versions[*]} " =~ " $v " ]]; then + marker="[U]" + fi + echo " $marker LLVM $v" + done +} + +# 如果第一个命令行参数为空,则默认执行list命令 --- IGNORE --- +COMMAND=${1:-list} + +# 如果是ls/list/l子命令,则列出已安装的llvm版本 +if [ "$COMMAND" == "list" ] || [ "$COMMAND" == "ls" ] || [ "$COMMAND" == "l" ]; then + list-llvm-versions +elif [ "$COMMAND" == "ls-remote" ]; then + list-remote-versions +elif [ "$COMMAND" == "use" ]; then + use-llvm-version "$2" +# 如果是install/i子命令, 则安装指定版本的llvm +elif [ "$COMMAND" == "install" ] || [ "$COMMAND" == "i" ]; then + install-llvm-version "$2" +# 安装然后切换到指定版本 , default 子命令 +elif [ "$COMMAND" == "default" ] || [ "$COMMAND" == "d" ]; then + install-llvm-version "$2" + use-llvm-version "$2" +elif [ "$COMMAND" == "remove" ] || [ "$COMMAND" == "rm" ]; then + remove-llvm-version "$2" +elif [ "$COMMAND" == "help" ] || [ "$COMMAND" == "h" ]; then + print-usage +else + print-usage +fi \ No newline at end of file diff --git a/scripts/mise-install.sh b/scripts/mise-install.sh new file mode 100755 index 0000000..e8adb53 --- /dev/null +++ b/scripts/mise-install.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +if ! command -v mise &>/dev/null; then + echo "❌ mise 未安装,请先安装 mise" + exit 1 +fi + +export MISE_YES=1 +mise trust --silent "$REPO_DIR/mise/config.toml" 2>/dev/null || true +mise install diff --git a/scripts/test-cargo-install-check.sh b/scripts/test-cargo-install-check.sh new file mode 100755 index 0000000..f1cc1f7 --- /dev/null +++ b/scripts/test-cargo-install-check.sh @@ -0,0 +1,198 @@ +#!/bin/bash +# ============================================================================= +# 测试 cargo-install check() 修复 +# ============================================================================= +# 场景:模拟已安装 wild-linker/bat(通过 binstall)的环境,运行 tool-installer +# 预期:check() 正确识别为已安装,跳过 install() +# ============================================================================= + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +echo "==========================================" +echo "测试环境:模拟已安装环境 + 检查 tool-installer check()" +echo "==========================================" + +# 创建临时测试目录 +TEST_DIR=$(mktemp -d) +trap "rm -rf $TEST_DIR" EXIT + +echo "" +echo "步骤 1: 准备测试环境..." +mkdir -p "$TEST_DIR/.cargo/bin" +mkdir -p "$TEST_DIR/.local/bin" + +# 模拟已安装的二进制(通过 binstall 安装的) +# wild-linker → 二进制名为 wild +cat > "$TEST_DIR/.cargo/bin/wild" << 'EOF' +#!/bin/bash +echo "Wild 0.8.0 non-git-build (compatible with GNU linkers)" +EOF +chmod +x "$TEST_DIR/.cargo/bin/wild" + +# bat → 二进制名为 bat +cat > "$TEST_DIR/.cargo/bin/bat" << 'EOF' +#!/bin/bash +echo "bat 0.26.1" +EOF +chmod +x "$TEST_DIR/.cargo/bin/bat" + +# 模拟 cargo install --list(只记录 cargo install 安装的,不记录 binstall) +# 这是关键:cargo install --list 找不到 wild-linker 和 bat +mkdir -p "$TEST_DIR/.cargo" +cat > "$TEST_DIR/.cargo/.crates2.json" << 'EOF' +{"installs":{}} +EOF + +# 准备 tool-installer +# 测试两种版本:旧版(bug)和新版(fix) +echo "" +echo "步骤 2: 准备 tool-installer..." + +# 复制当前仓库的 vendor/tool-installer(应该是修复后的版本) +if [ -f "$PROJECT_DIR/vendor/tool-installer" ]; then + cp "$PROJECT_DIR/vendor/tool-installer" "$TEST_DIR/.local/bin/tool-installer" + chmod +x "$TEST_DIR/.local/bin/tool-installer" + echo "✅ 使用仓库 vendor/tool-installer" +else + echo "❌ vendor/tool-installer 不存在" + exit 1 +fi + +# 复制 manifest.toml +cp "$PROJECT_DIR/manifest.toml" "$TEST_DIR/manifest.toml" + +# 创建最小化 tools.toml +cat > "$TEST_DIR/tools.toml" << 'EOF' +[dev] +modules = ["cargo-tools"] + +[cargo-tools] +manifest = "manifest.toml" +EOF + +# 创建最小化的 cargo-tools 模块配置(只包含 wild-linker 和 bat) +mkdir -p "$TEST_DIR/modules" + +# 进入测试目录 +cd "$TEST_DIR" + +# 设置 PATH +export PATH="$TEST_DIR/.cargo/bin:$TEST_DIR/.local/bin:$PATH" +export HOME="$TEST_DIR" + +echo "" +echo "步骤 3: 验证二进制可用性..." +if command -v wild &>/dev/null; then + echo "✅ wild 在 PATH 中: $(which wild)" + wild --version +else + echo "❌ wild 不在 PATH 中" +fi + +if command -v bat &>/dev/null; then + echo "✅ bat 在 PATH 中: $(which bat)" + bat --version +else + echo "❌ bat 不在 PATH 中" +fi + +echo "" +echo "步骤 4: 检查 tool-installer 版本..." +python3 -c " +import zipfile +z = zipfile.ZipFile('$TEST_DIR/.local/bin/tool-installer') +data = z.read('tool_installer/managers/commands.py').decode() +has_check_fix = 'binary exists in' in data +has_registry_fix = '--registry crates-io' in data +print(f'Has check() fix: {has_check_fix}') +print(f'Has registry fix: {has_registry_fix}') +if has_check_fix and has_registry_fix: + print('✅ tool-installer 包含完整修复') +else: + print('⚠️ tool-installer 可能缺少部分修复') +" + +echo "" +echo "步骤 5: 运行 tool-installer check(dry-run)..." +# 创建一个简化的 Python 脚本来测试 check() 逻辑 +python3 << 'PYEOF' +import sys +import os +sys.path.insert(0, os.path.expanduser('~/.local/bin')) + +# 由于 zipapp 的特殊结构,我们直接测试关键逻辑 +import zipfile +import subprocess + +# 读取 tool-installer 的代码 +z = zipfile.ZipFile(os.path.expanduser('~/.local/bin/tool-installer')) +commands_py = z.read('tool_installer/managers/commands.py').decode() + +# 检查关键函数是否存在 +if '_parse_binary_version' not in commands_py: + print("❌ 旧版本:缺少 _parse_binary_version") + sys.exit(1) + +if '_cargo_bin_dirs' not in commands_py: + print("❌ 旧版本:缺少 _cargo_bin_dirs") + sys.exit(1) + +# 模拟 check() 的核心逻辑:查找二进制并获取版本 +print("=== 模拟 check() 逻辑 ===") + +bin_dirs = [ + os.path.expanduser('~/.cargo/bin'), + os.path.expanduser('~/.local/bin'), +] + +for tool, expected_bin in [('wild-linker', 'wild'), ('bat', 'bat')]: + bin_path = None + for d in bin_dirs: + candidate = os.path.join(d, expected_bin) + if os.path.isfile(candidate): + bin_path = candidate + break + + if bin_path is None: + print(f"❌ {tool}: 二进制 {expected_bin} 不存在") + continue + + print(f"✅ {tool}: 找到二进制 {bin_path}") + + # 获取版本 + try: + result = subprocess.run([bin_path, '--version'], capture_output=True, text=True, timeout=5) + version_output = result.stdout.strip() + print(f" --version 输出: {version_output[:80]}") + + # 简单解析版本号 + version = None + for token in version_output.split(): + if token[0].isdigit() and '.' in token: + version = token + break + if token.startswith(('v', 'V')) and token[1:].replace('.', '').isdigit(): + version = token[1:] + break + + if version: + print(f" 解析到版本: {version}") + else: + print(f" ⚠️ 无法解析版本") + except Exception as e: + print(f" ❌ --version 失败: {e}") + +print("\n=== 结论 ===") +print("如果 tool-installer 包含修复,check() 会找到二进制并解析版本") +print("如果版本匹配,返回 SATISFIED,跳过 install()") +print("如果版本不匹配或为 latest,会尝试 install(),但 cargo binstall 会报 'already exists'") + +PYEOF + +echo "" +echo "==========================================" +echo "测试完成" +echo "==========================================" diff --git a/scripts/vendor/README.md b/scripts/vendor/README.md index 61558a4..94d974b 100644 --- a/scripts/vendor/README.md +++ b/scripts/vendor/README.md @@ -1,20 +1,57 @@ -# Vendor 脚本更新 SOP +# Vendor 策略与更新 SOP ## 概述 -`scripts/vendor/` 目录存放从第三方项目 vendor 的脚本文件。 -这些脚本由我们审查后放入项目,确保软件供应链安全。 +`vendor/` 目录存放从第三方项目 vendor 的资源(二进制或脚本)。 +这些资源由我们审查后放入项目,确保供应链安全和构建稳定性。 -**核心原则**:脚本不自动更新,人审查后才更新。 +**核心原则**:vendor 只用于必要场景,人审查后才更新。 --- -## Vendor 清单 +## Vendor 准入条件 -| 脚本 | 来源 URL | Vendor 日期 | 说明 | -|------|----------|------------|------| -| `rustup-init.sh` | `https://rsproxy.cn/rustup-init.sh` | 2026-05-16 | Rustup 安装脚本 | -| `cargo-binstall-install.sh` | `shells/scripts/vendor/`(项目内迁移) | 见 git log | cargo-binstall 安装脚本 | +以下类型允许放入 `vendor/`: + +| 条件 | 说明 | 示例 | +|------|------|------| +| **自定义工具** | 无标准分发渠道,必须自行构建/打包 | `tool-installer`(Python zipapp)| +| **供应链安全关键脚本** | 需要人工审查,避免 `curl \| sh` | `rustup-init.sh` | +| **自举依赖** | 上层工具依赖它才能工作,且无法通过上层工具自身获取 | `tool-installer` 本身 | + +## 明确禁止 Vendor + +以下类型**禁止**放入 `vendor/`: + +- ❌ **主流生态工具的二进制**(cargo-binstall, xdotter 等) + - 这些工具有标准分发渠道(GitHub releases, crates.io) + - 应由 Layer 1 的对应 manager 自行获取 + - Vendor 二进制增加维护负担(跨平台、版本更新、架构兼容) + +- ❌ **可由 tool-installer 自行下载的工具** + - tool-installer 的 `github-release` manager 已支持镜像回退 + - `_download_binstall` 已实现 cargo-binstall 的自举下载 + - 预装这些工具会掩盖 tool-installer 自身路径的 bug + +- ❌ **临时 workaround** + - 网络问题的修复应在工具内部解决(timeout、retry、镜像 fallback) + - 不应通过 vendor 二进制绕过 + +--- + +## 当前 Vendor 清单 + +| 文件 | 类型 | 准入理由 | 状态 | +|------|------|----------|------| +| `tool-installer` | 自定义 zipapp | 无标准分发渠道,Layer 0 必须预装 | ✅ 保留 | +| `rustup-init.sh` | 审查脚本 | 供应链安全,避免 `curl \| sh` | ✅ 保留 | + +## 已移除 + +| 文件 | 移除原因 | +|------|----------| +| `cargo-binstall` | 违反 vendor 策略,应由 tool-installer 自行获取 | +| `xdotter` | 违反 vendor 策略,应由 github-release manager 获取 | --- diff --git a/scripts/verify-docker-build.sh b/scripts/verify-docker-build.sh index e638a3f..a7bb6e3 100755 --- a/scripts/verify-docker-build.sh +++ b/scripts/verify-docker-build.sh @@ -152,9 +152,9 @@ check_version nvim "Neovim" echo echo "=== 6. mise 运行时 ===" -# mise 需要先激活 +# mise 需要先加载环境(hook-env 会立即修改 PATH,activate 只设置 hook) if command -v mise &> /dev/null; then - eval "$(mise activate bash 2>/dev/null)" || true + eval "$(mise hook-env -s bash 2>/dev/null)" || true fi check_version go check_version node @@ -178,7 +178,7 @@ check_file "$HOME/.config/starship.toml" "starship 配置" echo echo "=== 9. 额外工具验证 ===" -# setup.sh 安装但之前未检查的工具 +# setup-new.sh 安装但之前未检查的工具 check_cmd cargo-fuzz "cargo-fuzz" check_cmd uv "uv" echo diff --git a/setup-new.sh b/setup-new.sh new file mode 100755 index 0000000..f89cf95 --- /dev/null +++ b/setup-new.sh @@ -0,0 +1,241 @@ +#!/usr/bin/env bash +# 三层架构安装脚本(tool-installer 迁移版) +# Layer 0: Bootstrap — 仅安装 tool-installer 二进制(vendor 目录) +# Layer 1: 声明式安装 — tool-installer install dev(系统包 / 字体 / WezTerm / 工具链) +# Layer 2: 后置脚本 — 依赖 Layer 1 工具的配置后处理 +set -eo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +export PATH="$HOME/.local/bin:$PATH" + +if [[ "$(uname -s)" != "Darwin" ]]; then + export DEBIAN_FRONTEND=noninteractive + export TZ=Asia/Shanghai +fi + +usage() { + echo "用法: $0 [选项]" + echo "" + echo " 无参数 完整安装(bootstrap + deploy + install + post)" + echo " --bootstrap 仅 Layer 0:系统包 + gh 登录 + tool-installer" + echo " --deploy 仅配置部署(xdotter)" + echo " --install 仅 Layer 1:tool-installer 安装工具" + echo " --post 仅 Layer 2:后置脚本" + echo " --dry-run 显示 tool-installer 安装计划" +} + +export MISE_HTTP_TIMEOUT="${MISE_HTTP_TIMEOUT:-300}" + +# 确保 tool-installer 是最新的(vendor 中的版本) +# 适用于任何入口:全新环境、旧环境、或跳过 bootstrap 的 --install +_ensure_tool_installer() { + local artifact="${SCRIPT_DIR}/vendor/tool-installer" + if [ ! -f "$artifact" ]; then + echo "❌ vendor/tool-installer 不存在于 ${SCRIPT_DIR}" + echo " 可能原因: 仓库未正确克隆或 vendor 文件被删除" + return 1 + fi + + mkdir -p "$HOME/.local/bin" || { + echo "❌ 无法创建 ~/.local/bin 目录" + return 1 + } + + local target="$HOME/.local/bin/tool-installer" + local need_update=false + + if [ ! -f "$target" ]; then + echo "🆕 tool-installer 未安装,准备安装..." + need_update=true + elif [ "$artifact" -ef "$target" ]; then + # source 和 target 是同一个 inode(hardlink),需要重建 + echo "⚠️ tool-installer 是 hardlink,需要重新创建..." + need_update=true + elif ! cmp -s "$artifact" "$target" 2>/dev/null; then + echo "🔄 tool-installer 版本不匹配,准备更新..." + need_update=true + fi + + if [ "$need_update" = true ]; then + # 多级 fallback 确保可靠写入: + # 1. install -m 755(首选,处理 hardlink/symlink) + # 2. rm + install(处理文件被占用的情况) + # 3. cat + chmod(最后手段) + if install -m 755 "$artifact" "$target" 2>/dev/null; then + : # 成功 + elif rm -f "$target" 2>/dev/null && install -m 755 "$artifact" "$target" 2>/dev/null; then + : # 成功 + elif cat "$artifact" > "$target" 2>/dev/null && chmod +x "$target"; then + : # 成功 + else + echo "❌ 无法更新 tool-installer(所有写入方式均失败)" + return 1 + fi + + # 验证:确保写入后的文件与 vendor 一致 + if ! cmp -s "$artifact" "$target" 2>/dev/null; then + echo "❌ tool-installer 写入后校验失败(文件不完整或损坏)" + return 1 + fi + + echo "✅ tool-installer 已更新到 ${target}" + fi + + # 验证可执行性 + if ! "$target" --help &>/dev/null; then + echo "❌ ${target} 无法执行" + return 1 + fi + + return 0 +} + +do_bootstrap() { + # ── 0a: 安装 tool-installer ── + echo "==========================================" + echo "Layer 0: 安装 tool-installer..." + echo "==========================================" + + _ensure_tool_installer || return 1 + ~/.local/bin/tool-installer --help | head -3 + + echo "" + echo "✅ Layer 0 (Bootstrap) 完成" + echo " 下一步: tool-installer install dev" +} + +# 确保 xdotter 已安装(bootstrap 后 tool-installer 可用,用其安装 extras 组) +# 多重回退策略:tool-installer → 直接下载 → vendor +ensure_xdotter() { + if command -v xd &>/dev/null; then + return 0 + fi + echo "安装 xdotter..." + + # 方案 1: tool-installer(最优,使用 GitHub API,可能被限流) + if command -v tool-installer &>/dev/null; then + if tool-installer install extras 2>/dev/null; then + command -v xd &>/dev/null && return 0 + fi + echo "⚠️ tool-installer 安装 xdotter 失败,尝试直接下载..." + fi + + # 方案 2: 直接 curl 下载 musl 静态二进制(仅 Linux x86_64) + if [[ "$(uname -s)" == "Linux" && "$(uname -m)" == "x86_64" ]]; then + mkdir -p ~/.local/bin + local xd_url="https://github.com/CNCSMonster/xdotter/releases/latest/download/xd-x86_64-unknown-linux-musl" + if curl -fsSL --retry 3 --connect-timeout 15 "$xd_url" -o ~/.local/bin/xd 2>/dev/null; then + chmod +x ~/.local/bin/xd + echo "✅ xdotter 已通过直接下载安装" + return 0 + fi + fi + + # 方案 3: vendor 目录(仅 Linux x86_64) + if [[ "$(uname -s)" == "Linux" && "$(uname -m)" == "x86_64" && -f "${SCRIPT_DIR}/vendor/xdotter" ]]; then + install -m 755 "${SCRIPT_DIR}/vendor/xdotter" ~/.local/bin/xd + echo "✅ xdotter 已从 vendor 目录安装" + return 0 + fi + + # macOS 无回退,跳过 xdotter(deploy 会检查 xd 是否存在) + if [[ "$(uname -s)" == "Darwin" ]]; then + echo "⚠️ macOS 上 xdotter 安装失败(无 vendor 兜底),将跳过 xd deploy" + return 0 + fi + + echo "❌ 所有安装方式均失败,无法继续" + return 1 +} + +do_deploy() { + echo "==========================================" + echo "部署配置文件(xdotter deploy)..." + echo "==========================================" + ensure_xdotter + export PATH="$HOME/.local/bin:$PATH" + if command -v xd &>/dev/null; then + cd "${SCRIPT_DIR}" && xd deploy --force + echo "✅ 配置部署完成" + else + echo "⚠️ xdotter 未安装,跳过配置部署(macOS 上可能需要手动安装)" + fi +} + +do_install() { + echo "==========================================" + echo "Layer 1: 安装开发工具..." + echo "==========================================" + export PATH="$HOME/.local/bin:$PATH" + + # 确保 tool-installer 是最新的(处理旧环境或跳过 bootstrap 的情况) + _ensure_tool_installer || exit 1 + + if ! command -v tool-installer &>/dev/null; then + echo "❌ tool-installer 未安装" + exit 1 + fi + + # 如果 xdotter 已在 deploy 阶段部署了 ~/.cargo/config.toml, + # 其中的 sccache wrapper / wild linker 此时尚未安装,会阻断 cargo 编译。 + # 临时禁用这些配置,等 sccache/wild 安装完成后自动恢复。 + local cargo_config="$HOME/.cargo/config.toml" + local patched=false + if [ -f "$cargo_config" ] && grep -qE 'rustc-wrapper|ld-path=wild' "$cargo_config" 2>/dev/null; then + echo "🔧 临时禁用 sccache wrapper / wild linker(工具尚未安装)..." + cp "$cargo_config" "$cargo_config.bak" + sed -e 's/^rustc-wrapper = "sccache"/#rustc-wrapper = "sccache" # temporarily disabled during install/' \ + -e 's/--ld-path=wild/--ld-path=ld/' \ + "$cargo_config.bak" > "$cargo_config" + patched=true + fi + + # tool-installer 失败时也要恢复原始配置 + if $patched; then + local rc=0 + tool-installer install dev || rc=$? + mv "$cargo_config.bak" "$cargo_config" + return $rc + else + tool-installer install dev + fi +} + +do_post() { + bash "${SCRIPT_DIR}/scripts/layer2-post.sh" +} + +main() { + case "${1:-}" in + --bootstrap) do_bootstrap ;; + --deploy) do_deploy ;; + --install) do_install ;; + --post) do_post ;; + --dry-run) + _ensure_tool_installer || exit 1 + tool-installer install dev --dry-run + ;; + --help|-h) usage; exit 0 ;; + "") + echo "==========================================" + echo "完整安装:三层架构" + echo "==========================================" + do_bootstrap + do_deploy + do_install + do_post + echo "" + echo "==========================================" + echo "✅ 全部安装完成" + echo "==========================================" + ;; + *) + echo "未知选项: $1" + usage + exit 1 + ;; + esac +} + +main "$@" diff --git a/shells/common/env.sh b/shells/common/env.sh index 68f0e56..913493a 100644 --- a/shells/common/env.sh +++ b/shells/common/env.sh @@ -120,11 +120,13 @@ export EDITOR='hx' # ============================================================================= # mise 版本管理工具 # ============================================================================= -# 提供 npm/node/go/zig 等命令的环境变量 -# 注意:这是基础环境配置,交互式和非交互式环境都需要 -# 与 inter.sh 的区别: -# - env.sh: 基础环境变量(PATH 等),所有场景都需要 -# - inter.sh: 交互式特性(prompt、快捷键),仅交互式 shell 需要 +# 职责:非交互式 shell 中立即注入 PATH/shims +# 与 inter.sh 的分工: +# - env.sh: 非交互式/所有场景的基础环境变量(PATH 等) +# - inter.sh: 交互式 shell 中加载 [env] 并安装 prompt hook +# 为什么拆分: +# - activate 依赖 prompt 触发,非交互式 shell 没有 prompt,不会立即生效 +# - hook-env 能立即将 mise shims 注入 PATH,适合 CI、脚本等场景 if command -v mise &>/dev/null; then - eval "$(mise activate $SH)" 2>/dev/null || true + eval "$(mise hook-env -s $SH)" 2>/dev/null || true fi diff --git a/shells/common/install-functions.sh b/shells/common/install-functions.sh index 481673f..2645651 100644 --- a/shells/common/install-functions.sh +++ b/shells/common/install-functions.sh @@ -17,7 +17,7 @@ VENDOR_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../scripts/vendor" # 镜像站说明: # - https://mirror.ghproxy.com (ghproxy) - 相对知名,较可信 # 注意:使用第三方镜像站存在供应链攻击风险,如可直接访问 GitHub,建议禁用镜像 -GITHUB_MIRRORS="${GITHUB_MIRRORS:-https://mirror.ghproxy.com}" +GITHUB_MIRRORS="${GITHUB_MIRRORS:-https://ghfast.top https://mirror.ghproxy.com}" # 从 GitHub 下载文件的通用函数(自动尝试多个镜像站 → 直连) github_download() { @@ -461,11 +461,56 @@ function install-common-rust-tools() { function install-yazi-plugins(){ # 安装 yazi 插件(如果 yazi 配置目录存在) - if [ -d "${HOME}/.config/yazi" ] && command -v ya &> /dev/null; then - cd "${HOME}/.config/yazi" && ya pkg install - return $? + if [ ! -d "${HOME}/.config/yazi" ] || ! command -v ya &> /dev/null; then + return 0 + fi + + local attempts=0 + local max_attempts=3 + local installed=false + + while [ $attempts -lt $max_attempts ]; do + attempts=$((attempts + 1)) + echo "安装 Yazi 插件(第 ${attempts}/${max_attempts} 次尝试)..." + + if cd "${HOME}/.config/yazi"; then + if command -v git &>/dev/null; then + # 逐个 mirror 尝试 fallback + for git_mirror in $GITHUB_MIRRORS; do + [ -n "$git_mirror" ] || continue + echo " 使用镜像: $git_mirror" + # 使用 GIT_CONFIG_COUNT 零副作用注入 git insteadOf 规则 + # 不创建文件、不碰 ~/.gitconfig、只影响当前进程树 + if GIT_CONFIG_COUNT=1 \ + GIT_CONFIG_KEY_0="url.${git_mirror}/https://github.com/.insteadof" \ + GIT_CONFIG_VALUE_0="https://github.com/" \ + ya pkg install; then + installed=true + break 2 # 跳出 for 和 while + fi + echo " ⚠️ 镜像 $git_mirror 失败,尝试下一个..." + done + else + if ya pkg install; then + installed=true + break + fi + fi + fi + + if [ "$installed" != true ]; then + echo "⚠️ Yazi 插件安装失败,等待后重试..." + sleep $((attempts * 5)) + fi + done + + if [ "$installed" = true ]; then + echo "✅ Yazi 插件安装成功" + return 0 + else + echo "⚠️ Yazi 插件安装失败(已重试 ${max_attempts} 次),跳过" + return 0 fi - return 0 } function setup-cargo-fuzz() { diff --git a/shells/common/inter.sh b/shells/common/inter.sh index 76a03c7..e84b804 100644 --- a/shells/common/inter.sh +++ b/shells/common/inter.sh @@ -74,5 +74,20 @@ y() { rm -f -- "$tmp" } +# ============================================================================= +# mise - 交互式 shell 激活 +# ============================================================================= +# 职责:交互式 shell 中加载 [env] 环境变量并安装 prompt hook +# 与 env.sh 的分工: +# - env.sh: 非交互式/所有场景的基础环境变量(PATH 等) +# - inter.sh: 交互式 shell 中加载 [env] 并安装 prompt hook +# 为什么拆分: +# - activate 依赖 prompt 触发,每次 prompt 自动更新 PATH 和 [env] +# - 不适合非交互式 shell(CI、脚本),因为非交互式没有 prompt +# - 因此放在 inter.sh 中,仅在交互式 shell 中执行 +if command -v mise &>/dev/null; then + eval "$(mise activate $SH)" 2>/dev/null || true +fi + # 标记已完成加载 INTER_DONE=1 diff --git a/tools.toml b/tools.toml new file mode 100644 index 0000000..db356c9 --- /dev/null +++ b/tools.toml @@ -0,0 +1,95 @@ +[tool-installer] +manifest = "./manifest.toml" + +# ── Layer 0: 基础系统包(必须最先安装)── +[system-packages] +"system-packages@1" = "基础系统包(apt/brew)" + +# ── Layer 0: 字体(体验增强,非必须)── +[fonts] +depends = ["system-packages"] +"fonts@1" = "系统字体 + FiraCode Nerd Font(apt/brew + GitHub)" + +# ── Layer 1a: 编译基础设施 ── +[build-base] +depends = ["system-packages"] +rustup-install = "安装 rustup(vendor 脚本)" +"rust@stable" = "Rust stable toolchain" + +# ── Layer 1b: Cargo 预编译安装器(独立,不需要 Rust)── +# 必须在 cargo-tools 之前安装,因为 cargo-tools 中的工具使用 binstall_first +[cargo-binstall] +"cargo-binstall@v1.19.1" = { desc = "Cargo 预编译安装器", allow_fail = true } + +# ── Layer 1c: Cargo 工具(依赖 Rust + cargo-binstall)── +[cargo-tools] +depends = ["build-base", "cargo-binstall"] +"sccache@0.15.0" = { desc = "编译缓存", allow_fail = true } +wild-linker = { desc = "快速链接器", allow_fail = true } +"bat@0.26.1" = "Cat 替代品" +"eza@0.23.4" = "ls 替代品" +"fd-find@10.4.2" = "find 替代品" +"zoxide@0.9.9" = "cd 替代品" +"tokei@14.0.0" = "代码统计" +"navi@2.24.0" = "交互式命令行" +"gitui@0.28.1" = "Git TUI" +"kondo@0.9.0" = "项目清理" +"jaq@3.0.0" = "jq 替代品" +"mdbook@0.5.2" = "文档生成器" +"mdbook-mermaid@0.17.0" = "mdbook 图表" +"gen-mdbook-summary@0.0.10" = "mdbook 目录" +"tree-sitter-cli@0.26.8" = { desc = "语法解析器", allow_fail = true } +"tree-sitter-grep@0.1.0" = { desc = "语法感知搜索", allow_fail = true } +"tree-sitter-show-ast@0.0.2" = { desc = "AST 可视化", allow_fail = true } +"macchina@6.4.0" = "系统信息" +"conceal@0.7.0" = "隐藏敏感信息" +"rust-script@0.36.0" = "Rust 脚本运行器" +"parallel-disk-usage@0.22.0" = "磁盘分析" +"nu@0.112.2" = "现代 Shell" +"uv@0.11.18" = "Python 包管理器" +"mise@2026.4.5" = "版本管理器" +"cargo-audit@0.22.1" = "依赖审计" +cargo-fuzz = { desc = "模糊测试", allow_fail = true } +grcov = { desc = "代码覆盖率", allow_fail = true } +cargo-tarpaulin = { desc = "代码覆盖率", allow_fail = true } + +# ── Layer 1f: 静态网站工具(独立,github-release 预编译)── +[zola] +"zola@v0.22.1" = "静态网站生成器" + +# ── Layer 1c: Shell 工具(独立,不需要 Rust)── +[shell-tools] +"starship@v1.25.1" = "Shell prompt" + +# ── Layer 1d: 编辑器和终端工具(独立,不需要 Rust)── +[editors] +"neovim@v0.12.2" = "Neovim 编辑器" +"helix@25.07.1" = "Helix 编辑器" +"zellij@v0.44.3" = "终端复用器" +"yq@v4.53.3" = "YAML/JSON 处理器" + +# ── Layer 1d: 语言运行时(由 mise 管理,通过 script job 安装)── +[languages] +depends = ["cargo-tools"] +"mise-install@1" = "Trust mise config and install all language runtimes" + +# ── Layer 1e: LSP 服务器 ── +# 注意:不依赖 languages,因为 CI 中 node/uv 由前置步骤安装 +[lsp-servers] +"typescript-lsp@5.3.0" = { desc = "TypeScript LSP", allow_fail = true } +"pyright@1.1.410" = { desc = "Python LSP", allow_fail = true } +"yaml-lsp@1.23.0" = { desc = "YAML LSP", allow_fail = true } +"bash-lsp@5.6.0" = { desc = "Bash LSP", allow_fail = true } +"marksman@2026-02-08" = "Markdown LSP" +"zls@0.15.1" = "Zig LSP" +"lua-lsp@3.17.1" = "Lua LSP" +"qwen-code@0.17.0" = { desc = "Qwen Code", allow_fail = true } + +# ── Layer 1f: 其他(独立)── +[extras] +"xdotter@v0.5.2" = "配置部署工具" +"wezterm@20240203" = { desc = "终端模拟器(体验增强)", allow_fail = true } + +# ── 全量安装 ── +[dev] +depends = ["system-packages", "fonts", "build-base", "cargo-binstall", "cargo-tools", "shell-tools", "editors", "lsp-servers", "languages", "extras", "zola"] diff --git a/vendor/tool-installer b/vendor/tool-installer new file mode 100755 index 0000000..3f56588 Binary files /dev/null and b/vendor/tool-installer differ diff --git a/vendor/xdotter b/vendor/xdotter new file mode 100755 index 0000000..2896f39 Binary files /dev/null and b/vendor/xdotter differ