# CodeBuddy Code 开发容器

CodeBuddy Code 开发容器提供了一个预配置、安全的开发环境，适合需要一致性和隔离性工作空间的团队使用。它与 Visual Studio Code 的 Dev Containers 扩展以及类似工具无缝集成。


## 四步快速开始

1. **安装 VS Code 和 Remote - Containers 扩展**
2. **参考下方配置详解在工作区创建 `.devcontainer` 目录及相关文件**
3. **在 VS Code 中打开仓库**
4. **当提示时，点击 "Reopen in Container"**（或使用命令面板：Cmd+Shift+P → "Remote-Containers: Reopen in Container"）

---

## 配置详解

**目录结构**

```
your-project/
├── .devcontainer/
│   ├── devcontainer.json
│   ├── Dockerfile
│   └── init-firewall.sh
└── ...
```

开发容器设置由**三个主要组件**构成：

---

### 🚀 安装 CodeBuddy Code（推荐方案）

#### 方案 1：使用 Dev Containers Feature（推荐）

**Dev Containers Feature** 是在 Dev Container 中安装 CodeBuddy Code 的**推荐方式**。它提供自动版本管理、与其他特性配置一致、简化维护等优势。

**优势：**
- ✅ **自动版本管理** - 轻松升级到最新版本或固定特定版本
- ✅ **配置一致性** - 与其他 Dev Containers 特性采用相同配置方式
- ✅ **简化维护** - 无需在 Dockerfile 中管理复杂的安装逻辑
- ✅ **团队共享** - 易于在团队中统一配置

**配置方法：**

在 `devcontainer.json` 中的 `features` 字段添加配置：

```json
{
    "name": "CodeBuddy Code Sandbox",
    "features": {
        "ghcr.io/devcontainers-contrib/features/codebuddy-code:1": {
            "version": "latest"
        }
    },
    // ... 其他配置
}
```

**固定特定版本：**

```json
{
    "features": {
        "ghcr.io/devcontainers-contrib/features/codebuddy-code:1": {
            "version": "2.16.0"
        }
    }
}
```

**使用默认最新版本：**

```json
{
    "features": {
        "ghcr.io/devcontainers-contrib/features/codebuddy-code:1": {}
    }
}
```

#### 方案 2：在 Dockerfile 中手动安装

如果需要更多控制或特殊场景，可在 Dockerfile 中手动安装。详见下方 **Dockerfile** 部分。

---

### 1. devcontainer.json
控制容器设置、管理扩展、配置卷挂载。

**包含 Dev Containers Feature 的完整示例：**

```json
{
    "name": "CodeBuddy Code Sandbox",
    "build": {
        "dockerfile": "Dockerfile",
        "args": {
            "TZ": "${localEnv:TZ:America/Los_Angeles}",
            "GIT_DELTA_VERSION": "0.18.2",
            "ZSH_IN_DOCKER_VERSION": "1.2.0"
        }
    },
    "features": {
        "ghcr.io/devcontainers-contrib/features/codebuddy-code:1": {
            "version": "latest"
        }
    },
    "runArgs": [
        "--cap-add=NET_ADMIN",
        "--cap-add=NET_RAW"
    ],
    "customizations": {
        "vscode": {
            "extensions": [
                "dbaeumer.vscode-eslint",
                "esbenp.prettier-vscode",
                "eamodio.gitlens"
            ],
            "settings": {
                "editor.formatOnSave": true,
                "editor.defaultFormatter": "esbenp.prettier-vscode",
                "editor.codeActionsOnSave": {
                    "source.fixAll.eslint": "explicit"
                },
                "terminal.integrated.defaultProfile.linux": "zsh",
                "terminal.integrated.profiles.linux": {
                    "bash": {
                        "path": "bash",
                        "icon": "terminal-bash"
                    },
                    "zsh": {
                        "path": "zsh"
                    }
                }
            }
        }
    },
    "remoteUser": "node",
    "mounts": [
        "source=codebuddy-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
        "source=codebuddy-code-config-${devcontainerId},target=/home/node/.codebuddy,type=volume"
    ],
    "containerEnv": {
        "NODE_OPTIONS": "--max-old-space-size=4096",
        "CODEBUDDY_CONFIG_DIR": "/home/node/.codebuddy",
        "POWERLEVEL9K_DISABLE_GITSTATUS": "true"
    },
    "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
    "workspaceFolder": "/workspace",
    "postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
    "waitFor": "postStartCommand"
}
```

