Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"biomejs.biome"
],
"settings": {
"terminal.integrated.defaultProfile.linux": "zsh",
"terminal.integrated.defaultProfile.linux": "bash",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
Expand Down
62 changes: 62 additions & 0 deletions .github/workflows/image-build-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# PR validation for the Prebuilt Image definition. NOT part of the release mechanism
# (that is build-image.yml, contract: docs/adr/0001-image-release-contract.md).
# This never pushes: it only proves `devcontainer build` succeeds and the image
# satisfies the agent-first baseline (ADR 0002) before a change can merge.
name: Image Build Check

on:
pull_request:
paths:
- 'devimage-build/**'
- '.github/workflows/image-build-check.yml'

jobs:
build-check:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Build image (single-arch, no push)
run: |
npm install -g @devcontainers/cli
devcontainer build \
--workspace-folder ./devimage-build \
--image-name image-build-check:pr

- name: Verify agent-first baseline (ADR 0002)
run: |
docker run --rm --user vscode image-build-check:pr bash -c '
set -e

echo "--- 必备工具(agent 与 Human Supervisor 双方所需)---"
for tool in bash git jq rg fd node npm python3 cargo gh; do
command -v "$tool" >/dev/null || { echo "::error::missing tool: $tool"; exit 1; }
echo "ok: $tool"
done

echo "--- 工效层不得存在(zsh / oh-my-zsh / tmux / 装饰性 CLI)---"
# htop / tree 不在此列:它们由 common-utils Feature 作为基础包安装,
# 且是 Human Supervisor debug 实际会用的工具(ADR 0002 的 keep 侧)。
for tool in zsh tmux bat batcat eza xclip; do
if command -v "$tool" >/dev/null; then echo "::error::ergonomics layer leaked in: $tool"; exit 1; fi
echo "absent: $tool"
done
[ ! -e "$HOME/.oh-my-zsh" ] || { echo "::error::oh-my-zsh present"; exit 1; }

echo "--- 默认 shell 与用户 ---"
[ "$(getent passwd vscode | cut -d: -f7)" = "/bin/bash" ] || { echo "::error::vscode login shell is not /bin/bash"; exit 1; }
sudo -n true 2>/dev/null || { echo "::error::vscode has no passwordless sudo"; exit 1; }

echo "--- dev-cache 符号链接 ---"
for link in "$HOME/.npm" "$HOME/.local/share/pnpm" "$HOME/.cache/pip" "$HOME/.cache/pypoetry" "$HOME/.vscode-server/extensions"; do
[ -L "$link" ] || { echo "::error::missing cache symlink: $link"; exit 1; }
echo "ok: $link -> $(readlink "$link")"
done

echo "--- git 全局配置写在 vscode 用户下 ---"
[ "$(git config --global core.autocrlf)" = "input" ] || { echo "::error::git core.autocrlf not configured for vscode user"; exit 1; }

echo "✅ agent-first baseline holds"
'
18 changes: 18 additions & 0 deletions agent-compose/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 复制为 .env 后按需修改;全部变量都有默认值,不建 .env 也能直接用。

# 要让 agent 操作的项目目录(宿主机路径;默认挂本仓库根目录)
# WORKSPACE_DIR=F:/zsp/Projects/my-app

# 镜像来源(fork 者改成自己的 registry;与 .devcontainer/.env 语义一致)
# DOCKERHUB_USERNAME=xiao806852034
# IMAGE_NAME=ai-dev-container

# pin 到具体版本(默认 latest;可复现场景设为 vX.Y.Z)
# IMAGE_TAG=v2.0.0

# Agent Home volume(默认与 devcontainer 共享 dev-home,登录一次两边可用)
# WSL_HOME=dev-home

# 通过环境变量传 API key 时,先在 compose.yaml 里取消 environment 段注释
# ANTHROPIC_API_KEY=
# OPENAI_API_KEY=
57 changes: 57 additions & 0 deletions agent-compose/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# agent-compose —— 不用 devcontainer 跑 coding agent

用纯 Docker Compose 在 Prebuilt Image 里运行 claude / codex / gemini 这类 coding agent,**不依赖 VS Code devcontainer**。适合:

- 只想在终端里跑 agent,不开 VS Code
- 在服务器 / CI 等没有 devcontainer 工具链的环境复用同一套镜像
- 同时给多个项目各起一个 agent 容器

复用与 `.devcontainer/` 相同的镜像和同名 volume(`dev-home` / `dev-cache`),因此 **agent 登录态与缓存两边共享**——在 devcontainer 里登录过的 claude,这里直接可用,反之亦然。

## 使用

```bash
cd agent-compose

# (可选)配置目标项目目录等
cp .env.example .env

docker compose up -d # 启动并完成 Startup Install
docker compose exec agent claude # 或 codex / gemini / bash
docker compose down # 用完关掉
```

首次 `up` 时引导脚本(`bootstrap.sh`)会安装缺失的 agent CLI(Startup Install,镜像刻意不烘焙,见 [ADR 0001](../docs/adr/0001-image-release-contract.md));agent 配置目录(`~/.claude` 等)链接到 `dev-home` volume,重建容器不丢登录态。

## 换一个项目

编辑 `.env` 的 `WORKSPACE_DIR` 指向目标项目,然后 `docker compose up -d --force-recreate`。

要同时跑多个项目,用 `-p` 起独立实例:

```bash
WORKSPACE_DIR=/path/to/app docker compose -p agent-app up -d
docker compose -p agent-app exec agent claude
```

## 更新镜像

这里没有 devcontainer 的 `initializeCommand` 自动拉取(那是给 VS Code rebuild 用的,见 [ADR 0001](../docs/adr/0001-image-release-contract.md)),手动拉:

```bash
docker compose pull && docker compose up -d --force-recreate
```

pin 版本:`.env` 里设 `IMAGE_TAG=vX.Y.Z`。

## 与 `.devcontainer/` 的关系

| | `.devcontainer/` | `agent-compose/` |
|---|---|---|
| 入口 | VS Code Reopen in Container | `docker compose up -d` |
| 镜像 | 同一个 Prebuilt Image | 同一个 Prebuilt Image |
| Agent 登录态 | `dev-home` volume | 同一个 `dev-home` volume |
| 缓存 | `dev-cache` volume | 同一个 `dev-cache` volume |
| Startup Install | `post-create.sh`(含 HAPI Local Hub) | `bootstrap.sh`(仅 agent CLI) |

> HAPI Local Hub 属于 devcontainer 工作流,本目录不拉起;需要时进容器手动 `npm i -g @twsxtd/hapi` 使用。
43 changes: 43 additions & 0 deletions agent-compose/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
# ============================================
# agent-compose 容器引导脚本
# ============================================
# 由 compose.yaml 作为容器 command 运行(对应 devcontainer 流程的 post-create.sh):
# 1. 链接 Agent Home(dev-home volume)里的 agent 配置到 ~
# 2. Startup Install:安装缺失的 coding agent CLI(镜像刻意不烘焙,见 ADR 0001)
# 3. sleep infinity 常驻,等待 `docker compose exec agent <cli>` 进入
set -e

AGENT_HOME=/home/vscode/wsl-home

# ============================================
# 链接 AI agent 配置(与 devcontainer 共享同一 dev-home volume)
# ============================================
for dir in .claude .codex .gemini; do
mkdir -p "$AGENT_HOME/$dir"
ln -sfn "$AGENT_HOME/$dir" "$HOME/$dir"
echo "📦 Linked $dir -> Agent Home"
done

# ============================================
# 安装 AI Agents(Startup Install,幂等)
# ============================================
if ! command -v claude &> /dev/null; then
echo "📦 Installing Claude Code..."
npm install -g @anthropic-ai/claude-code
fi

if ! command -v codex &> /dev/null; then
echo "📦 Installing OpenAI Codex..."
npm install -g @openai/codex
fi

if ! command -v gemini &> /dev/null; then
echo "📦 Installing Gemini CLI..."
npm install -g @google/gemini-cli
fi

echo "✅ Agent 环境就绪。进入方式:docker compose exec agent claude(或 codex / gemini / bash)"

# 常驻(PID 1 由 compose 的 init: true 托管,可正常响应 stop 信号)
exec sleep infinity
49 changes: 49 additions & 0 deletions agent-compose/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 纯 Docker Compose 的 coding agent 运行环境 —— 不依赖 VS Code devcontainer。
# 复用同一个 Prebuilt Image 与同名 volume(dev-home / dev-cache),
# 因此 agent 登录态、包管理器缓存与 devcontainer 完全共享。
# 发布契约见 docs/adr/0001-image-release-contract.md(默认消费 :latest)。
services:
agent:
# 与 .devcontainer/compose.yaml 相同的参数化引用;fork 者在本目录 .env 覆盖。
image: ${DOCKERHUB_USERNAME:-xiao806852034}/${IMAGE_NAME:-ai-dev-container}:${IMAGE_TAG:-latest}

volumes:
# 要让 agent 操作的项目目录;默认挂本仓库根,改 .env 的 WORKSPACE_DIR 指向任意项目
- ${WORKSPACE_DIR:-..}:/home/vscode/workspace

# Agent Home(.claude / .codex / .gemini 登录态与配置),与 devcontainer 共享
- ${WSL_HOME:-dev-home}:/home/vscode/wsl-home:cached

# 统一缓存目录(npm / pnpm / pip / poetry),与 devcontainer 共享
- dev-cache:/home/vscode/.cache-volumes

# 容器内引导脚本:链接 Agent Home、Startup Install agent CLI,然后常驻
- ./bootstrap.sh:/usr/local/bin/agent-bootstrap:ro

working_dir: /home/vscode/workspace

# 引导完成后 sleep infinity 常驻;用 `docker compose exec agent claude` 进入
command: bash /usr/local/bin/agent-bootstrap

init: true

# 需要通过环境变量传 API key 时在此打开(值来自本目录 .env):
# environment:
# ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
# OPENAI_API_KEY: ${OPENAI_API_KEY:-}

# 资源限制(与 devcontainer 一致)
deploy:
resources:
limits:
memory: 8G
reservations:
memory: 4G

volumes:
# AI agent 配置存储(与 .devcontainer/compose.yaml 同名 → 同一个 volume)
dev-home:
name: dev-home
# 统一缓存存储
dev-cache:
name: dev-cache
55 changes: 24 additions & 31 deletions devimage-build/.devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,55 +1,48 @@
FROM mcr.microsoft.com/devcontainers/base:ubuntu
# 裸 Debian 基线(ADR 0002 agent-first):不用 devcontainers/base:*——那一族预烘焙了
# common-utils 默认配置,自带 zsh / oh-my-zsh / htop / tree 等交互工效层。
# devcontainer 必需品(sudo、用户配置等)由 common-utils Feature 在此镜像之上补齐。
FROM debian:trixie-slim

# 避免交互式提示
ENV DEBIAN_FRONTEND=noninteractive

# 安装系统依赖(Features 未覆盖的工具)
# 安装系统依赖:agent 与 Human Supervisor 双方都需要的工具,
# 以及 Features 安装脚本运行所需的基础组件(ca-certificates / curl)
# fd-find 的二进制名为 fdfind;符号链接为 fd,便于 agent 与 Human Supervisor 直接调用
RUN apt-get update && apt-get install -y \
ca-certificates \
curl \
git \
jq \
ripgrep \
fd-find \
bat \
eza \
htop \
tree \
tmux \
xclip \
&& rm -rf /var/lib/apt/lists/*
&& rm -rf /var/lib/apt/lists/* \
&& ln -sf "$(command -v fdfind)" /usr/local/bin/fd

# 配置 Git(避免 Windows 换行符问题)
RUN git config --global core.autocrlf input \
&& git config --global init.defaultBranch main \
&& git config --global pull.rebase false
# 裸基础镜像没有非 root 用户;先建 vscode(uid/gid 1000),
# common-utils Feature 检测到同名用户已存在时会原样采用并补上 sudo 配置
RUN groupadd --gid 1000 vscode \
&& useradd --uid 1000 --gid 1000 --create-home --shell /bin/bash vscode

# 切换到 vscode 用户
USER vscode
WORKDIR /home/vscode

# 创建缓存目录和符号链接
# 配置 Git(避免 Windows 换行符问题);须在 USER vscode 之后执行,
# 否则写入的是 /root/.gitconfig,对实际运行用户无效
RUN git config --global core.autocrlf input \
&& git config --global init.defaultBranch main \
&& git config --global pull.rebase false

# 创建缓存目录和符号链接(包管理器缓存持久化到 dev-cache volume)
RUN mkdir -p ~/.cache-volumes/npm \
&& mkdir -p ~/.cache-volumes/pnpm \
&& mkdir -p ~/.cache-volumes/pip \
&& mkdir -p ~/.cache-volumes/poetry \
&& mkdir -p ~/.cache-volumes/vscode-extensions \
&& mkdir -p ~/.cache-volumes/zsh \
&& mkdir -p ~/.cache-volumes/tmux/plugins \
&& mkdir -p ~/.local/share ~/.cache ~/.vscode-server \
&& ln -sf ~/.cache-volumes/npm ~/.npm \
&& ln -sf ~/.cache-volumes/pnpm ~/.local/share/pnpm \
&& ln -sf ~/.cache-volumes/pip ~/.cache/pip \
&& ln -sf ~/.cache-volumes/poetry ~/.cache/pypoetry \
&& ln -sf ~/.cache-volumes/vscode-extensions ~/.vscode-server/extensions \
&& ln -sf ~/.cache-volumes/zsh ~/.zsh \
&& ln -sf ~/.cache-volumes/tmux ~/.tmux

# 配置 tmux(持久化到 dev-cache)
RUN git clone https://github.com/tmux-plugins/tpm ~/.cache-volumes/tmux/plugins/tpm
COPY --chown=vscode:vscode configs/tmux.conf /home/vscode/.cache-volumes/tmux/.tmux.conf
RUN ln -sf ~/.cache-volumes/tmux/.tmux.conf ~/.tmux.conf

# 配置 zsh(持久化到 dev-cache)
RUN git clone https://github.com/zsh-users/zsh-autosuggestions ~/.cache-volumes/zsh/zsh-autosuggestions \
&& git clone https://github.com/zsh-users/zsh-syntax-highlighting ~/.cache-volumes/zsh/zsh-syntax-highlighting
COPY --chown=vscode:vscode configs/zshrc-addon /tmp/zshrc-addon
RUN cat /tmp/zshrc-addon >> ~/.zshrc \
&& rm /tmp/zshrc-addon
&& ln -sf ~/.cache-volumes/vscode-extensions ~/.vscode-server/extensions
20 changes: 0 additions & 20 deletions devimage-build/.devcontainer/configs/tmux.conf

This file was deleted.

18 changes: 0 additions & 18 deletions devimage-build/.devcontainer/configs/zshrc-addon

This file was deleted.

8 changes: 4 additions & 4 deletions devimage-build/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": true,
"configureZshAsDefaultShell": true,
"installOhMyZsh": true,
"upgradePackages": true
"installZsh": false,
"installOhMyZsh": false,
"upgradePackages": true,
"username": "vscode"
},
"ghcr.io/devcontainers/features/node:2.0.0": {
"version": "lts"
Expand Down
Loading
Loading