已合并
[feature]:devcontainer机制打样仓合入 #61
孟广欣创建于 7月22日
[feature]:devcontainer机制打样仓合入 #61
已合并
孟广欣创建于 7月22日
11 个文件变更+1160-5
@@ -0,0 +1,124 @@
1+# Dev Container 快速入门指南
2+ 
3+> **真正开箱即用**:零手动配置!**首次 2 分钟** 全自动构建,**后续 10 秒** 极速开工。
4+ 
5+## 🛠️ 极简前置准备
6+ 
7+无需手动配置复杂工具链,请根据场景选择以下任一基础环境:
8+ 
9+| 方案 | VS Code 安装 | Docker 服务 | 适用场景 |
10+| :--- |:---------------------------------------------------------------------------------|:----------------------------------------------------------------------------| :--- |
11+| **远程服务器(推荐)** | [VS Code](https://code.visualstudio.com/) + `Dev Containers` + `Remote - SSH` 插件 | Linux 服务器已启用 Docker 服务 | 高性能计算、释放本地资源 |
12+| **本地 PC** | [VS Code](https://code.visualstudio.com/) + `Dev Containers` 插件 | [Docker Desktop](https://www.docker.com/products/docker-desktop/)(Linux 模式) | 单机离线开发 |
13+ 
14+> ⚠️ *注意:默认配置启用了 Host 网络模式及高权限,请务必在可信环境中使用。*
15+ 
16+## 🚀 3 步闪电开工
17+ 
18+1. **打开项目**:在 VS Code 中打开本项目代码目录。
19+2. **加载容器**:点击右下角弹出的 **`Reopen in Container`** 提示(或通过 `F1` 执行同名命令)。
20+3. **进入开发**:待容器环境自动初始化完成后,即可直接进行编码、编译、单元测试及调试。
21+ 
22+## 🔨 编译与单元测试
23+ 
24+环境就绪后,通过 VS Code 菜单栏 **`Terminal`** > **`Run Task`** 即可调用预设的自动化任务:
25+ 
26+| 任务名称 | 功能说明 |
27+| :--- | :--- |
28+| `Build: Release Mode` | 构建 Release 版本,产物输出至 `artifacts` 目录 |
29+| `Build: Debug Mode` | 构建 Debug 版本(仅 C++ 项目支持,Python 项目请忽略) |
30+| `Test: Run Unit Tests` | 执行全量单元测试 |
31+| `Clean: All Workspace` | 清理工作区内的所有构建缓存与临时文件 |
32+ 
33+> *也可直接在终端执行 `python3 build.py` 命令,其功能与上述任务一致。*
34+ 
35+## ⏱️ 自动化流程与耗时说明
36+ 
37+启动 Dev Container 后,系统将**全自动完成以下环境配置**
38+ 
39+| 阶段 | 自动化任务 | 首次耗时 | 后续启动 | 体验 |
40+|:--------------|:---------------------------------| :--- | :--- | :--- |
41+| **1. 环境拉取** | 拉取预置镜像并部署 VS Code Server | ~1 分钟 | 3 秒 | 全程无感 |
42+| **2. 身份与挂载** | 挂载代码目录(`/workspace`)并同步 Git 权限 | ~10 秒 | 3 秒 | 全程无感 |
43+| **3. 工具链加载** | 并行安装 Python 插件及 Clangd 等开发工具 | ~20 秒 | 3 秒 | 开箱即用 |
44+| **总计** | **零人工干预·全自动就绪** | **⏱️ ~2 分钟** | **⚡ ~10 秒** | **一次配置,持续高效** |
45+ 
46+> **镜像说明**:因 MindStudio 镜像制作流程复杂且耗时,本方案**内置预构建镜像**。若需了解镜像细节,可参考 [《MindStudio 统一构建镜像制作指南》](https://gitcode.com/Ascend/msot/blob/master/docs/zh/common/docker_image_build_guide.md)。
47+ 
48+## 💡 效率优化与故障恢复
49+ 
50+### 1. 配置 SSH 免密登录(10 秒完成)
51+ 
52+为避免频繁输入密码,可在 Windows PowerShell 中粘贴执行以下脚本,按提示操作即可自动完成配置:
53+ 
54+```powershell
55+# 1. 交互式输入用户名和IP地址
56+$ip = Read-Host "请输入远程服务器的IP地址"
57+$user = Read-Host "请输入远程服务器的用户名"
58+ 
59+# 2. 定义本地SSH相关路径
60+$sshDir = "$env:USERPROFILE\.ssh"
61+$pubKeyPath = "$sshDir\id_ed25519.pub"
62+ 
63+# 3. 检查本地是否存在公钥,若不存在则自动生成
64+if (-not (Test-Path $pubKeyPath)) {
65+ Write-Host "未检测到本地公钥,正在生成 ed25519 密钥对..." -ForegroundColor Yellow
66+ ssh-keygen -t ed25519 -C "mindstudio_devcontainer" -f "$sshDir\id_ed25519" -N '""'
67+ Write-Host "密钥对生成完毕。" -ForegroundColor Green
68+}
69+ 
70+# 4. 上传公钥至远程服务器
71+Write-Host "正在将公钥上传至 ${user}@${ip} ..." -ForegroundColor Cyan
72+Get-Content $pubKeyPath | ssh "${user}@${ip}" "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
73+Write-Host "公钥上传完成,免密登录配置成功!" -ForegroundColor Green
74+```
75+ 
76+### 2. 毁坏无忧:一键复原环境
77+ 
78+若开发过程中容器环境搞乱或损坏,无需重新搭建:只需按 `F1` 键选择 **Dev Containers: Rebuild Container**,即可瞬间获得一个全新的纯净环境!
79+ 
80+## ❓ FAQ
81+ 
82+### 1. VS Code 远程连接卡在“Waiting for port forwarding...”?
83+ 
84+**原因分析**
85+VS Code 远程开发依赖 SSH 端口转发。若服务端 `sshd_config` 限制过严,或远程 VS Code Server 组件异常,均会导致连接挂起。
86+ 
87+**解决方案**
88+ 
89+1. **检查服务端 SSH 配置**
90+ 
91+ - 编辑 `/etc/ssh/sshd_config`(需 root 权限),确保以下参数已启用:
92+ 
93+ ```bash
94+ AllowTcpForwarding yes
95+ GatewayPorts yes
96+ X11Forwarding yes
97+ ```
98+ 
99+ - **关键检查**:确认不存在 `PermitOpen none` 配置,若有请注释掉(`#PermitOpen none`),否则将禁用所有端口转发。
100+ - 重启 SSH 服务:`sudo systemctl restart sshd`
101+ 
102+2. **清理远程 VS Code Server**
103+ 若配置无误仍无法连接,可能是服务端组件损坏或版本不匹配。
104+ - 在远程服务器执行:`rm -rf ~/.vscode-server`
105+ - 重新发起连接,VS Code 将自动重新部署匹配的 Server 组件。
106+ 
107+### 2. 代码提交响应缓慢或无反馈?
108+ 
109+**原因分析**
110+项目默认启用 `pre-commit` 钩子。首次提交时需下载并初始化检查工具,耗时约 30~60 秒。后续提交将直接执行检查,响应通常为秒级。
111+ 
112+### 3. 修改 `.vscode/settings.json` 后 `git pull` 冲突且无法更新?
113+ 
114+**原因分析**
115+为支持个性化配置,该文件被标记为 `skip-worktree`,本地修改不会显示在 `git status` 中。当远端同步更新该文件时,Git 会拒绝覆盖本地内容以防止丢失。
116+ 
117+**解决方案**
118+请使用封装命令更新代码(**注意**:此操作将以远端版本覆盖本地,请提前备份):
119+ 
120+```bash
121+git safe-pull
122+```
123+ 
124+该命令会自动处理 `skip-worktree` 标记与本地暂存:拉取成功后应用远端版本并恢复标记;若拉取失败,本地修改将保留在 stash 中,确保数据安全。
@@ -0,0 +1,109 @@
1+// -------------------------------------------------------------------------
2+// This file is part of the MindStudio project.
3+// Copyright (c) 2025 Huawei Technologies Co.,Ltd.
4+//
5+// MindStudio is licensed under Mulan PSL v2.
6+// You can use this software according to the terms and conditions of the Mulan PSL v2.
7+// You may obtain a copy of Mulan PSL v2 at:
8+//
9+// http://license.coscl.org.cn/MulanPSL2
10+//
11+// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
12+// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
13+// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
14+// See the Mulan PSL v2 for more details.
15+// -------------------------------------------------------------------------
16+ 
17+{
18+ "name": "mskl",
19+ "image": "swr.cn-north-4.myhuaweicloud.com/mindstudio-image/mindstudio-build:26.1.0-0701",
20+ 
21+ // 镜像 WORKDIR 即 /workspace;z_cache.sh 通过设备号探测 /workspace 是否为
22+ // 独立挂载点,命中后自动把 ccache/uv 缓存放到 /workspace/.cache 下,
23+ // 因此 workspaceFolder 必须与镜像约定的 /workspace 保持一致,缓存才能生效。
24+ "workspaceFolder": "/workspace",
25+ "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
26+ 
27+ // 宿主机初始化:容器创建前拉取镜像、准备 Git 配置快照,并创建 bind mount
28+ // 所需的 uv 缓存目录。脚本必须在 Docker daemon 所在主机执行。
29+ "initializeCommand": "bash ${localWorkspaceFolder}/.devcontainer/initialize.sh",
30+ 
31+ // 镜像内非 root 业务用户,镜像默认 UID/GID 为 20001。Dev Containers 在
32+ // Linux 上按宿主用户更新其 UID/GID,避免向 workspace 写入 root 属主文件。
33+ "remoteUser": "mindstudio",
34+ "updateRemoteUserUID": true,
35+ 
36+ // 对齐 ctr_in.py 启动 mindstudio-build 时的 docker run 关键参数:
37+ // --network=host、--privileged、--ipc=host、/usr/local/sbin 只读挂载。
38+ // 这些参数会放宽容器隔离边界,保留它们是为了与 MindStudio 构建镜像行为一致。
39+ // ctr_in.py 的 --user root + HOST_UID/HOST_GID/HOST_HOME 用于触发镜像
40+ // entrypoint 做用户重映射;devcontainer 这里交给 updateRemoteUserUID 处理,
41+ // 因此不再强制 containerUser=root,避免和 VS Code 的用户管理打架。
42+ "runArgs": [
43+ "--network=host",
44+ "--privileged",
45+ "--ipc=host",
46+ "--ulimit", "nproc=65535:65535",
47+ "--security-opt", "seccomp=unconfined"
atomgit-bot
atomgit-botatomgit-bot7月22日

🟡 Medium Priority

devcontainer.json 第 43-47 行配置了 --network=host(绕过网络隔离)、--privileged(授予容器所有宿主机设备访问权及全部 capabilities)、--ipc=host(共享宿主机 IPC 命名空间)以及 --security-opt seccomp=unconfined(禁用 seccomp 系统调用过滤)。虽然注释说明这是为了对齐 ctr_in.py 的行为,但这些配置结合在一起实际上完全解除了 Docker 容器安全边界,使容器内任意进程等同于拥有宿主机 root 权限。若镜像或依赖被投毒,攻击面极大。README.md 第 14 行已提示"请在可信环境中使用",但仍需代码层面确认此为有意设计。

建议:若构建流程确实需要特权模式,建议:(1)在 README 中更突出地警告安全风险;(2)评估是否可以用更细粒度的 --cap-add 替代 --privileged(如仅加 SYS_PTRACE、NET_ADMIN 等);(3)评估 --security-opt seccomp=unconfined 是否可移除或替换为自定义 seccomp profile。

likedislike
48+ ],
49+ "mounts": [
50+ // 构建工具可能调用宿主机提供的系统管理脚本,仅以只读方式暴露。
51+ "source=/usr/local/sbin,target=/usr/local/sbin,type=bind,readonly",
52+ // initialize.sh 生成宿主 ~/.gitconfig 的快照;容器只读挂载该文件,
53+ // post-create.sh 仅从中读取 user.name 和 user.email,不挂载整个宿主 home。
54+ "source=${localWorkspaceFolder}/.devcontainer/.host-gitconfig,target=/tmp/host-gitconfig,type=bind,readonly",
55+ 
56+ // uv 缓存持久化:将宿主机 ~/.cache/uv bind mount 到容器内。
57+ // 配合下方 UV_CACHE_DIR 环境变量,uv 安装/构建缓存不受容器重建影响。
58+ "source=${localEnv:HOME}/.cache/uv,target=/home/mindstudio/.cache/uv,type=bind"
59+ ],
60+ "containerEnv": {
61+ // ctr_in.py 进入 mindstudio-build 容器时注入,保证 profile.d 启用 gcc11
62+ // 时不把构建镜像专属 rpath 写入手工编译产物。
63+ "GCC11_NO_RPATH": "1",
64+ // 用户执行 npm install -g 时,镜像默认 prefix 位于
65+ // /usr/local/nodejs,非 root 用户没有写权限,因此把全局 npm 包放到用户目录。
66+ "NPM_CONFIG_PREFIX": "/home/mindstudio/.local",
67+ // 配合上方 mounts 中宿主机 uv 缓存目录挂载,固化 uv 包安装缓存路径。
68+ "UV_CACHE_DIR": "/home/mindstudio/.cache/uv"
69+ },
70+ "remoteEnv": {
71+ // VS Code Server 及其扩展进程不一定读取 shell 启动文件,显式补充用户命令目录。
72+ "PATH": "/home/mindstudio/.local/bin:${containerEnv:PATH}"
73+ },
74+ 
75+ // profile.d 中的 z_gcc11.sh / z_cann.sh / z_python311.sh 只在交互式 /
76+ // login shell 下自动 source(openEuler /etc/bashrc 触发),因此:
77+ // 1) postCreateCommand 用 `bash -lc` 显式走 login shell,才能拿到 CANN /
78+ // gcc11 / python3.11 环境;
79+ // 2) VS Code 集成终端默认也切到 login shell,交互式操作体验与镜像一致。
80+ "postCreateCommand": "bash -lc '/workspace/.devcontainer/post-create.sh'",
81+ 
82+ "customizations": {
83+ "vscode": {
84+ // 扩展安装由 Dev Containers 管理,容器重建后自动恢复。
85+ "extensions": [
86+ "ms-python.python",
87+ "ms-python.vscode-pylance",
88+ "charliermarsh.ruff",
89+ "shd101wyy.markdown-preview-enhanced"
90+ ],
91+ "settings": {
92+ // login shell 会加载镜像 /etc/profile.d 下的 CANN、GCC 和 Python 环境。
93+ "terminal.integrated.defaultProfile.linux": "bash-login",
94+ "terminal.integrated.profiles.linux": {
95+ "bash-login": {
96+ "path": "/bin/bash",
97+ "args": ["-l"]
98+ }
99+ },
100+ // post-create 默认激活 Python 3.11,此处跟随当前 PATH 中的 python3。
101+ "python.defaultInterpreterPath": "python3",
102+ // 将仓库根目录加入 Pylance 的额外模块搜索路径。
103+ "python.analysis.extraPaths": [
104+ "${workspaceFolder}"
105+ ]
106+ }
107+ }
108+ }
109+}
@@ -0,0 +1,77 @@
1+#!/usr/bin/env bash
2+# -------------------------------------------------------------------------
3+# This file is part of the MindStudio project.
4+# Copyright (c) 2025 Huawei Technologies Co.,Ltd.
5+#
6+# MindStudio is licensed under Mulan PSL v2.
7+# You can use this software according to the terms and conditions of the Mulan PSL v2.
8+# You may obtain a copy of Mulan PSL v2 at:
9+#
10+# http://license.coscl.org.cn/MulanPSL2
11+#
12+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
13+# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
14+# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
15+# See the Mulan PSL v2 for more details.
16+# -------------------------------------------------------------------------
17+ 
18+# git safe-pull 的实际执行脚本,由 post-create.sh 安装到
19+# $HOME/.local/bin/git-safe-pull,并注册为全局 Git alias。
20+#
21+# 处理流程:
22+# 1. 收集当前仓库全部 skip-worktree 文件。
23+# 2. 临时取消标记,使 Git 能识别并 stash 这些文件的本地修改。
24+# 3. 执行 git pull,并原样透传 --rebase 等调用参数。
25+# 4. pull 成功时丢弃临时 stash,以远端版本为准;失败时保留 stash。
26+# 5. 通过 EXIT trap 恢复 skip-worktree 标记。
27+#
28+# 限制:本命令只处理 skip-worktree 文件,不会自动暂存其它未提交修改;其它文件
29+# 仍遵循 git pull 的标准冲突与保护行为。
30+ 
31+set -euo pipefail
32+ 
33+# git ls-files -v 以行首 S 表示 skip-worktree。使用数组保存文件名,避免 xargs
34+# 或多层 shell 引号破坏带空格的路径。
35+files=()
36+while IFS= read -r line; do
37+ files+=("${line:2}")
38+done < <(git ls-files -v | sed -n '/^S /p')
39+ 
40+if [ "${#files[@]}" -eq 0 ]; then
41+ # 没有特殊文件时不增加额外流程,直接用当前进程执行标准 pull。
42+ exec git pull "$@"
43+fi
44+ 
45+# 无论 pull、stash drop 或其它步骤在哪一点退出,都尽力恢复索引标记。
46+restore_skip_worktree() {
47+ git update-index --skip-worktree -- "${files[@]}" 2>/dev/null || true
48+}
49+trap restore_skip_worktree EXIT
50+ 
51+# 取消标记后,git stash 才能识别这些文件的本地修改。
52+git update-index --no-skip-worktree -- "${files[@]}"
53+ 
54+# 对比操作前后的 refs/stash,区分“没有本地修改”与“确实创建了新 stash”。
55+stash_before=$(git rev-parse -q --verify refs/stash 2>/dev/null || true)
56+git stash push -q -m "devcontainer-safe-pull" -- "${files[@]}" || true
57+stash_after=$(git rev-parse -q --verify refs/stash 2>/dev/null || true)
58+stash_created=false
59+if [ -n "$stash_after" ] && [ "$stash_after" != "$stash_before" ]; then
60+ stash_created=true
61+fi
62+ 
63+if ! git pull "$@"; then
64+ # pull 失败时不自动 pop,避免在冲突或未完成的 merge/rebase 上叠加修改。
65+ # 用户可在处理 Git 状态后按提示的提交 ID 手工恢复。
66+ if [ "$stash_created" = true ]; then
67+ echo "[safe-pull] pull 失败,本地个性化修改已保存在 stash:$stash_after" >&2
68+ fi
69+ exit 1
70+fi
71+ 
72+# 新 stash 固定位于栈顶;pull 成功后使用远端版本并删除该临时条目。
73+if [ "$stash_created" = true ]; then
74+ git stash drop -q 'stash@{0}'
75+fi
76+ 
77+printf '[safe-pull] skip-worktree 文件已恢复:%s\n' "${files[*]}"
@@ -0,0 +1,60 @@
1+#!/bin/bash
2+# -------------------------------------------------------------------------
3+# This file is part of the MindStudio project.
4+# Copyright (c) 2025 Huawei Technologies Co.,Ltd.
5+#
6+# MindStudio is licensed under Mulan PSL v2.
7+# You can use this software according to the terms and conditions of the Mulan PSL v2.
8+# You may obtain a copy of Mulan PSL v2 at:
9+#
10+# http://license.coscl.org.cn/MulanPSL2
11+#
12+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
13+# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
14+# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
15+# See the Mulan PSL v2 for more details.
16+# -------------------------------------------------------------------------
17+ 
18+# ---------------------------------------------------------------------------
19+# initialize.sh - devcontainer initializeCommand
20+#
21+# 在宿主侧、容器创建之前执行,完成三件事:
22+# 1. 拉取最新镜像(镜像名从 devcontainer.json 的 image 字段读取,无需重复维护)
23+# 2. 准备宿主 ~/.gitconfig 快照,供容器读取 Git 用户名和邮箱
24+# 3. 创建宿主 uv 缓存目录,避免 bind mount 的 source 路径不存在
25+#
26+# 本脚本由 Dev Containers 在宿主机执行,不应依赖容器内路径或工具。
27+# ---------------------------------------------------------------------------
28+ 
29+set -euo pipefail
30+ 
31+# 脚本所在目录即 .devcontainer/
32+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33+ 
34+# ---- 拉取最新镜像 ----
35+# 使用 python3 过滤 // 和 /* */ 注释后解析 JSON,比 sed+jq 更鲁棒地
36+# 处理单行注释和多行注释块。
37+IMAGE="$(python3 -c "
38+import re, json
39+with open('$SCRIPT_DIR/devcontainer.json') as f:
40+ text = re.sub(r'//.*$|/\*[\s\S]*?\*/', '', f.read(), flags=re.MULTILINE)
41+ print(json.loads(text)['image'])
42+")"
43+echo "==> Pulling image: $IMAGE"
44+docker pull "$IMAGE"
45+ 
46+# ---- 准备 Git 配置快照 ----
47+# 快照文件位于仓库的 .devcontainer/ 下并被 .gitignore 排除。当前实现复制完整
48+# 配置文件;容器内 post-create.sh 只读取 user.name 和 user.email,不会把其它
49+# 配置写入容器全局 Git 配置。
50+if [ -f "$HOME/.gitconfig" ]; then
51+ cp "$HOME/.gitconfig" "$SCRIPT_DIR/.host-gitconfig"
52+else
53+ : > "$SCRIPT_DIR/.host-gitconfig"
54+fi
55+ 
56+# ---- 准备 uv 缓存挂载源 ----
57+# devcontainer.json 将该目录 bind mount 到 /home/mindstudio/.cache/uv。
58+# bind mount 的 source 必须在 docker create 前存在;initializeCommand 以宿主
59+# 当前用户执行,因此新建目录天然归当前用户所有。
60+install -d -m 0755 "$HOME/.cache/uv"
@@ -0,0 +1,614 @@
1+#!/usr/bin/env bash
2+# -------------------------------------------------------------------------
3+# This file is part of the MindStudio project.
4+# Copyright (c) 2025 Huawei Technologies Co.,Ltd.
5+#
6+# MindStudio is licensed under Mulan PSL v2.
7+# You can use this software according to the terms and conditions of the Mulan PSL v2.
8+# You may obtain a copy of Mulan PSL v2 at:
9+#
10+# http://license.coscl.org.cn/MulanPSL2
11+#
12+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
13+# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
14+# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
15+# See the Mulan PSL v2 for more details.
16+# -------------------------------------------------------------------------
17+ 
18+# =============================================================================
19+# MindStudio devcontainer 初始化脚本
20+# =============================================================================
21+#
22+# 职责说明:
23+# 该脚本在 devcontainer 首次创建或重建后自动执行,负责完成用户级开发环境
24+# 初始化,确保开发者进入容器后即可直接进入编码、编译和调试状态。
25+#
26+# 与 devcontainer.json 的关系:
27+# devcontainer.json 通过 postCreateCommand 调用本脚本:
28+# "postCreateCommand": "bash -lc '/workspace/.devcontainer/post-create.sh'"
29+# 使用 login shell(-l)执行,目的是在初始化过程中自动加载镜像
30+# /etc/profile.d/ 中的 CANN、GCC 11、Python 等环境脚本,确保后续配置
31+# 能够感知到这些工具链的存在。
32+#
33+# 执行顺序(按依赖关系排列):
34+# 1. fix_cache_ownership - 修复 z_cache.sh 创建的缓存目录权限
35+# 2. fix_file_watcher_limit - 提升 inotify max_user_watches 至 524288
36+# 3. configure_user_bin - 建立用户级命令目录,重映射 npm prefix
37+# 4. configure_python311 - 在 shell 启动文件中启用 Python 3.11
38+# 5. sync_git_identity - 从宿主同步 Git 用户名和邮箱
39+# 6. append_dev_hint_once - 向 .bash_profile 追加常用开发命令提示
40+# 7. install_pre_commit_hook - 自动安装 pre-commit Git Hook
41+# 8. install_gitleaks - 从 OBS 下载 gitleaks 二进制(pre-commit 依赖)
42+# 9. ignore_vscode_settings - 隔离个人化 VS Code settings 修改
43+# 10. install_git_safe_pull_alias - 安装可处理 skip-worktree 文件的拉取命令
44+#
45+# =============================================================================
46+ 
47+# 不使用 set -e:各模块自行降级并记录告警,单项失败不应阻止进入容器。
48+# -u 和 pipefail 仍用于暴露未定义变量及管道中的隐蔽错误。
49+set -uo pipefail
50+ 
51+# ---------------------------------------------------------------------------
52+# 工具函数
53+# ---------------------------------------------------------------------------
54+ 
55+# 输出统一格式的信息日志,便于从 Dev Containers 启动日志中筛选。
56+log() {
57+ printf '[post-create] %s\n' "$*"
58+}
59+ 
60+# 输出统一格式的告警日志到 stderr;告警默认不终止后续初始化。
61+warn() {
62+ printf '[post-create] warning: %s\n' "$*" >&2
63+}
64+ 
65+# append_path_once:
66+# 幂等地将 $HOME/.local/bin 放到指定 shell 启动文件的 PATH 前端。
67+# 解决非 root 用户(mindstudio)无法写入 /usr/local/nodejs 等系统级目录的问题。
68+# 参数 $1: 目标 shell 启动文件路径(如 $HOME/.bashrc 或 $HOME/.bash_profile)
69+append_path_once() {
70+ local file="$1"
71+ local line='export PATH="$HOME/.local/bin:$PATH"'
72+ 
73+ touch "$file"
74+ if ! grep -Fqx "$line" "$file"; then
75+ printf '\n%s\n' "$line" >> "$file"
76+ fi
77+ 
78+ log "append_path_once succeeded: $file"
79+}
80+ 
81+# append_python311_once:
82+# 幂等地将 Python 3.11 切换脚本注入到指定 shell 启动文件中。
83+# 通过 marker 注释判断是否已注入,避免重复写入。
84+# 注入内容:
85+# 1. 加载镜像的 /etc/profile.d/z_python_switch.sh(Python 版本切换基础设施)
86+# 2. 调用 use-python 3.11 激活 Python 3.11 环境
87+# 参数 $1: 目标 shell 启动文件路径
88+append_python311_once() {
89+ local file="$1"
90+ local marker_begin="# >>> mindstudio devcontainer python >>>"
91+ 
92+ touch "$file"
93+ if grep -Fqx "$marker_begin" "$file"; then
94+ log "append_python311_once succeeded: $file"
95+ return 0
96+ fi
97+ 
98+ cat >> "$file" <<'EOF'
99+ 
100+# >>> mindstudio devcontainer python >>>
101+if [ -r /etc/profile.d/z_python_switch.sh ]; then
102+ . /etc/profile.d/z_python_switch.sh
103+fi
104+if [ -r /usr/local/bin/use-python ]; then
105+ . /usr/local/bin/use-python 3.11 >/dev/null 2>&1 || true
106+ export PY311_ENV_ENABLED=1
107+fi
108+# <<< mindstudio devcontainer python <<<
109+EOF
110+ 
111+ log "append_python311_once succeeded: $file"
112+}
113+ 
114+# =============================================================================
115+# configure_user_bin —— 用户级命令与 npm 全局路径重映射
116+# =============================================================================
117+#
118+# 背景与问题:
119+# 构建镜像中的 npm 全局安装目录默认指向 /usr/local/nodejs,非 root 用户
120+# (mindstudio)无写入权限。如果直接在镜像内执行 npm install -g,会因权限
121+# 不足而失败。
122+#
123+# 解决方案:
124+# 1. 创建 $HOME/.local/bin 目录,作为用户级可执行文件存放路径。
125+# 2. 通过 npm config set prefix 将 npm 全局安装目录重映射到 $HOME/.local,
126+# 这样 npm install -g 会将包安装到用户可写的目录下。
127+# 3. 将 $HOME/.local/bin 写入 .bashrc 和 .bash_profile 的 PATH 前端,
128+# 确保用户级命令优先级高于系统级命令。
129+#
130+# 容器环境变量配合:
131+# devcontainer.json 中设置了 containerEnv:
132+# NPM_CONFIG_PREFIX=/home/mindstudio/.local
133+# 这个变量在 VS Code 远端会话中生效;本函数确保在交互式终端 Login Shell 中
134+# 同样生效,覆盖所有使用场景。
135+configure_user_bin() {
136+ mkdir -p "$HOME/.local/bin"
137+ 
138+ if command -v npm >/dev/null 2>&1; then
139+ local current_prefix
140+ current_prefix=$(npm config get prefix 2>/dev/null || true)
141+ if [ "$current_prefix" != "$HOME/.local" ]; then
142+ npm config set prefix "$HOME/.local" || warn "failed to set npm prefix"
143+ fi
144+ else
145+ warn "npm is not available; skipping npm prefix setup"
146+ fi
147+ 
148+ append_path_once "$HOME/.bashrc"
149+ append_path_once "$HOME/.bash_profile"
150+ 
151+ log "configure_user_bin succeeded"
152+}
153+ 
154+# =============================================================================
155+# ensure_shared_bin_path —— 跨用户命令路径补齐
156+# =============================================================================
157+#
158+# 背景与问题:
159+# devcontainer.json 的 containerEnv 将 NPM_CONFIG_PREFIX 硬编码为
160+# /home/mindstudio/.local,这意味着 npm install -g 始终安装到 mindstudio
161+# 用户目录。当宿主机是 root 时,会话用户可能解析为 root($HOME=/root),
162+# 其 $HOME/.local/bin 指向 /root/.local/bin,与 npm 实际安装目标不重合,
163+# 导致 claude 等 npm 全局命令不可见。
164+#
165+# 解决方案:
166+# 幂等地将 /home/mindstudio/.local/bin 追加到 root 用户的 .bashrc 和
167+# .bash_profile 中,确保无论以哪个用户登录,都能找到 npm -g 安装的命令。
168+# 同时对已经安装的命令(如 claude)创建 /usr/local/bin 软链接作为兜底。
169+#
170+# 幂等性保证:
171+# 通过 grep -Fqx 检测目标行是否已存在,避免重复写入。
172+ensure_shared_bin_path() {
173+ local shared_bin="/home/mindstudio/.local/bin"
174+ local line="export PATH=\"$shared_bin:\$PATH\""
175+ 
176+ # 为 root 用户补齐 mindstudio bin 路径(幂等)
177+ # 兼容 postCreateCommand 以 root 或非 root 身份运行的场景
178+ if [ -d "/root" ]; then
179+ for rc in "/root/.bashrc" "/root/.bash_profile"; do
180+ if [ "$(id -u)" = "0" ]; then
181+ # 当前是 root,直接操作
182+ touch "$rc" 2>/dev/null || continue
183+ if ! grep -Fqx "$line" "$rc"; then
184+ printf '\n%s\n' "$line" >> "$rc"
185+ log "added shared bin path to $rc"
186+ fi
187+ else
188+ # 当前是非 root,通过 sudo 操作
189+ sudo touch "$rc" 2>/dev/null || continue
190+ if ! sudo grep -Fqx "$line" "$rc"; then
191+ printf '\n%s\n' "$line" | sudo tee -a "$rc" >/dev/null
192+ log "added shared bin path to $rc"
193+ fi
194+ fi
195+ done
196+ fi
197+ 
198+ # 兜底:将已安装的常用命令软链到系统路径,覆盖 sudo / 纯 root 等不读取
199+ # mindstudio rc 文件的场景
200+ if [ -d "$shared_bin" ]; then
201+ for cmd in claude; do
202+ if [ -x "$shared_bin/$cmd" ] && [ ! -e "/usr/local/bin/$cmd" ]; then
203+ if [ "$(id -u)" = "0" ]; then
204+ ln -sf "$shared_bin/$cmd" "/usr/local/bin/$cmd" 2>/dev/null || true
205+ else
206+ sudo ln -sf "$shared_bin/$cmd" "/usr/local/bin/$cmd" 2>/dev/null || true
207+ fi
208+ log "linked $cmd to /usr/local/bin/$cmd"
209+ fi
210+ done
211+ fi
212+ 
213+ log "ensure_shared_bin_path succeeded"
214+}
215+ 
216+# =============================================================================
217+# configure_python311 —— Python 3.11 环境激活
218+# =============================================================================
219+#
220+# 背景与问题:
221+# MindStudio 构建镜像预装了多个 Python 版本,3.11 是当前开发环境的默认版本。
222+# 镜像内 /etc/profile.d/z_python_switch.sh 和 /usr/local/bin/use-python
223+# 提供了版本切换能力,但这些脚本只在 Login Shell 中自动生效。如果 shell
224+# 启动文件中缺少这些调用,非 Login Shell 或子进程中可能使用错误的 Python 版本。
225+#
226+# 解决方案:
227+# 在 .bashrc 和 .bash_profile 中注入 Python 3.11 切换逻辑,覆盖 Login Shell
228+# 和非 Login Shell 两种场景:
229+# - .bashrc:覆盖 VS Code 集成终端(非 Login Shell)
230+# - .bash_profile:覆盖 SSH / 外部终端(Login Shell)
231+#
232+# 幂等性保证:
233+# 通过 marker 注释 (# >>> mindstudio devcontainer python >>>) 检测是否已
234+# 注入,避免重复写入导致环境变量被多次定义。
235+configure_python311() {
236+ append_python311_once "$HOME/.bashrc"
237+ append_python311_once "$HOME/.bash_profile"
238+ 
239+ log "configure_python311 succeeded"
240+}
241+ 
242+# =============================================================================
243+# sync_git_identity —— 宿主 Git 身份同步
244+# =============================================================================
245+#
246+# 背景与问题:
247+# 容器内的 Git 配置是全新的,如果不做身份同步,开发者在容器内的 commit 会
248+# 缺少正确的 author 信息,导致提交记录与开发者身份脱钩。但出于安全考虑,
249+# 不应该将宿主整个 $HOME 目录暴露到容器中(避免密钥、token 等敏感文件泄漏)。
250+#
251+# 解决方案:
252+# 1. initialize.sh 在宿主机复制 ~/.gitconfig 到被 Git 忽略的快照文件。
253+# 2. devcontainer.json 通过 mounts 将该快照文件只读挂载到容器内的
254+# /tmp/host-gitconfig。
255+# 3. 本函数从 /tmp/host-gitconfig 读取 user.name 和 user.email,
256+# 写入容器全局 Git 配置 (git config --global)。
257+#
258+# 降级策略:
259+# - 宿主没有 ~/.gitconfig 时,initializeCommand 生成空的 .host-gitconfig。
260+# 本函数检测到空文件或缺少字段时只告警,不阻塞容器创建。
261+# - 快照可能包含其它 Git 配置,但本函数只读取 user.name 和 user.email,
262+# 不会把 credential、alias、include 等设置写入容器全局配置。
263+sync_git_identity() {
264+ local host_gitconfig="/tmp/host-gitconfig"
265+ local git_name=""
266+ local git_email=""
267+ 
268+ if [ ! -s "$host_gitconfig" ]; then
269+ warn "host gitconfig is empty or missing; skipping git identity sync"
270+ log "sync_git_identity succeeded"
271+ return 0
272+ fi
273+ 
274+ git_name="$(git config -f "$host_gitconfig" --get user.name 2>/dev/null || true)"
275+ git_email="$(git config -f "$host_gitconfig" --get user.email 2>/dev/null || true)"
276+ 
277+ if [ -n "$git_name" ]; then
278+ git config --global user.name "$git_name" || warn "failed to sync git user.name"
279+ else
280+ warn "host gitconfig has no user.name"
281+ fi
282+ 
283+ if [ -n "$git_email" ]; then
284+ git config --global user.email "$git_email" || warn "failed to sync git user.email"
285+ else
286+ warn "host gitconfig has no user.email"
287+ fi
288+ 
289+ log "sync_git_identity succeeded"
290+}
291+ 
292+# =============================================================================
293+# install_pre_commit_hook —— pre-commit 自动安装
294+# =============================================================================
295+#
296+# 背景与问题:
297+# 仓库已有 .pre-commit-config.yaml 配置,但需要开发者手工执行
298+# `pre-commit install` 才能生效。在传统开发模式中,这一步容易被遗漏或忘记,
299+# 导致提交时未触发质量检查,低质量代码进入仓库。
300+#
301+# 解决方案:
302+# 容器初始化时自动执行 pre-commit install,将 pre-commit Hook 安装到
303+# .git/hooks/pre-commit,确保每次 git commit 时自动触发。
304+#
305+# 降级策略:
306+# 1. pre-commit 命令不存在时只告警,不阻塞容器创建。
307+# 原因:纯 Python 工具有可能不需要 pre-commit,不应因工具缺失阻止进入容器。
308+# 2. 当前目录不是 Git 仓库时只告警,不阻塞容器创建。
309+# 原因:非 Git 场景(如镜像内临时工作区)不应因 .git 缺失而失败。
310+# 3. 安装失败时只告警,不影响容器正常使用。
311+install_pre_commit_hook() {
312+ # pre-commit CLI 工具和 pre_commit Python 模块是分开的:
313+ # - CLI(/usr/local/bin/pre-commit)用于执行 pre-commit install 等管理命令
314+ # - Python 模块用于 git hook 运行时(hook 模板调用 /usr/bin/python3 -mpre_commit)
315+ # 两者都需要存在,否则 git commit 时会报 "No module named pre_commit"
316+ if ! command -v pre-commit >/dev/null 2>&1; then
317+ warn "pre-commit is not available; skipping hook installation"
318+ log "install_pre_commit_hook succeeded"
319+ return 0
320+ fi
321+ 
322+ if ! python3 -c "import pre_commit" 2>/dev/null; then
323+ log "pre_commit Python module not found; installing..."
324+ python3 -m pip install pre-commit || {
325+ warn "failed to install pre_commit Python module; skipping hook installation"
326+ log "install_pre_commit_hook succeeded"
327+ return 0
328+ }
329+ fi
330+ 
331+ if ! git rev-parse --git-dir >/dev/null 2>&1; then
332+ warn "workspace is not a git repository; skipping pre-commit hook installation"
333+ log "install_pre_commit_hook succeeded"
334+ return 0
335+ fi
336+ 
337+ pre-commit install || warn "pre-commit hook installation failed"
338+ 
339+ log "install_pre_commit_hook succeeded"
340+}
341+ 
342+# =============================================================================
343+# fix_cache_ownership —— 修复缓存目录权限
344+# =============================================================================
345+#
346+# 背景与问题:
347+# 镜像内置的 /etc/profile.d/z_cache.sh 在 login shell 启动时探测 bind mount
348+# 路径(/workspace、/home/mindstudio 等),并自动创建 .cache/ccache 和
349+# .cache/uv 目录。但 z_cache.sh 可能在 updateRemoteUserUID 完成 UID 重映射
350+# 之前被触发(例如通过 docker run 而非 devcontainer),或由 root 身份的
351+# 进程触发,导致这些目录在宿主机上显示为 root:root,与普通文件权限不一致。
352+#
353+# 典型症状:
354+# - 容器内 ls -la ~/.cache → mindstudio:mindstudio(UID 已被重映射)
355+# - 宿主机 ls -la ~/.cache → root:root(原始 UID 0 未变)
356+#
357+# 解决方案:
358+# 在 post-create 阶段主动检测并修复这些缓存目录的 owner,确保与当前用户
359+# 一致。优先修复当前 shell 实际导出的 CCACHE_DIR、UV_CACHE_DIR;这可覆盖
360+# /home/<宿主用户> 被额外挂载进容器的场景。缓存父目录也必须可写,供
361+# pre-commit、pip 等工具创建各自的同级缓存。固定路径作为兜底,覆盖
362+# z_cache.sh 可能探测的工作区和容器用户目录。
363+#
364+# 幂等性保证:
365+# 仅在目录存在且 owner 不匹配时才执行 chown,避免不必要的文件系统操作。
366+fix_cache_ownership() {
367+ local dirs_to_fix=(
368+ "${HOME}/.cache"
369+ "${CCACHE_DIR:-}"
370+ "${UV_CACHE_DIR:-}"
371+ "/home/mindstudio/.cache"
372+ "/workspace/.cache/ccache"
373+ "/workspace/.cache/uv"
374+ "/home/mindstudio/.cache/ccache"
375+ "/home/mindstudio/.cache/uv"
376+ )
377+ 
378+ for d in "${dirs_to_fix[@]}"; do
379+ [ -n "$d" ] || continue
380+ if [ -d "$d" ]; then
381+ local owner
382+ owner=$(stat -c '%U' "$d" 2>/dev/null || true)
383+ if [ "$owner" != "$USER" ]; then
384+ log "fixing ownership of $d ($owner → $USER)"
385+ sudo chown -R "$USER:$USER" "$d" 2>/dev/null || \
386+ warn "failed to chown $d"
387+ fi
388+ fi
389+ done
390+ 
391+ log "fix_cache_ownership succeeded"
392+}
393+ 
394+# =============================================================================
395+# append_dev_hint_once —— 常用开发命令提示
396+# =============================================================================
397+#
398+# 目的:
399+# 在每次打开终端时展示常用开发命令的快捷提示,降低新开发者的学习成本,
400+# 让所有人都能快速知道如何编译项目和运行单元测试。
401+#
402+# 展示时机:
403+# 写入 $HOME/.bash_profile,在每次 Login Shell 启动时显示。
404+#
405+# 幂等性保证:
406+# 通过 marker 注释 (# >>> mindstudio devcontainer dev-hint >>>) 检测
407+# 是否已追加,避免每次容器重建都重复写入。
408+append_dev_hint_once() {
409+ local file="$HOME/.bash_profile"
410+ local marker="# >>> mindstudio devcontainer dev-hint >>>"
411+ 
412+ touch "$file"
413+ if grep -Fqx "$marker" "$file"; then
414+ return 0
415+ fi
416+ 
417+ cat >> "$file" <<'EOF'
418+ 
419+# >>> mindstudio devcontainer dev-hint >>>
420+printf '──────────────────────────────────────────────────────────────────────\n'
421+printf '\033[1;33m 🔥 常用开发命令 (Common Development Commands):\033[0m\n'
422+printf ' \033[1;36m•\033[0m 编译项目 : \033[1;32mpython3 build.py # 结果生成到 artifacts 目录\033[0m\n'
423+printf ' \033[1;36m•\033[0m 单元测试 : \033[1;32mpython3 build.py test\033[0m\n'
424+printf '──────────────────────────────────────────────────────────────────────\n'
425+# <<< mindstudio devcontainer dev-hint <<<
426+EOF
427+}
428+ 
429+# =============================================================================
430+# fix_file_watcher_limit —— 增大 inotify 文件监听上限
431+# =============================================================================
432+#
433+# 背景与问题:
434+# Linux 内核默认的 fs.inotify.max_user_watches 通常为 8192。VS Code 的
435+# File Watcher 会为工作区中每个被监听的文件消耗一个 inotify watch,大型项目
436+# (含 node_modules、build 产物、源代码)很容易超过该限制,导致 VS Code 报错:
437+# "Unable to watch for file changes. Please follow the instructions link to
438+# resolve this issue."
439+#
440+# 这会令 VS Code 的文件变更探测能力降级,表现为:
441+# - 源代码变更无法触发搜索/语法高亮刷新
442+# - Git 面板不能实时反映未暂存变更
443+# - 部分扩展(如 clangd)无法检测文件变化而重建索引
444+#
445+# 解决方案:
446+# 将 max_user_watches 提升到 524288(VS Code 推荐的典型值)。由于容器使用了
447+# --privileged 运行参数,具备修改内核参数的权限,可直接通过 sysctl 或向
448+# /proc/sys/fs/inotify/max_user_watches 写入目标值完成调整。
449+#
450+# 降级策略:
451+# 如果 sysctl 和 /proc 写入均失败(极少数受限环境),只输出告警,不阻断容器创建。
452+fix_file_watcher_limit() {
453+ local desired=524288
454+ local current
455+ current=$(cat /proc/sys/fs/inotify/max_user_watches 2>/dev/null || echo 0)
456+ 
457+ if [ "$current" -ge "$desired" ]; then
458+ log "inotify max_user_watches is already ${current} (>= ${desired})"
459+ return 0
460+ fi
461+ 
462+ log "inotify max_user_watches is ${current}, increasing to ${desired}..."
463+ 
464+ # 优先通过 sysctl 设置
465+ if command -v sysctl >/dev/null 2>&1; then
466+ if sudo sysctl -w fs.inotify.max_user_watches="$desired" >/dev/null 2>&1; then
467+ log "inotify max_user_watches increased to ${desired} (via sysctl)"
468+ return 0
469+ fi
470+ fi
471+ 
472+ # 回退:直接写 /proc
473+ if sudo sh -c "echo ${desired} > /proc/sys/fs/inotify/max_user_watches" 2>/dev/null; then
474+ log "inotify max_user_watches increased to ${desired} (via /proc)"
475+ return 0
476+ fi
477+ 
478+ warn "failed to increase inotify max_user_watches; VS Code file watching may not work correctly"
479+}
480+# =============================================================================
481+# ignore_vscode_settings —— 隔离个人化 VS Code Settings 修改
482+# =============================================================================
483+#
484+# 背景与问题:
485+# .vscode/settings.json 是工作区级别的 VS Code 配置文件,已纳入 Git 版本管理。
486+# 开发者在使用过程中可能需要根据个人偏好微调某些设置(如字体大小、主题等),
487+# 这些个人化修改如果频繁出现在 git status 中会造成噪声,并可能在合入 MR 时
488+# 引入不必要的配置冲突。
489+#
490+# 解决方案:
491+# 通过 git update-index --skip-worktree 标记 .vscode/settings.json,减少个人化
492+# 修改在 git status 中产生的噪声。该标记只影响当前工作区,不会传播到远端。
493+#
494+# 注意:
495+# - skip-worktree 不是 .gitignore 的替代,文件仍然被 Git 追踪;本地修改通常
496+# 不显示在 git status 中,但远端也修改该文件时,普通 pull 仍可能拒绝覆盖。
497+# - 如果需要更新仓库中的 settings.json 模板,建议在本地另 clone 一份代码仓
498+# 或在 gitcode 页面上直接修改提交,避免本地个人偏好被误提交。
499+ignore_vscode_settings() {
500+ git update-index --skip-worktree .vscode/settings.json 2>/dev/null || true
501+ 
502+ log "ignore_vscode_settings succeeded"
503+}
504+ 
505+# =============================================================================
506+# install_git_safe_pull_alias —— 安装 git safe-pull 别名
507+# =============================================================================
508+#
509+# 背景与问题:
510+# ignore_vscode_settings 对 .vscode/settings.json 设置了 skip-worktree,
511+# 这会让 git status 忽略该文件的本地修改。但当远程仓库也更新了同一个文件时,
512+# git pull 会因为 "Your local changes would be overwritten by merge" 而失败。
513+# 普通开发者不了解 git update-index,排查和修复门槛较高。
514+#
515+# 解决方案:
516+# 安装一个全局 Git 别名 git safe-pull,自动处理 skip-worktree 文件:
517+# 1. 找出所有被 skip-worktree 标记的文件
518+# 2. 临时取消这些标记
519+# 3. 暂存 (stash) 本地修改
520+# 4. 执行正常的 git pull(支持所有 git pull 参数)
521+# 5. pull 成功时丢弃临时 stash,采用远端版本
522+# 6. pull 失败时保留 stash,避免本地修改丢失
523+# 7. 无论成功或失败,均重新设置 skip-worktree 标记
524+#
525+# 使用方式:
526+# git safe-pull # 等价于 git pull
527+# git safe-pull --rebase # 等价于 git pull --rebase
528+#
529+# 实现说明:
530+# 具体逻辑放在 .devcontainer/git-safe-pull.sh,避免复杂 Git shell alias 的多层
531+# 引号破坏参数。每次 post-create 都重新安装脚本并刷新 alias,以覆盖旧容器中
532+# 已存在的错误版本。
533+install_git_safe_pull_alias() {
534+ local source_script="/workspace/.devcontainer/git-safe-pull.sh"
535+ local target_script="$HOME/.local/bin/git-safe-pull"
536+ 
537+ if [ ! -f "$source_script" ]; then
538+ warn "safe-pull source script not found: $source_script"
539+ return 0
540+ fi
541+ 
542+ install -m 0755 "$source_script" "$target_script" || {
543+ warn "failed to install git-safe-pull"
544+ return 0
545+ }
546+ git config --global alias.safe-pull '!git-safe-pull'
547+ 
548+ log "install_git_safe_pull_alias succeeded"
549+}
550+ 
551+# =============================================================================
552+# install_gitleaks —— Gitleaks 秘密扫描二进制下载
553+# =============================================================================
554+#
555+# 背景与问题:
556+# pre-commit 配置中的 gitleaks-offline-scan hook 执行 `./gitleaks protect`,
557+# 期望仓库根目录存在 gitleaks 二进制文件。如果缺失,git commit 时
558+# pre-commit hook 会因 "Executable ./gitleaks not found" 而失败。
559+#
560+# 解决方案:
561+# 从华为 OBS 镜像站下载预编译的 gitleaks 二进制到 /workspace/gitleaks,
562+# 确保 devcontainer 创建后立即可用,无需开发者手工下载。
563+#
564+# 降级策略:
565+# wget 下载失败时只告警,不阻塞容器创建。
566+# 开发者仍可手工下载或使用 git commit --no-verify 绕过。
567+install_gitleaks() {
568+ local target="/workspace/gitleaks"
569+ local base_url="https://inst.obs.cn-north-4.myhuaweicloud.com/env/mirror"
570+ local arch=""
571+ local url=""
572+ 
573+ # 根据 CPU 架构选择对应的二进制目录
574+ case "$(uname -m)" in
575+ x86_64) arch="x86_64" ;;
576+ aarch64) arch="aarch64" ;;
577+ *) arch="x86_64" ;; # 默认回退到 x86_64
578+ esac
579+ url="${base_url}/${arch}/gitleaks"
580+ 
581+ log "downloading gitleaks (${arch}) from OBS..."
582+ if wget --no-host-directories -c --no-check-certificate \
583+ -O "$target" "$url" 2>/dev/null; then
584+ chmod +x "$target"
585+ log "gitleaks installed successfully: $($target --version 2>/dev/null || echo 'version unknown')"
586+ else
587+ warn "failed to download gitleaks (${arch}) from OBS; git commit may fail on pre-commit hook"
588+ warn "URL attempted: ${url}"
589+ rm -f "$target"
590+ fi
591+}
592+ 
593+# =============================================================================
594+# 主执行流程
595+# =============================================================================
596+#
597+# 按依赖顺序执行:先修复目录权限,再写用户配置,最后安装 Git 辅助能力。
598+# 各模块尽量自行降级并输出 warning,非关键项失败不阻止容器启动。
599+ 
600+log "post-create setup started"
601+ 
602+fix_cache_ownership
603+fix_file_watcher_limit
atomgit-botatomgit-bot
atomgit-botatomgit-bot7月22日