**关键配置说明：**
- `features` - 声明使用 CodeBuddy Code Dev Containers Feature，支持版本管理
- `version: "latest"` - 使用最新版本（可替换为具体版本号如 "2.16.0"）
- 注意：使用 Feature 方式时，Dockerfile 中无需 `CODEBUDDY_CODE_VERSION` 参数

### 2. Dockerfile
定义容器镜像、指定安装的工具。

#### 使用 Feature 时的简化 Dockerfile

如使用上方 Dev Containers Feature 安装 CodeBuddy Code，Dockerfile 可以更简洁（无需 `CODEBUDDY_CODE_VERSION` 参数和手动安装命令）：

> 如果你选择使用 Dev Containers Feature 方式（推荐），可以参考下方"简化版 Dockerfile"。如需在 Dockerfile 中手动安装，可参考"完整版 Dockerfile"。

**简化版 Dockerfile（推荐配合 Feature 使用）：**

```dockerfile
FROM node:20

ARG TZ
ENV TZ="$TZ"

# Install basic development tools and iptables/ipset
RUN apt-get update && apt-get install -y --no-install-recommends \
  less \
  git \
  procps \
  sudo \
  fzf \
  zsh \
  man-db \
  unzip \
  gnupg2 \
  gh \
  iptables \
  ipset \
  iproute2 \
  dnsutils \
  aggregate \
  jq \
  nano \
  vim \
  && apt-get clean && rm -rf /var/lib/apt/lists/*

# Ensure default node user has access to /usr/local/share
RUN mkdir -p /usr/local/share/npm-global && \
  chown -R node:node /usr/local/share

ARG USERNAME=node

# Persist bash history.
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
  && mkdir /commandhistory \
  && touch /commandhistory/.bash_history \
  && chown -R $USERNAME /commandhistory

# Set `DEVCONTAINER` environment variable to help with orientation
ENV DEVCONTAINER=true

# Create workspace and config directories and set permissions
RUN mkdir -p /workspace /home/node/.codebuddy && \
  chown -R node:node /workspace /home/node/.codebuddy

WORKDIR /workspace

ARG GIT_DELTA_VERSION=0.18.2
RUN ARCH=$(dpkg --print-architecture) && \
  wget "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"

# Set up non-root user
USER node

# Install global packages
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
ENV PATH=$PATH:/usr/local/share/npm-global/bin

# Set the default shell to zsh rather than sh
ENV SHELL=/bin/zsh

# Set the default editor and visual
ENV EDITOR=nano
ENV VISUAL=nano

# Default powerline10k theme
ARG ZSH_IN_DOCKER_VERSION=1.2.0
RUN sh -c "$(wget -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 /usr/share/doc/fzf/examples/key-bindings.zsh" \
  -a "source /usr/share/doc/fzf/examples/completion.zsh" \
  -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
  -x

# CodeBuddy Code will be installed via Dev Containers Feature

# Copy and set up firewall script
COPY init-firewall.sh /usr/local/bin/
USER root
RUN chmod +x /usr/local/bin/init-firewall.sh && \
  echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall && \
  chmod 0440 /etc/sudoers.d/node-firewall
USER node
```

#### 手动安装方式的完整 Dockerfile

如需完全控制安装过程，可在 Dockerfile 中手动安装 CodeBuddy Code（此方案不使用 Dev Containers Feature）：

