mcpField
S
setup-devcontainer

setup-devcontainer

github.com/ken-guru云与 DevOps146 次安装6 天前更新
代码仓库

关于 setup-devcontainer

根据模板为仓库或本地工作区生成共享 Dev Container 配置。该技能会指导工作区检测、现有容器检查,以及 SSH 配置和出站网络限制等选项的决策。

主要功能

  1. 01根据模板生成 .devcontainer/
  2. 02检测仓库与工作区状态
  3. 03检查现有配置和容器
  4. 04配置可选的 SSH 自动化
  5. 05配置出站网络允许列表

适用场景

  1. 01为仓库创建 Dev Container
  2. 02在连接 GitHub 前配置本地工作区
  3. 03重新打开前检查遗留容器
  4. 04限制开发容器的出站网络访问

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ken-guru/skills --skill setup-devcontainer

安装后在 Claude Code 中输入/setup-devcontainer确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CONTEXT.md
docs/
adding-a-backing-service.md
adding-firewall-later.md
adding-ssh-later.md
connecting-local-checkout.md
docs/adr/
0001-per-container-ssh-keys.md
0002-collapse-tool-container-isolation.md
0003-manual-first-deploy-key-registration.md
0004-digest-pin-base-image-reject-docker-hardened-images.md
0005-network-egress-firewall-and-network-manifest.md
0006-capability-drop-reject-readonly-rootfs-and-seccomp.md
0007-agent-trust-hub-audit-response.md
scripts/
check-skill-sync-targets.sh
patch-if-absent.sh
patch-json-array-if-absent.sh
render-devcontainer.sh
test-devcontainer.sh
test-firewall-lib.sh
test-integration.sh
test-network-manifest-domains.sh
test-patch-if-absent.sh
test-patch-json-array-if-absent.sh
verify-devcontainer.sh
scripts/lib/
render-lib.sh
templates/
allowed-domains.local.txt
bash-env.sh
devcontainer.json
devcontainer.with-ssh.json
Dockerfile
env.baseline.example

详细介绍

设置 Devcontainer

根据 templates/ 中的模板生成 .devcontainer/: 一个共享容器——单个 devcontainer.json 和 Dockerfile——供每个选定的 AI CLI 通过各自独立的技能安装到其中;每项技能都在本技能之后单独运行。有关本技能的术语(共享容器、本地检出、共享检出、脚手架、CLI 技能、自有块约定、项目自有块、能力接缝、网络清单、项目挂载),请参阅 CONTEXT.md。

本技能完全负责容器定义(devcontainer.json、Dockerfile)以及 post-create.sh/post-start.sh/README.md 中的共享部分——任何 CLI 技能都不会直接编辑它们,只有一个范围狭窄且明确的例外(能力接缝 runArgs,由 setup-codex-devcontainer 使用)。

1. 解析工作区根目录,然后检测目标仓库

首先,确保你位于 VS Code 实际会为此开发容器打开的目录中。从现在起,所有检查和文件写入(包括第 2 步中的过时容器检查)都以 $(pwd) 为准;如果目录不对,过时容器或放错位置的 .devcontainer/ 就会悄无声息地漏过检查。

git rev-parse --is-inside-work-tree 2>/dev/null
  • 失败:当前还不是 Git 仓库(下文“本地检出”部分中 git init 之前的情况)。完全跳过此检查,继续检测仓库。

  • 成功:将当前检出的根目录与仓库的主工作树进行比较:

    git rev-parse --show-toplevel
    git rev-parse --git-common-dir
    

    --git-common-dir 是共享的 .git 目录;其父目录是主检出的根目录。链接工作树中的情况不同(每个工作树都有各自的顶层目录,但共享此公共目录)。

    • --git-common-dir 的父目录与 --show-toplevel 相同:你已经位于仓库根目录,或其普通子目录中。切换到 --show-toplevel 并继续——此技能中之后所有 $(pwd) 引用现在都正确,无需进一步更改。
    • 两者不同:这是一个链接的 Git 工作树——与此仓库共享历史记录的独立检出目录,常用于隔离的逐任务 Agent 会话。不要猜测 VS Code 会打开哪个目录(每个开发容器对应一个工作树也是合理的设置)。停止并询问:“这看起来是一个 Git 工作树,不一定是你会在 VS Code 中为开发容器打开的文件夹。你会打开哪个文件夹?如果不是这个,请从那个文件夹重新运行此技能——以下检查(过时容器、现有配置)只会检查当前目录。”
