diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 2586039..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,100 +0,0 @@ -# docker build -t my-dev-container . -FROM debian:13.1-slim - -# 元数据 -LABEL maintainer="i@oo1.dev" \ - description="Development container for Claude and Gemini development" - -# 构建参数 -ARG GIT_DELTA_VERSION=0.18.2 -ARG ZSH_IN_DOCKER_VERSION=1.2.1 -ARG TZ=Asia/Shanghai - -# 环境变量 -ENV PNPM_HOME="/home/usr_vscode/.local/share/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -ENV LANG=zh_CN.UTF-8 \ - LC_ALL=zh_CN.UTF-8 \ - TZ=${TZ} \ - # Set the default shell to zsh rather than sh - SHELL=/bin/zsh \ - # Set the default editor and visual - EDITOR=nano \ - VISUAL=nano \ - POWERLEVEL9K_DISABLE_GITSTATUS=true - -# 安装所有系统依赖 -RUN apt-get update && apt-get install -y --no-install-recommends \ - # 语言包 - locales \ - # 基础工具 - ca-certificates wget curl \ - # 开发工具 - less git procps sudo fzf zsh man-db unzip gnupg2 gh \ - # 网络工具 - iptables ipset iproute2 dnsutils aggregate \ - # 编辑器和实用工具 - jq nano vim \ - # 配置中文环境 - && sed -i 's/^# *zh_CN.UTF-8/zh_CN.UTF-8/' /etc/locale.gen \ - && locale-gen zh_CN.UTF-8 \ - && update-locale LANG=zh_CN.UTF-8 \ - # 清理缓存 - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# 创建用户和配置 sudo -RUN groupadd --gid 1000 usr_vscode \ - && useradd --uid 1000 --gid usr_vscode --shell /bin/zsh --create-home usr_vscode \ - && echo "usr_vscode ALL=(root) NOPASSWD: ALL" > /etc/sudoers.d/usr_vscode_user_config \ - && chmod 0440 /etc/sudoers.d/usr_vscode_user_config \ - && cat /etc/passwd - -# 配置命令历史 -RUN mkdir -p /commandhistory \ - && touch /commandhistory/.bash_history \ - && chown -R usr_vscode:usr_vscode /commandhistory - -# 创建工作目录 -RUN mkdir -p /wrkspc /home/usr_vscode/.claude \ - && chown -R usr_vscode:usr_vscode /wrkspc /home/usr_vscode/.claude - -# 安装 git-delta https://github.com/dandavison/delta/releases -RUN ARCH=$(dpkg --print-architecture) && \ - curl -L -o "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \ - sudo dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \ - rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" - -WORKDIR /wrkspc - -# 切换到非 root 用户 -USER usr_vscode - -# 配置 Zsh https://github.com/deluan/zsh-in-docker/releases -# Default powerline10k theme -RUN sh -c "$(curl -L -o - https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \ - -p git \ - -p fzf \ - -a "source <(fzf --zsh)" \ - -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \ - -x \ - # -a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \ - # -a "source /usr/share/doc/fzf/examples/completion.zsh" \ - # https://github.com/deluan/zsh-in-docker/blob/55a4e476f78f37204af9dfc0290a92e52cf881b1/zsh-in-docker.sh#L121-L123 - && sed -i 's/^export LANG=.*/export LANG="zh_CN.UTF-8"/' /home/usr_vscode/.zshrc \ - && sed -i 's/^export LANGUAGE=.*/export LANGUAGE="zh_CN:zh"/' /home/usr_vscode/.zshrc \ - && sed -i 's/^export LC_ALL=.*/export LC_ALL="zh_CN.UTF-8"/' /home/usr_vscode/.zshrc \ - # >>>>> - && echo "" >> /home/usr_vscode/.zshrc \ - && echo "alias gemini='gemini --yolo -m gemini-2.5-pro'" >> /home/usr_vscode/.zshrc \ - && echo "alias claude='claude --dangerously-skip-permissions'" >> /home/usr_vscode/.zshrc \ - && echo '# [[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"' >> /home/usr_vscode/.zshrc - -# 安装 pnpm -# https://pnpm.io/installation#in-a-docker-container -RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.zshrc" SHELL="$(which zsh)" zsh - \ - && pnpm --version \ - && mkdir /home/usr_vscode/.pnpm-store \ - && mkdir -p /home/usr_vscode/.cache/pnpm \ - # /home/usr_vscode/.config/pnpm/rc - && pnpm config --global set store-dir /home/usr_vscode/.pnpm-store diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 673fd6f..06a539e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,41 +1,31 @@ -/** - * 一些参考链接: - * https://containers.dev/implementors/json_reference/#variables-in-devcontainerjson - * https://code.claude.com/docs/zh-CN/devcontainer - * https://github.com/Kilo-Org/kilocode/blob/main/cli/Dockerfile - */ { - "name": "SBX", - "image": "my-dev-container", - // "build": { "dockerfile": "Dockerfile", "args": {} }, - "runArgs": [ - // ----- - // "--network=host", - // "--add-host=myservice.local:127.0.0.1", - // ----- - // "--env-file", - // ".devcontainer/.env", - // ----- - "--cap-add=NET_ADMIN", - "--cap-add=NET_RAW", - "--add-host=host.docker.internal:host-gateway", - "--name=${localWorkspaceFolderBasename}-devcontainer" - ], + "image": "ghcr.io/yanhao98/h-devcontainer:oncreatecommand.d", + "runArgs": ["--name=${localWorkspaceFolderBasename}-devcontainer"], "forwardPorts": [4730, 4731], // vscode://settings/remote.localPortHost -> 默认只监听 localhost "portsAttributes": { "4730": { "label": "开发服务器端口", "onAutoForward": "notify" }, "4731": { "label": "预览服务器端口", "onAutoForward": "notify" } }, + "remoteEnv": { + "ANTHROPIC_AUTH_TOKEN": "${localEnv:ANTHROPIC_AUTH_TOKEN}", + "ANTHROPIC_BASE_URL": "${localEnv:ANTHROPIC_BASE_URL}", + "GEMINI_API_KEY": "${localEnv:GEMINI_API_KEY}", + "GOOGLE_GEMINI_BASE_URL": "${localEnv:GOOGLE_GEMINI_BASE_URL}", + "PATH": "${containerEnv:PATH}:${containerWorkspaceFolder}/node_modules/.bin:${containerEnv:HOME}/.bun/bin:${containerEnv:HOME}/.bun/bin/bun-node-fallback-bin" + }, + "containerEnv": { + "HOME": "/home/usr_vscode", + // "NODE_OPTIONS": "--max-old-space-size=4096", + "TZ": "${localEnv:TZ:Asia/Shanghai}" + }, "customizations": { "vscode": { "extensions": [ // AI "github.copilot-chat", - "vicanent.gcmp", "anthropic.claude-code", "google.gemini-cli-vscode-ide-companion", - "kilocode.kilo-code", - "alibaba-cloud.tongyi-lingma", + "vicanent.gcmp", // >>>>> // "eamodio.gitlens", "tu6ge.naive-ui-intelligence", @@ -46,7 +36,6 @@ "vue.volar", // <<<<< // 代码质量 / 格式化 / Lint - "editorconfig.editorconfig", "dbaeumer.vscode-eslint", "stylelint.vscode-stylelint", "oxc.oxc-vscode", @@ -58,13 +47,14 @@ "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, - "chat.extensionUnification.enabled": true, - "chat.tools.terminal.autoApprove": { "/.*/": true }, - "chat.tools.terminal.ignoreDefaultAutoApproveRules": false, + "chat.tools.terminal.autoApprove": { + "/.*/": true, + "git push": false + }, // * 尽管使用了“/.*/”,但有些还是会失败,因为有几个错误的默认值: // * https://github.com/microsoft/vscode/issues/266651#issuecomment-3292581459 - + "chat.tools.terminal.ignoreDefaultAutoApproveRules": true, "tasks": { "version": "2.0.0", "tasks": [ @@ -72,24 +62,16 @@ "type": "npm", "script": "dev", "label": "🚀 Dev: Run on Folder Open", - "detail": "启动开发服务器", - "runOptions": { "runOn": "folderOpen" }, - "problemMatcher": [], + "runOptions": { + "runOn": "folderOpen" + }, "isBackground": true, - "presentation": { "reveal": "always", "panel": "dedicated" }, - "group": { "kind": "build", "isDefault": false } + "presentation": { + "panel": "dedicated" + } } ] }, - "terminal.integrated.defaultProfile.linux": "💲zsh", - "terminal.integrated.profiles.linux": { - "💲zsh": { - "path": "zsh", - "icon": "terminal-cmd", - "overrideName": true, - "color": "terminal.ansiGreen" - } - }, // https://stackoverflow.com/questions/75708866/vscode-dev-container-fails-to-load-ms-vscode-js-debug-extension-correctly // https://davidwesst.com/blog/missing-bootloader-in-vscode-devcontainer/ /** @@ -100,62 +82,61 @@ * Error: illegal value for flag --max-old-space-size=4096--max-old-space-size=4096 of type size_t * 将其设置为 "disabled" 以规避该问题。 */ - "debug.javascript.autoAttachFilter": "disabled" + // "debug.javascript.autoAttachFilter": "disabled", + "terminal.integrated.defaultProfile.linux": "💲zsh", + "terminal.integrated.profiles.linux": { + "💲zsh": { + "path": "zsh", + "icon": "terminal-cmd", + "overrideName": true, + "color": "terminal.ansiGreen" + } + } } } }, - "containerEnv": { - // "CLAUDE_CONFIG_DIR": "/home/usr_vscode/.claude", - "ANTHROPIC_AUTH_TOKEN": "${localEnv:ANTHROPIC_AUTH_TOKEN}", - "ANTHROPIC_BASE_URL": "${localEnv:ANTHROPIC_BASE_URL}", - "GEMINI_API_KEY": "${localEnv:GEMINI_API_KEY}", - "GOOGLE_GEMINI_BASE_URL": "${localEnv:GOOGLE_GEMINI_BASE_URL}", - "NODE_OPTIONS": "--max-old-space-size=4096", - "CLAUDE_CONFIG_DIR": "/home/usr_vscode/.claude", - "DEVCONTAINER": "true", // https://github.com/anthropics/claude-code/blob/1fe9e369a7c30805189cbbb72eb69c15ed4ec96b/.devcontainer/Dockerfile#L42 - "SANDBOX": "sandbox-devcontainer", // gemini 显示这个。 - "TZ": "${localEnv:TZ:Asia/Shanghai}" - }, "mounts": [ - "source=${localWorkspaceFolderBasename}---bashhistory,target=/commandhistory,type=volume", - "source=${localWorkspaceFolderBasename}---iflow,target=/home/usr_vscode/.iflow,type=volume", - "source=${localWorkspaceFolderBasename}---gemini,target=/home/usr_vscode/.gemini,type=volume", - "source=${localWorkspaceFolderBasename}---claude-code-router,target=/home/usr_vscode/.claude-code-router,type=volume", - "source=${localWorkspaceFolderBasename}---claude-code,target=/home/usr_vscode/.claude,type=volume", - "source=${localWorkspaceFolderBasename}---pnpm-store,target=/home/usr_vscode/.pnpm-store,type=volume", - "source=${localWorkspaceFolderBasename}---cache-pnpm,target=/home/usr_vscode/.cache/pnpm,type=volume", - "source=${localWorkspaceFolderBasename}---node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume" + { + // 不挂载可能会遇到:`Cannot run macOS (Mach-O) executable in Docker: Exec format error` + "type": "volume", + "source": "${localWorkspaceFolderBasename}-node_modules", + "target": "${containerWorkspaceFolder}/node_modules" + }, + { + "type": "volume", + "source": "devcontainer-pnpm-store", + "target": "/home/usr_vscode/.pnpm-store" + }, + { + "type": "volume", + "source": "devcontainer-bun-install-cache", + "target": "/home/usr_vscode/.bun/install/cache" + // rm -rf ~/.bun/install/cache/* + }, + { + "type": "bind", + "source": "${localWorkspaceFolder}/.devcontainer/onCreateCommand.d", + "target": "/usr/local/etc/onCreateCommand.d" + } ], /** * 执行顺序总结 - 1. initializeCommand (本地主机) + 1. initializeCommand (本地主机,容器创建前) 2. 容器创建 - 3. onCreateCommand (容器内,仅首次) - 4. updateContentCommand (容器内) - 5. postCreateCommand (容器内) + 3. onCreateCommand (容器首次创建时执行一次) + 4. updateContentCommand () + 5. postCreateCommand (每次容器启动时执行) 6. 容器启动 7. postStartCommand (容器内,每次启动) 8. VS Code 附加到容器 9. postAttachCommand (容器内,每次附加) */ - "initializeCommand": /* 本地执行(容器创建前) */ "echo '准备创建容器...'", - "onCreateCommand": /* 容器内执行(仅首次创建) */ { - "setup-node": "pnpm env use lts --global", - "fix-claude-code-router": "sudo chown -R usr_vscode /home/usr_vscode/.claude-code-router", - "fix-claude": "sudo chown -R usr_vscode /home/usr_vscode/.claude", - "fix-gemini": "sudo chown -R usr_vscode /home/usr_vscode/.gemini", - "fix-iflow": "sudo chown -R usr_vscode /home/usr_vscode/.iflow", - "fix-node_modules": "sudo chown usr_vscode node_modules" - }, - "updateContentCommand": /* 容器内执行(创建后或内容更新) */ "pnpm install", - "postCreateCommand": /* 容器内执行(创建完成后) */ { - "install-global-cli": "pnpm install -g @google/gemini-cli@latest @anthropic-ai/claude-code@latest @musistudio/claude-code-router@latest @iflow-ai/iflow-cli@latest" - }, - "postStartCommand": /* 容器内执行(每次启动) */ "echo '容器已启动!'", - "postAttachCommand": /* 容器内执行(每次附加) */ "echo '开发环境已就绪!${containerWorkspaceFolder}'", - // "updateRemoteUserUID": true, + "initializeCommand": "echo '↘️ 准备创建容器...'; docker pull ghcr.io/yanhao98/h-devcontainer:oncreatecommand.d;", + "onCreateCommand": "echo '↘️ 容器首次创建!'; /usr/local/bin/onCreateCommand.sh", + "updateContentCommand": "echo '↘️ 容器内容已更新!'; zsh -c 'time pnpm install;'", + "postCreateCommand": "echo '↘️ 容器已创建!';", + "postStartCommand": "echo '↘️ 容器启动了!'", + "postAttachCommand": "echo '↘️ VS Code 已附加到容器!'; zsh -c 'source ~/.zshrc; echo \"$(date +%Y-%m-%dT%H:%M:%S%z) - Welcome to your Dev Container!\"';", "waitFor": "updateContentCommand", - // "workspaceMount": "source=${localWorkspaceFolder},target=/${localWorkspaceFolderBasename},type=bind,consistency=delegated", - // "workspaceFolder": "/${localWorkspaceFolderBasename}", "remoteUser": "usr_vscode" } diff --git a/.devcontainer/onCreateCommand.d/00-configure-locale b/.devcontainer/onCreateCommand.d/00-configure-locale new file mode 100755 index 0000000..e5aac50 --- /dev/null +++ b/.devcontainer/onCreateCommand.d/00-configure-locale @@ -0,0 +1,32 @@ +#!/bin/zsh -eu + +run_sys() { + if [[ "$EUID" -ne 0 ]]; then + sudo "$@" + else + "$@" + fi +} + +if ! dpkg -s locales >/dev/null 2>&1; then + run_sys apt-get update -qq + run_sys DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --no-install-recommends locales +fi + +run_sys sed -i "s/^# *zh_CN.UTF-8/zh_CN.UTF-8/" /etc/locale.gen +if ! locale -a | grep -qi "^zh_CN\.utf8$"; then + run_sys locale-gen zh_CN.UTF-8 +fi + +zshrc="$HOME/.zshrc" +mkdir -p "${zshrc:h}" +touch "$zshrc" + +if ! grep -q '^export LANG=zh_CN.UTF-8$' "$zshrc" 2>/dev/null; then + printf '\nexport LANG=zh_CN.UTF-8\n' >>"$zshrc" +fi + +if ! grep -q '^export LC_ALL=zh_CN.UTF-8$' "$zshrc" 2>/dev/null; then + printf 'export LC_ALL=zh_CN.UTF-8\n' >>"$zshrc" +fi +run_sys update-locale LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 diff --git a/.devcontainer/onCreateCommand.d/00-fix-permission b/.devcontainer/onCreateCommand.d/00-fix-permission new file mode 100755 index 0000000..025bd43 --- /dev/null +++ b/.devcontainer/onCreateCommand.d/00-fix-permission @@ -0,0 +1,3 @@ +#!/bin/zsh -eu + +sudo chown -R $(whoami):$(whoami) node_modules || true diff --git a/.devcontainer/onCreateCommand.d/01-configure-zsh b/.devcontainer/onCreateCommand.d/01-configure-zsh new file mode 100755 index 0000000..e06e31b --- /dev/null +++ b/.devcontainer/onCreateCommand.d/01-configure-zsh @@ -0,0 +1,36 @@ +#!/bin/zsh -eu + +TARGET_USER="usr_vscode" +if [[ "$EUID" -eq 0 ]]; then + target_home="/home/${TARGET_USER}" +else + target_home="$HOME" +fi + +zshrc="${target_home}/.zshrc" +mkdir -p "${zshrc:h}" +touch "$zshrc" + +vscode_marker='if [[ "$TERM_PROGRAM" == "vscode" ]]; then' +if ! grep -Fq "$vscode_marker" "$zshrc" 2>/dev/null; then + cat <<'EOF' >>"$zshrc" + +if [[ "$TERM_PROGRAM" == "vscode" ]]; then + local vscode_executable + if command -v code >/dev/null 2>&1; then + vscode_executable="code" + elif command -v code-insiders >/dev/null 2>&1; then + vscode_executable="code-insiders" + fi + + if [ -n "$vscode_executable" ]; then + . "$($vscode_executable --locate-shell-integration-path zsh)" + fi +fi +EOF +fi + +alias_snippet="alias clean-node-modules='setopt rm_star_silent; rm -rf node_modules/.*; rm -rf node_modules/*'; unsetopt rm_star_silent" +if ! grep -Fqx -- "$alias_snippet" "$zshrc" 2>/dev/null; then + printf '\n%s\n' "$alias_snippet" >>"$zshrc" +fi diff --git a/.devcontainer/onCreateCommand.d/10-00-install-bun b/.devcontainer/onCreateCommand.d/10-00-install-bun new file mode 100755 index 0000000..988c95d --- /dev/null +++ b/.devcontainer/onCreateCommand.d/10-00-install-bun @@ -0,0 +1,49 @@ +#!/bin/zsh -eu + +# >>>>> 一些参考资料 +# https://github.com/oven-sh/bun/blob/277fc558e2039e3ab25ecebecc5a3f04c5c3199a/dockerhub/debian-slim/Dockerfile +# COPY --chown=usr_vscode:usr_vscode --from=oven/bun:1-debian /usr/local/bin/bun /usr/local/bin +# IS_BUN_AUTO_UPDATE=true SHELL=zsh bun completions +# bun install -g 将会安装到 ENV BUN_INSTALL_BIN +# ENV PATH "${BUN_INSTALL_BIN}:${PATH}" +# RUN sudo mkdir -p /usr/local/bun-node-fallback-bin && sudo ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +# ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" +# <<<<< + +# fix permission +# 有可能这个目录是 volume 挂载过来的,权限会有问题。 +sudo mkdir -p ~/.bun || true +sudo chown -R $(whoami):$(whoami) ~/.bun || true + +curl -fsSL https://bun.com/install | bash + +# https://bun.sh/docs/runtime/nodejs-compat +mkdir -p ~/.bun/bin/bun-node-fallback-bin || true +ln -sf ~/.bun/bin/bun ~/.bun/bin/bun-node-fallback-bin/node + +which -a bun +bun --version +which -a bunx +bunx --version +which -a node + +# >>>>> pnpm +bun add -g pnpm@latest +# echo "alias pnpm='bunx pnpm'" >> ~/.zshrc +# echo "alias pnpx='bunx pnpx'" >> ~/.zshrc +bunx pnpm config set store-dir ~/.pnpm-store +sudo chown -R $(whoami):$(whoami) ~/.pnpm-store || true + +# >>>>> npm +bun add -g npm@latest + +# >>>>> Claude Code 工具 +bun add -g @anthropic-ai/claude-code@latest +echo "alias claude='bunx claude --dangerously-skip-permissions'" >> ~/.zshrc +# 参考: https://github.com/anthropics/claude-code/blob/1fe9e369a7c30805189cbbb72eb69c15ed4ec96b/.devcontainer/Dockerfile#L42 +echo "export DEVCONTAINER=true" >> ~/.zshrc + +# >>>>>> Gemini CLI 工具 +bun add -g @google/gemini-cli@latest +echo "alias gemini='bunx -g gemini --yolo -m gemini-2.5-pro'" >> ~/.zshrc +echo "export SANDBOX=bun-devcontainer" >> ~/.zshrc