🟠 High Priority

post-create.sh 第 508 行调用 fix_file_watcher_limit,但该函数在整个仓库中从未定义。脚本头部注释(第 35 行)描述了其职责为"提升 inotify max_user_watches 至 524288",但函数体缺失。由于脚本使用 set -uo pipefail 但未启用 -e,调用该函数时 bash 报 "command not found" 后继续执行,但预期的 inotify 限制提升不会生效。这将导致 VS Code 在监视大量文件时出现 "ENOSPC: System limit for number of file watchers reached" 错误。

建议:添加 fix_file_watcher_limit 函数定义,将其放在与其他函数定义同级的位置(如 fix_cache_ownership 之后),实现提升 inotify max_user_watches 至 524288 的逻辑。典型实现:sudo sysctl -w fs.inotify.max_user_watches=524288 或写入 /etc/sysctl.conf

likedislike
atomgit-botatomgit-bot7月22日

🟠 High Priority

变更行:.devcontainer/post-create.sh 第 508 行调用了 fix_file_watcher_limit,该函数在脚本顶部的执行顺序注释(第 35 行)中被描述为"提升 inotify max_user_watches 至 524288",但在整个仓库中均未定义。

证据链:

  • 第 35 行注释声明了该函数的存在和用途;
  • 第 508 行在主执行流程中直接调用 fix_file_watcher_limit
  • 使用 grep 在整个仓库搜索 fix_file_watcher_limit,仅在 post-create.sh 的注释和调用处出现,没有任何函数定义;
  • 脚本使用 set -uo pipefail(未启用 -e),因此调用未定义命令时不会终止脚本,但该步骤会静默失败。