git remote get-url origin

从中解析出 owner/repo(适用于 [email protected]:owner/repo.git 和 https://github.com/owner/repo 两种形式)——这就是 {{REPO_SLUG}}。{{REPO_NAME}} 仅为其中的 repo 部分,用于卷名称。

如果还没有 origin 远程仓库,问一个问题:“此项目是否已有 GitHub 仓库?如果有,请提供其名称(owner/repo)——容器的 GH_TOKEN 需要有权访问它。如果没有,或者你暂时还不准备连接,则将其设为仅本地的本地检出——之后可以连接到 GitHub(参见‘将本地检出连接到真实的 GitHub 仓库’)。”

  • 提供了现有仓库的名称:按照上文处理现有 origin 的方式解析 {{REPO_SLUG}}/{{REPO_NAME}}。
  • 本地检出:{{REPO_SLUG}} 保持为空。{{REPO_NAME}} 回退为本地工作目录的基本名称(basename "$(pwd)"),并按 Docker 的命名规则进行清理(转为小写,将无效字符替换为 -)——在总结中向用户说明解析出的名称;不要悄悄自行决定。还要单独询问一个是/否问题:“是否使用 git init 初始化此工作区?”将答案记录为 {{LOCAL_CHECKOUT_GIT_INIT}}("true"/"false")。如果回答是,则从主机的 git config --global init.defaultBranch 解析 {{GIT_DEFAULT_BRANCH}}——如果主机未配置此项,则替换为空字符串;始终要用某个值替换此占位符,即使是 "" 也可以。

完成条件:已获取 {{REPO_SLUG}}(可能为空)和 {{REPO_NAME}};此外,如果是本地检出,还需获取 {{LOCAL_CHECKOUT_GIT_INIT}};若答案为是,还需获取 {{GIT_DEFAULT_BRANCH}}。

2. 检查现有设置

test -f .devcontainer/devcontainer.json && echo "devcontainer already exists"
  • 已存在:此仓库已有之前运行时创建的共享容器。无需重新生成——CLI 安装完全由 setup-<tool>-devcontainer 技能处理;在现有开发容器中添加 SSH,请参阅稍后为工具添加 SSH。除非用户明确要求重新配置基础开发容器本身,否则到此为止。 Exception:如果 devcontainer.json 设置了 workspaceFolder,却没有对应的 workspaceMount,说明它早于下面的修复,且会导致仓库完全无法挂载。请直接就地修补(在 workspaceFolder 旁添加 "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"),并检查是否有此前失败尝试留下的过期容器(见下文)——仅重启无法应用修复,因为挂载配置在创建容器时就已固定。

    Exception:如果 devcontainer.json 中没有 containerEnv.BASH_ENV 条目,说明它早于这项修复:该修复会让 GH_TOKEN(以及 .devcontainer/.env 中的其余内容)对 AI CLI 自身发起的工具调用可见——如果没有这项配置,GH_TOKEN 只会传递给三个生命周期脚本(postCreate/postStart/postAttach),永远不会传递给 Agent 自己运行的 gh 命令,即使刚添加了令牌并重建了容器也是如此。请直接就地修补:添加 .devcontainer/bash-env.sh(复制自 templates/bash-env.sh,并执行 chmod +x),在 devcontainer.json 中添加 "containerEnv": {"BASH_ENV": "/workspace/.devcontainer/bash-env.sh"},并将 templates/post-create-base.sh 中的 devcontainer-env-load 代码块添加到 .devcontainer/post-create.sh(如果其中尚无该代码块)。需要执行 Dev Containers: Rebuild Container 才能生效。

    Warning,而非 exception(这里不会自动修补这一项):如果 devcontainer.json 中没有 build.target 字段,说明它早于 Baseline Containment 加固(始终启用 --cap-drop=ALL,并使用小型 --cap-add 允许列表;可选择启用网络防火墙;使用固定摘要的多阶段 Dockerfile)——整个容器仍会使用 Docker 默认的完整 capability 集和未固定版本的单阶段镜像,无论上述 setup-<tool>-devcontainer 技能或“后续”文档对其做了什么。此项没有自动回填机制(这是有意的选择,并非疏忽——原因请参阅该技能自身的 git 历史记录)。请明确告知用户:要真正启用加固,必须删除 .devcontainer/ 并从头重新生成,然后重新运行所有 setup-<tool>-devcontainer 技能,并再次回答 SSH/防火墙相关问题。

  • 不存在:全新设置,继续执行第 3 步。

还要检查是否有此工作区文件夹之前其他设置遗留的容器——Dev Containers CLI 会根据 devcontainer.local_folder=<absolute workspace path> 为容器添加标签,与当前配置无关,因此即使旧配置已删除或从未提交,过期容器也可能残留:

docker ps -a --filter "label=devcontainer.local_folder=$(pwd)" --format '{{.ID}} {{.Image}}'

如果有任何返回结果,请在用户重新打开前发出警告:使用不同设置构建的容器不会包含此设置所需的 vscode 用户,重新打开时会失败,并显示难以理解的错误 unable to find user vscode: no matching entries in passwd file。可以提议移除它(docker rm -f <id>),但未经询问,不要移除。如果未安装 docker 或其未运行,则完全跳过此检查。

同样的情况也适用于在上述 workspaceMount 修复之前构建的容器:重新打开时会失败,显示 chdir to cwd ("/workspace") set in config.json failed: no such file or directory,随后显示 postCreateCommand ... failed with exit code 127(由于 shell 无法进入 /workspace,脚本甚至无法运行)。容器已成功构建,因此直到重新打开时才会发现问题。解决方法相同——先征得用户确认,再用 docker rm -f <id> 移除,因为挂载配置在创建时就已固定,重启不会更新。

3. 询问 SSH 和防火墙层

如果 {{REPO_SLUG}}(第 1 步)为空,请完全跳过 SSH 问题——本地检出尚未关联 GitHub 仓库,无法为其注册部署密钥/签名密钥;请向用户说明原因:“SSH 层目前不可用——此仓库尚未连接到 GitHub;连接后再添加(参见‘将本地检出连接到真实的 GitHub 仓库’)。”

否则,询问:此仓库是否需要由 Agent 驱动 git push 并签署提交?(添加部署密钥/签名密钥自动化——整个容器共用一对密钥。)以后可以添加,无需重新生成已生成的任何内容(参见稍后为工具添加 SSH)。

然后,无论 SSH 问题的答案是什么,也无论 {{REPO_SLUG}} 是否为空(限制出站访问对本地检出和已连接 GitHub 的仓库同样有益),都要询问:此仓库是否希望将此容器的出站网络限制为允许列表?(默认拒绝的 iptables+ipset 防火墙——通过网络清单自动允许每个已安装 CLI 技能所需的主机,以及 npm/GitHub;其他所有访问均会被阻止。权衡之处:如果某个 AI CLI 或其调用的工具需要访问其他主机(例如软件包注册表镜像或项目自己的 API),必须先将该主机添加到 .devcontainer/allowed-domains.local.txt,否则请求会被默认拒绝,而不是悄然成功。)推荐默认值:是,适用于运行带有 --dangerously-skip-permissions/YOLO 模式自动化功能的 AI CLI 的任何容器,因为这会限制行为异常或遭到入侵的进程能够访问的范围;现在不启用也没有任何损失。以后可以添加,无需重新生成已生成的任何内容(参见稍后添加防火墙层)。

4. 解析剩余占位符

  • {{REPO_SLUG}}、{{REPO_NAME}}、{{LOCAL_CHECKOUT_GIT_INIT}}、{{GIT_DEFAULT_BRANCH}} — 来自第 1 步。
  • {{GIT_EMAIL_DEFAULT}}、{{GIT_NAME_DEFAULT}} — 在主机上运行 git config --global user.email 和 git config --global user.name。如果其中任一项未设置,请勿自行编造默认值。

5. 生成 devcontainer

  • .devcontainer/Dockerfile ← templates/Dockerfile,原样复制(无占位符)——无论是否启用防火墙,始终使用这一个文件。它采用多阶段构建:base(纯净镜像)和 firewall(在 base 基础上扩展,加入防火墙脚本所需的 iptables/ipset/iproute2/dnsutils/aggregate 软件包,以及一条权限范围恰好限定为这两个脚本路径的 sudo 规则)。实际构建哪个阶段由下方 devcontainer.json 的 build.target 决定——而非复制了哪个文件——因此 Dockerfile 本身无需替换。

  • .devcontainer/bash-env.sh ← templates/bash-env.sh,原样复制(无占位符),并执行 chmod +x。它会加载 .devcontainer/.env;下方将其设为 BASH_ENV,这样容器中的每个非交互式 shell(包括 AI CLI 自己的工具调用,这些调用会运行 bash -c ...,且从不加载 ~/.bashrc)都能获取 GH_TOKEN 等环境变量,而不仅仅是三个生命周期脚本。

  • .devcontainer/network-manifest.json ← templates/network-manifest.json,原样复制(无占位符)。这是网络清单(见 CONTEXT.md):一个以 CLI 名称为键的对象,每个值都包含一个由 {host, purpose, source} 条目组成的 networkAllowlist 数组。此处预先填入每个容器都需要的通用基线条目,无论最终安装了哪些 CLI 技能——用于 npm/git/gh 的 registry.npmjs.org、github.com、api.github.com,以及 archive.ubuntu.com/ports.ubuntu.com,以便在会话中临时执行 sudo apt-get install <tool>(issue #291 story 19)时,即使未启用防火墙也能正常工作;若缺少后两个域名,apt-get update 会静默地无法刷新软件包索引,之后只能安装缓存中已有的软件包。这些条目归入 "baseline" 键,而不是虚构的 CLI 名称,因为它们并不归任何一个 CLI 所有。之后每个 CLI 技能都会添加自己的键值条目(这是独立事项,不在此处生成)。无论是否选择启用防火墙,都会无条件生成——之后安装的 CLI 技能始终会在此记录自己的条目,而防火墙(无论现在还是之后添加)都会自动读取,无需额外步骤。

  • .devcontainer/network-manifest-domains.sh ← templates/network-manifest-domains.sh,原样复制 (无占位符),并执行 chmod +x。它是网络清单的 jq 派生辅助脚本:给定清单文件,会输出所有键值条目中 networkAllowlist 的主机名,每行一个,且去重。此文件也会无条件生成;存在防火墙层时由 firewall-lib.sh(见下文)加载,否则保留但不使用。

  • .devcontainer/project-mounts.local.json ← templates/project-mounts.local.json,原样复制——一个空 JSON 数组。这是项目挂载接口(见 CONTEXT.md):一个由项目所有的文件,与下方的 allowed-domains.local.txt 并列,此后任何技能都不会再触碰它。无条件生成(挂载与 SSH/防火墙选项无关)。在此处添加本项目自己的 devcontainer.json mounts 条目(原始挂载字符串,例如 "source=my-project-data,target=/var/lib/data,type=volume",格式与 devcontainer.json 已包含的 mounts 条目相同),然后使用以下命令将它们合并到 devcontainer.json 中:

    scripts/patch-json-array-if-absent.sh .devcontainer/devcontainer.json .mounts .devcontainer/project-mounts.local.json
    

    每次编辑后都可以安全地重新运行——已存在的条目会保持不变。需要执行 Dev Containers: Rebuild Container 才能生效,因为 mounts 在创建容器时设置,不会在普通重启时重新读取。

  • 如果 .devcontainer/project-mounts.local.json 尚未被忽略,请将其添加到 .gitignore (与上文的 .devcontainer/.env 同理)。

  • .devcontainer/devcontainer.json ← templates/devcontainer.json (如果第 3 步对 SSH 的回答为“是”,则使用 templates/devcontainer.with-ssh.json),替换 {{REPO_NAME}}。runArgs 字段预先填入了无条件的 Baseline Containment capability-drop 条目(--cap-drop=ALL --cap-add=CHOWN --cap-add=DAC_OVERRIDE --cap-add=FOWNER --cap-add=SETUID --cap-add=SETGID——最后两项是 sudo 本身所需:它的二进制文件带有 setuid-root 权限,但它仍会在内部调用 setresuid()/setresgid() 以彻底切换身份,因此无论 setuid 位如何,这些能力都必须在 bounding set 中,否则每次调用 sudo 都会直接失败,包括防火墙自身的调用)——这些条目不受任何设置问题控制;这也是 Capability Seam,即唯一的扩展点,CLI 技能(目前只有 setup-codex-devcontainer)可以在其基础上幂等地追加自己的条目。containerEnv.BASH_ENV 指向上文的 bash-env.sh——这使 GH_TOKEN 对 AI CLI 自身的工具调用可见,而不只是对生命周期脚本可见。initializeCommand 通过 sh 调用生成的 .devcontainer/initialize.sh(见下文),而不是 bash——它在主机上运行,此时容器尚未创建;脚本本身不依赖 bash。mounts 初始仅包含共享配置卷;项目自己的条目来自 project-mounts.local.json(见下文),单独合并,绝不手动添加到此处。如果第 3 步对防火墙的回答为“是”,还需通过 Capability Seam 本身所基于的同一个 patch-json-array-if-absent.sh 基元,加入防火墙自身所需的能力授权(--cap-add=NET_ADMIN --cap-add=NET_RAW——iptables/ipset 需要这些能力)。不要使用 Capability Seam 扩展点本身,因为这是基础技能在写入自己拥有的 runArgs,而不是 CLI 技能在其基础上追加内容;应在生成时应用,而不是通过之后的 CLI 技能补丁应用,因为这取决于基础技能自身的设置问题,而非任何特定 CLI;并将 build.target 从 "base" 改为 "firewall",这样 Dockerfile 的防火墙阶段(见上文)才会实际构建:

    scripts/patch-json-array-if-absent.sh .devcontainer/devcontainer.json .runArgs templates/firewall-capability-entries.json
    jq '.build.target = "firewall"' .devcontainer/devcontainer.json > .devcontainer/devcontainer.json.tmp \
      && mv .devcontainer/devcontainer.json.tmp .devcontainer/devcontainer.json
    
  • .devcontainer/post-create.sh——由 scripts/render-devcontainer.sh 生成,该脚本会组装基础骨架(git 身份信息、Local Checkout 的 git-init、共享的 install_cli/chown_config_volume 函数定义,以及启用时的 SSH 区块对),并将生成文件的权限改为 +x:

    scripts/render-devcontainer.sh \
      --repo-name "{{REPO_NAME}}" --repo-slug "{{REPO_SLUG}}" \
      --out .devcontainer/post-create.sh \
      --initialize-out .devcontainer/initialize.sh \
      [--ssh] \
      [--git-email-default "<value>"] [--git-name-default "<value>"] \
      [--local-checkout-git-init "{{LOCAL_CHECKOUT_GIT_INIT}}"] [--git-default-branch "{{GIT_DEFAULT_BRANCH}}"]
    

    使用 scripts/verify-devcontainer.sh 验证,传入完全相同的标志(去掉 --out/--initialize-out,添加 --file/--initialize-file)。

    这个骨架是每个 CLI 技能之后追加自身安装区块的基础。SSH 区块不会导致构建失败——如果 GH_TOKEN 权限不足或缺失(或 DEVCONTAINER_HOST 未设置),则会跳过其余 SSH 设置,并在 ~/.ssh/.ssh-setup-skipped 中记录原因。 同一命令还会写入 .devcontainer/initialize.sh(并执行 chmod +x)——使用上面的同一个 --initialize-out 标志,无条件执行,不提供 --ssh/--firewall 变体。这是 initializeCommand 的目标(见上文的 devcontainer.json):将当前精确的 .env 引导 行为移植到可追加的脚本文件中,而非内联单行命令,这样需要自行添加主机端初始化步骤的项目(该步骤必须在容器 创建前运行,因此不能放在 post-create.sh 中)就可以像为 post-create.sh/post-start.sh 添加内容那样, 自行追加一个 项目所有者区块:

    scripts/patch-if-absent.sh append .devcontainer/initialize.sh "# --- Project: <name> ---" <block-file>
    
  • .devcontainer/post-start.sh ← templates/post-start-base.sh, 原样复制,并执行 chmod +x。空骨架——如果用户选择为某个 CLI 工具启用同步,各 CLI 技能会在此追加自己的技能同步区块; 此外(见下文),如果第 3 步中用户对防火墙的回答为是,基础技能会在此追加防火墙调用。

  • .devcontainer/post-attach.sh ← templates/post-attach.sh, 替换内容,并执行 chmod +x。

  • .devcontainer/.env.example ← templates/env.baseline.example,替换内容。

  • .devcontainer/README.md ← templates/README.baseline.md,替换内容。其 ## Installed CLI Tools 标题是文件的最后一个章节,且有意留空——每个 CLI 技能都只需将自己的条目直接追加到文件末尾, 无需查找插入位置。

  • 如果 .devcontainer/.env 尚未加入忽略列表,则将其添加到 .gitignore。

如果第 3 步中用户对 SSH 的回答为是:

  • 将 templates/env.ssh-block.example 幂等地追加到 .devcontainer/.env.example,并将其 GH_TOKEN 注释改为:Deploy-key registration is manual by default (see the SSH setup prompt after first attach) and needs no extra permission. Optional: grant this repo's Administration (read/write) permission too if you'd rather the deploy key auto-register itself — plus whatever else you use gh for.

    scripts/patch-if-absent.sh append .devcontainer/.env.example "# Your host machine's hostname — used to label the SSH deploy/signing keys so" templates/env.ssh-block.example
    
  • 将 templates/README.ssh-block.md 的内容 替换({{REPO_NAME}}、{{REPO_SLUG}})到临时副本中,再写入 .devcontainer/README.md——与上面所有其他需要替换内容的模板不同, 此模板还会作为另一个命令的输入,而不是直接写入的文件,因此应在修补之前完成替换,而不是之后——将其插入到 ## Installed CLI Tools 之前,而非追加到文件末尾——这样无论何时添加 SSH,该章节始终是文件的最后一个章节——并删除基线模板末尾的 “SSH deploy key and signing key automation — Not set up here”章节(由实际章节取代):

    scripts/patch-if-absent.sh insert-before .devcontainer/README.md "## SSH deploy key and signing key" "## Installed CLI Tools" <substituted-copy-of-templates/README.ssh-block.md>
    scripts/patch-if-absent.sh delete-section .devcontainer/README.md "## SSH deploy key and signing key automation"
    

如果第 3 步中用户对防火墙的回答为是:

  • .devcontainer/init-firewall.sh ← templates/init-firewall.sh,原样复制(不含占位符),并执行 chmod +x。.devcontainer/refresh-allowlist.sh ← templates/refresh-allowlist.sh,原样复制,并执行 chmod +x。 .devcontainer/firewall-lib.sh ← templates/firewall-lib.sh,原样复制, 并执行 chmod +x——由上面的两个脚本加载(而非直接执行),是两者构建域名列表的唯一来源 (通过 network-manifest-domains.sh 获取网络清单,再加上下面的 allowed-domains.local.txt), 因此不会像两份手动复制的列表那样各自出现偏差。

  • .devcontainer/allowed-domains.local.txt ← templates/allowed-domains.local.txt,原样复制——文件为空, 只有说明性头部注释。此仓库特有的项目网络主机名应添加到这里;之后任何技能都不会再改动此文件。

  • 如果 .devcontainer/allowed-domains.local.txt 尚未加入 .gitignore,请将其加入 (与上面的 .devcontainer/.env 一样)。

  • 将 templates/post-start-firewall-block.sh 以幂等方式追加到 .devcontainer/post-start.sh:

    scripts/patch-if-absent.sh append .devcontainer/post-start.sh "# --- Network egress firewall ---" templates/post-start-firewall-block.sh
    
  • 将 templates/README.firewall-block.md 插入 .devcontainer/README.md 的 ## Installed CLI Tools 之前,并删除基线模板末尾的 “Network egress firewall automation — Not set up here”部分(已由实际章节取代):

    scripts/patch-if-absent.sh insert-before .devcontainer/README.md "## Network egress firewall" "## Installed CLI Tools" templates/README.firewall-block.md
    scripts/patch-if-absent.sh delete-section .devcontainer/README.md "## Network egress firewall automation"
    

完成条件:以上所有文件均存在;devcontainer.json 可解析为有效 JSON,且 runArgs 包含且仅包含基线能力移除项;如果第 3 步的防火墙问题回答为是,还需包含 --cap-add=NET_ADMIN 和 --cap-add=NET_RAW (拒绝时使用 jq -e '.runArgs | sort == ["--cap-add=CHOWN","--cap-add=DAC_OVERRIDE","--cap-add=FOWNER","--cap-add=SETGID","--cap-add=SETUID","--cap-drop=ALL"]' .devcontainer/devcontainer.json 进行验证,接受时使用 ... sort == ["--cap-add=CHOWN","--cap-add=DAC_OVERRIDE","--cap-add=FOWNER","--cap-add=NET_ADMIN","--cap-add=NET_RAW","--cap-add=SETGID","--cap-add=SETUID","--cap-drop=ALL"]' .devcontainer/devcontainer.json 进行验证);拒绝时 build.target 为 "base",接受时为 "firewall" (使用 jq -e '.build.target == "base"' .devcontainer/devcontainer.json 或 ... == "firewall"' 进行验证);所有写入的文件中均不含 {{...}} 占位符 (使用 grep -rn '{{' .devcontainer/ 检查);并且 post-create.sh/initialize.sh 通过 scripts/verify-devcontainer.sh 的验证,且使用其生成时的确切标志 (--file/--initialize-file,并添加 --project-mounts-file .devcontainer/project-mounts.local.json,以确认其仍与模板逐字一致)。

6. 报告后续步骤

根据是否有 SSH/防火墙,向用户说明:

  1. 安装 Docker Desktop 和 VS Code 扩展 Dev Containers。
  2. 将 .devcontainer/.env.example 复制为 .devcontainer/.env,并填写 GH_TOKEN{{,如果有 SSH,还要填写 DEVCONTAINER_HOST(运行 hostname)}}。{{如果是对现有容器重新运行:如果 .devcontainer/.env 仍包含 github_pat_your_token_here 或 your-hostname-here,请替换为真实值或清除它们——较旧版本提供的 .env.example 将这些占位符作为实际值,其中的内容会作为真实凭据导出。}}
  3. 在容器中重新打开仓库(Dev Containers: Reopen in Container)。
  4. 运行所需的 setup-<tool>-devcontainer 技能,以添加 AI CLI。
  5. {{如果有 SSH:连接时,post-attach.sh 会打印尚未注册的部署密钥和签名密钥的设置说明(如果 GH_TOKEN 具有 Administration 权限,部署密钥可能已设置)——按说明操作,并使用给出的 touch 命令逐一关闭提示。}}
  6. {{如果有防火墙:此容器的出站网络现在默认拒绝所有连接,仅允许网络清单中的许可列表。请在需要之前,将本项目自己的主机添加到 .devcontainer/allowed-domains.local.txt——按照设计,未列于该文件、网络清单或 GitHub/npm 自身网段中的主机均无法访问。}}
  7. 如果本项目需要自己的 devcontainer.json 挂载项(数据库数据目录、持久化历史记录卷等),请将其添加到 .devcontainer/project-mounts.local.json,然后使用 scripts/patch-json-array-if-absent.sh .devcontainer/devcontainer.json .mounts .devcontainer/project-mounts.local.json 合并,再重新构建。如果需要自己的主机端初始化步骤,请像向 post-create.sh/post-start.sh 追加内容那样,将其追加到 .devcontainer/initialize.sh。如果需要容器内的后端服务(例如 PostgreSQL),请参阅添加后端服务。

稍后为工具添加 SSH

适用于已有 devcontainer、现在需要由 Agent 驱动 git push/签名提交的仓库。请参阅 docs/adding-ssh-later.md。

稍后添加防火墙层

适用于已有 devcontainer、现在需要启用可选网络出口防火墙的仓库。请参阅 docs/adding-firewall-later.md。

添加后端服务

适用于需要在共享容器内运行后端服务(例如 PostgreSQL),且不重新引入 Docker Compose 的项目。请参阅 docs/adding-a-backing-service.md。

将本地检出连接到真实的 GitHub 仓库

适用于作为本地检出(步骤 1)生成、现在已有真实 GitHub 仓库可供推送的仓库。无需重新生成技能级内容——请参阅 docs/connecting-local-checkout.md。

评论

支持 Markdown