```dockerfile
FROM node:20

ARG TZ
ENV TZ="$TZ"

ARG CODEBUDDY_CODE_VERSION=latest

# Install basic development tools and iptables/ipset
RUN apt-get update && apt-get install -y --no-install-recommends \
  less \
  git \
  procps \
  sudo \
  fzf \
  zsh \
  man-db \
  unzip \
  gnupg2 \
  gh \
  iptables \
  ipset \
  iproute2 \
  dnsutils \
  aggregate \
  jq \
  nano \
  vim \
  && apt-get clean && rm -rf /var/lib/apt/lists/*

# Ensure default node user has access to /usr/local/share
RUN mkdir -p /usr/local/share/npm-global && \
  chown -R node:node /usr/local/share

ARG USERNAME=node

# Persist bash history.
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
  && mkdir /commandhistory \
  && touch /commandhistory/.bash_history \
  && chown -R $USERNAME /commandhistory

# Set `DEVCONTAINER` environment variable to help with orientation
ENV DEVCONTAINER=true

# Create workspace and config directories and set permissions
RUN mkdir -p /workspace /home/node/.codebuddy && \
  chown -R node:node /workspace /home/node/.codebuddy

WORKDIR /workspace

ARG GIT_DELTA_VERSION=0.18.2
RUN ARCH=$(dpkg --print-architecture) && \
  wget "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"

# Set up non-root user
USER node

# Install global packages
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
ENV PATH=$PATH:/usr/local/share/npm-global/bin

# Set the default shell to zsh rather than sh
ENV SHELL=/bin/zsh

# Set the default editor and visual
ENV EDITOR=nano
ENV VISUAL=nano

# Default powerline10k theme
ARG ZSH_IN_DOCKER_VERSION=1.2.0
RUN sh -c "$(wget -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 /usr/share/doc/fzf/examples/key-bindings.zsh" \
  -a "source /usr/share/doc/fzf/examples/completion.zsh" \
  -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
  -x

# Install CodeBuddy Code (manual installation method - only if not using Dev Containers Feature)
RUN npm install -g @tencent-ai/codebuddy-code@${CODEBUDDY_CODE_VERSION}

# Copy and set up firewall script
COPY init-firewall.sh /usr/local/bin/
USER root
RUN chmod +x /usr/local/bin/init-firewall.sh && \
  echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall && \
  chmod 0440 /etc/sudoers.d/node-firewall
USER node
```

> **注意：** 若使用此方式，devcontainer.json 中的 `build.args` 需包含 `"CODEBUDDY_CODE_VERSION"` 参数,且 `features` 字段中不应包含 CodeBuddy Code Feature。

### 3. init-firewall.sh
建立网络安全规则。

```bash
#!/bin/bash
set -euo pipefail  # Exit on error, undefined vars, and pipeline failures
IFS=$'\n\t'       # Stricter word splitting

# 1. Extract Docker DNS info BEFORE any flushing
DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)

# Flush existing rules and delete existing ipsets
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
ipset destroy allowed-domains 2>/dev/null || true

# 2. Selectively restore ONLY internal Docker DNS resolution
if [ -n "$DOCKER_DNS_RULES" ]; then
    echo "Restoring Docker DNS rules..."
    iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
    iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
    echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
else
    echo "No Docker DNS rules to restore"
fi

# First allow DNS and localhost before any restrictions
# Allow outbound DNS
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
# Allow inbound DNS responses
iptables -A INPUT -p udp --sport 53 -j ACCEPT
# Allow outbound SSH
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
# Allow inbound SSH responses
iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
# Allow localhost
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Create ipset with CIDR support
ipset create allowed-domains hash:net

# Fetch GitHub meta information and aggregate + add their IP ranges
echo "Fetching GitHub IP ranges..."
gh_ranges=$(curl -s https://api.github.com/meta)
if [ -z "$gh_ranges" ]; then
    echo "ERROR: Failed to fetch GitHub IP ranges"
    exit 1
fi

if ! echo "$gh_ranges" | jq -e '.web and .api and .git' >/dev/null; then
    echo "ERROR: GitHub API response missing required fields"
    exit 1
fi

echo "Processing GitHub IPs..."
while read -r cidr; do
    if [[ ! "$cidr" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then
        echo "ERROR: Invalid CIDR range from GitHub meta: $cidr"
        exit 1
    fi
    echo "Adding GitHub range $cidr"
    ipset add allowed-domains "$cidr"
done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | aggregate -q)

# Resolve and add other allowed domains
for domain in \
    "registry.npmjs.org" \
    "copilot.tencent.com" \
    "sentry.io" \
    "marketplace.visualstudio.com" \
    "vscode.blob.core.windows.net" \
    "update.code.visualstudio.com"; do
    echo "Resolving $domain..."
    ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
    if [ -z "$ips" ]; then
        echo "ERROR: Failed to resolve $domain"
        exit 1
    fi
    
    while read -r ip; do
        if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
            echo "ERROR: Invalid IP from DNS for $domain: $ip"
            exit 1
        fi
        echo "Adding $ip for $domain"
        ipset add allowed-domains "$ip"
    done < <(echo "$ips")
done

# Get host IP from default route
HOST_IP=$(ip route | grep default | cut -d" " -f3)
if [ -z "$HOST_IP" ]; then
    echo "ERROR: Failed to detect host IP"
    exit 1
fi

HOST_NETWORK=$(echo "$HOST_IP" | sed "s/\.[0-9]*$/.0\/24/")
echo "Host network detected as: $HOST_NETWORK"

# Set up remaining iptables rules
iptables -A INPUT -s "$HOST_NETWORK" -j ACCEPT
iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT

# Set default policies to DROP first
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# First allow established connections for already approved traffic
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Then allow only specific outbound traffic to allowed domains
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT

# Explicitly REJECT all other outbound traffic for immediate feedback
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited

echo "Firewall configuration complete"
echo "Verifying firewall rules..."
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
    echo "ERROR: Firewall verification failed - was able to reach https://example.com"
    exit 1
else
    echo "Firewall verification passed - unable to reach https://example.com as expected"
fi

# Verify GitHub API access
if ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
    echo "ERROR: Firewall verification failed - unable to reach https://api.github.com"
    exit 1
else
    echo "Firewall verification passed - able to reach https://api.github.com as expected"
fi
```

---

## 安全特性

容器实现了**多层安全防护**：

- **精确访问控制**：限制出站连接到白名单域名（npm registry、GitHub、CodeBuddy API 等）
- **允许的出站连接**：防火墙允许出站 DNS 和 SSH 连接
- **默认拒绝策略**：阻止所有其他外部网络访问
- **启动验证**：容器初始化时验证防火墙规则
- **隔离**：创建与主系统分离的安全开发环境

### 重要安全提示
> 虽然开发容器提供了实质性的保护，但没有系统能够完全免疫所有攻击。当使用 `-y` （或 `--dangerously-skip-permissions`) 执行时，开发容器无法阻止恶意项目窃取容器内可访问的任何内容，包括 CodeBuddy Code 凭证。**我们建议仅在处理可信仓库时使用开发容器。** 始终保持良好的安全实践并监控 CodeBuddy 的活动。

### 无人值守操作
容器增强的安全措施（隔离和防火墙规则）允许您运行 `codebuddy -y` （或 `codebuddy --dangerously-skip-permissions`) 来绕过权限提示，实现无人值守操作。

---

## 自定义选项

开发容器配置设计灵活，可根据需求调整：

- 根据工作流添加或删除 VS Code 扩展
- 为不同硬件环境调整资源分配
- 调整网络访问权限
- 自定义 shell 配置和开发工具

---

## 使用场景

### 1. 安全的客户项目开发
使用开发容器隔离不同客户的项目，确保代码和凭证永不混合。

### 2. 团队快速入职
新团队成员可在几分钟内获得完全配置的开发环境，所有必要的工具和设置都已预装。

### 3. 一致的 CI/CD 环境
在 CI/CD 流水线中镜像您的开发容器配置，确保开发和生产环境匹配。

---

## 相关资源

- [VS Code 开发容器文档](https://code.visualstudio.com/docs/devcontainers/containers)
- [Bash 沙箱](./bash-sandboxing.md)
- [设置配置](./settings.md)
- [GitLab CI/CD 集成](./gitlab-ci-cd.md)
- 📖 [Official Dev Containers Docs](https://containers.dev/)
- 🔗 [devcontainers-contrib/features](https://github.com/devcontainers-contrib/features)