影响:容器初始化时 inotify max_user_watches 不会被提升到 524288,可能导致文件监视器达到上限后 VS Code 无法正常监视文件变更,开发体验受损。

likedislike
604+configure_user_bin
605+ensure_shared_bin_path
606+configure_python311
607+sync_git_identity
608+append_dev_hint_once
609+install_pre_commit_hook
610+install_gitleaks
611+ignore_vscode_settings
612+install_git_safe_pull_alias
613+ 
614+log "post-create setup finished"
@@ -1,11 +1,36 @@
1-__pycache__1+# IDE
2-.vscode/
3.idea/2.idea/
3+.vscode/settings.local.json
4+ 
5+# Python
6+__pycache__/
7+*.py[cod]
8+*$py.class
9+.venv/
10+venv/
11+.pytest_cache/
12+.mypy_cache/
13+.ruff_cache/
14+.coverage
15+htmlcov/
16+*.egg-info/
17+pip-wheel-metadata/
18+test.log
19+test_output_*.cpp
20+ 
21+# Build outputs
4build/22build/
5build_ut/23build_ut/
6output/24output/
7-cmake-build-debug/25+artifacts/
8dist/26dist/
9-mskl.egg-info/27+*.so
28+*.pyd
10 29 
11-.codemate30+# Cache and local snapshots
31+.cache/
32+.codemate
33+.devcontainer/.host-gitconfig
34+ 
35+# Pre-commit tools
36+gitleaks
@@ -19,6 +19,7 @@ repos:
19 - id: check-merge-conflict19 - id: check-merge-conflict
20 - id: detect-private-key20 - id: detect-private-key
21 - id: check-json21 - id: check-json
22+ exclude: ^\.devcontainer/
22 23 
23 # -------------------------- Python 核心检查 --------------------------24 # -------------------------- Python 核心检查 --------------------------
24 # Ruff:指定读取 pre-commit/pyproject.toml25 # Ruff:指定读取 pre-commit/pyproject.toml
@@ -0,0 +1,34 @@
1+{
2+ "version": "0.2.0",
3+ "configurations": [
4+ {
5+ "name": "Python: Debug Active File",
6+ "type": "debugpy",
7+ "request": "launch",
8+ "program": "${file}",
9+ "console": "integratedTerminal",
10+ "cwd": "${workspaceFolder}",
11+ "env": {
12+ "PYTHONPATH": "${workspaceFolder}"
13+ },
14+ "justMyCode": false
15+ },
16+ {
17+ "name": "Python: Debug Pytest Case",
18+ "type": "debugpy",
19+ "request": "launch",
20+ "module": "pytest",
21+ "args": [
22+ "${file}",
23+ "-v",
24+ "-s"
25+ ],
26+ "console": "integratedTerminal",
27+ "cwd": "${workspaceFolder}",
28+ "env": {
29+ "PYTHONPATH": "${workspaceFolder}"
30+ },
31+ "justMyCode": false
32+ }
33+ ]
34+}
@@ -0,0 +1,43 @@
1+{
2+ "files.watcherExclude": {
3+ ".cache/**": true,
4+ "{build,build_ut,output,artifacts,dist}/**": true,
5+ "**/*.egg-info/**": true,
6+ "**/{.venv,node_modules,__pycache__,.pytest_cache,.ruff_cache,.mypy_cache}/**": true
7+ },
8+ "search.exclude": {
9+ ".cache/**": true,
10+ "{build,build_ut,output,artifacts,dist}/**": true,
11+ "**/*.egg-info/**": true,
12+ "**/{.venv,node_modules,__pycache__,.pytest_cache,.ruff_cache,.mypy_cache}/**": true
13+ },
14+ "files.exclude": {
15+ ".cache": true,
16+ "**/*.egg-info": true,
17+ "**/{__pycache__,.pytest_cache,.ruff_cache,.mypy_cache}": true
18+ },
19+ "terminal.integrated.scrollback": 10000,
20+ "git.detectSubmodules": false,
21+ "git.autoRepositoryDetection": false,
22+ "python.defaultInterpreterPath": "python3",
23+ "python.terminal.activateEnvironment": false,
24+ "python.analysis.extraPaths": [
25+ "${workspaceFolder}"
26+ ],
27+ "python.analysis.exclude": [
28+ "**/node_modules",
29+ "**/__pycache__",
30+ "**/.*",
31+ ".venv",
32+ "build",
33+ "build_ut",
34+ "output",
35+ "artifacts",
36+ "dist"
37+ ],
38+ "python.testing.pytestEnabled": true,
39+ "python.testing.pytestArgs": [
40+ "test/launcher",
41+ "test/op_tune"
42+ ]
43+}
@@ -0,0 +1,62 @@
1+{
2+ "version": "2.0.0",
3+ "tasks": [
4+ {
5+ "label": "Build: Release Mode",
6+ "type": "shell",
7+ "command": "python3",
8+ "args": [
9+ "${workspaceFolder}/build.py"
10+ ],
11+ "options": {
12+ "cwd": "${workspaceFolder}"
13+ },
14+ "problemMatcher": [],
15+ "presentation": {
16+ "reveal": "always",
17+ "panel": "dedicated",
18+ "clear": false
19+ },
20+ "group": {
21+ "kind": "build",
22+ "isDefault": true
23+ }
24+ },
25+ {
26+ "label": "Test: Run Unit Tests",
27+ "type": "shell",
28+ "command": "python3",
29+ "args": [
30+ "${workspaceFolder}/build.py",
31+ "test"
32+ ],
33+ "options": {
34+ "cwd": "${workspaceFolder}"
35+ },
36+ "problemMatcher": [],
37+ "presentation": {
38+ "reveal": "always",
39+ "panel": "dedicated",
40+ "clear": false
41+ },
42+ "group": {
43+ "kind": "test",
44+ "isDefault": true
45+ }
46+ },
47+ {
48+ "label": "Workspace: Clean",
49+ "type": "shell",
50+ "command": "git clean -xdf -e .cache -e '.cache/**'",
51+ "options": {
52+ "cwd": "${workspaceFolder}"
53+ },
54+ "problemMatcher": [],
55+ "presentation": {
56+ "reveal": "always",
57+ "panel": "dedicated",
58+ "clear": false
59+ }
60+ }
61+ ]
62+}
@@ -175,10 +175,16 @@ class BuildManager:
175 if build_version:175 if build_version:
176 logging.info("--build-version: %s", build_version)176 logging.info("--build-version: %s", build_version)
177 177 
178+ extra_options = {}
178 for option in self.parsed_arguments.extra:179 for option in self.parsed_arguments.extra:
179 key, _, value = option.partition('=')180 key, _, value = option.partition('=')
181+ extra_options[key] = value
180 logging.info("--extra: %s = %s", key, value)182 logging.info("--extra: %s = %s", key, value)
181 183 
184+ if extra_options.get('only_down_deps') == 'true':
185+ logging.info("only_down_deps=true, exiting after dependency download.")
186+ return
187+ 
182 if 'test' in self.parsed_arguments.command:188 if 'test' in self.parsed_arguments.command:
183 self._run_unit_tests()189 self._run_unit_tests()
184 else:190 else: