Files
2026-04-19 21:47:08 +08:00

230 lines
11 KiB
Docker
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 使用本地缓存资源的 Dockerfile
# 构建前请先运行 ./download-resources.sh 下载所需资源
# 注意:默认情况下 .env 文件对 Dockerfile 是无效的
# ============================================================================
# 基础镜像: 全局环境变量和标签
# ============================================================================
FROM mcr.microsoft.com/devcontainers/base:noble AS base_0_5
ARG CONTAINER_USER_UID=1000
ARG CONTAINER_USER_GID=1000
ARG PROJECT_NAME=isos
ARG DOCKER_GID=984
ENV TZ="Asia/Shanghai" \
CONTAINER_USER_UID="$CONTAINER_USER_UID" \
CONTAINER_USER_GID="$CONTAINER_USER_GID" \
PROJECT_NAME="$PROJECT_NAME" \
DOCKER_GID="$DOCKER_GID" \
PROJECT_ROOT="/workspace" \
DEBIAN_FRONTEND=noninteractive
LABEL maintainer="Arno Jin <arno@arnojin.com>" \
base.image="mcr.microsoft.com/devcontainers/base:noble" \
ubuntu.version="24.04" \
uv.version="installed" \
spec-kit.version="installed" \
container.name="$PROJECT_NAME" \
container.hostname="$PROJECT_NAME"
# ============================================================================
# 阶段 1/4: 系统级安装 + Docker CLI(需要 root 权限)
# ============================================================================
# 注意: mcr.microsoft.com/devcontainers/base:noble 已预装:
# 核心: git, curl, wget, sudo, jq, unzip, zip, gnupg, xz-utils, patch
# 编辑: vim-tiny, vim-common, nano
# 构建: build-essential (gcc, g++, make), libc6-dev, libssl-dev, zlib1g-dev
# 网络: iproute2, net-tools, openssh-client
# 系统: locales, tzdata, ca-certificates, htop, strace, lsof, ncdu, tree, rsync
# Shell: bash, zsh (+ oh-my-zsh)
# 用户: vscode (UID/GID 1000, sudo NOPASSWD, shell=/bin/bash)
# Root: shell=/bin/bash
# 以下工具/库需要额外安装:
FROM base_0_5 AS system_and_docker_1_4
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
set -eu && \
# 配置清华镜像源
sed -i 's|http://archive.ubuntu.com|https://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/ubuntu.sources \
&& sed -i 's|http://security.ubuntu.com|https://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/ubuntu.sources \
# 移除失效的 Yarn APT 源
&& rm -f /etc/apt/sources.list.d/yarn.list \
/usr/share/keyrings/yarn-archive-keyring.gpg \
/etc/apt/sources.list.d/yarn.list.bak 2>/dev/null || true \
# 安装系统包
&& apt-get update \
&& apt-get install -y --no-install-recommends \
bats tmux ssh ipset iptables \
iputils-ping dnsutils telnet fzf vim xvfb xauth x11-utils \
fonts-inter fonts-noto-cjk fonts-jetbrains-mono fonts-liberation fontconfig fonts-noto-color-emoji \
# Playwright Chromium 核心依赖
libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 libatspi2.0-0t64 \
libdrm2 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libxext6 \
libgbm1 libxkbcommon0 libpango-1.0-0 libcairo2 libcairo-gobject2 \
libasound2t64 libcups2t64 libgtk-3-0t64 libvulkan1 xdg-utils \
# Playwright Chromium X11/Wayland 依赖
libdbus-1-3 libx11-6 libxcb1 libxcursor1 libxi6 libxrender1 \
libx11-xcb1 libxcb-shm0 libglib2.0-0t64 \
# Playwright Chromium 渲染/媒体依赖
libfontconfig1 libfreetype6 libgdk-pixbuf-2.0-0 libpangocairo-1.0-0 \
libavcodec60 libopus0 libvpx9 libwebp7 libxslt1.1 libharfbuzz0b \
libjpeg-turbo8 libpng16-16t64 libavif16 liblcms2-2 libevent-2.1-7t64 \
# GPU 支持
libgl1 libegl1 \
# --- Docker CLIDooD 模式,仅 CLI,不含 daemon,使用清华镜像源)---
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& chmod a+r /etc/apt/keyrings/docker.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
docker-ce-cli \
docker-buildx-plugin \
docker-compose-plugin \
# --- 系统配置 ---
&& locale-gen zh_CN.UTF-8 \
&& update-locale LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 \
&& echo "Asia/Shanghai" > /etc/timezone \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& fc-cache -fv \
&& echo 'fs.inotify.max_user_watches=524288' >> /etc/sysctl.d/10-sysctl.conf \
&& rm -rf /var/lib/apt/lists/* \
/tmp/* \
/var/tmp/* \
/var/cache/apt/archives/*.deb
# ============================================================================
# 阶段 2/4: 安装 Chrome + 配置用户和目录权限(需要 root 权限)
# ============================================================================
FROM system_and_docker_1_4 AS chrome_2_4
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=bind,source=.devcontainer/.cache/chrome,target=/tmp/chrome-cache \
set -eu && \
apt-get update \
&& dpkg -i /tmp/chrome-cache/google-chrome-stable_*.deb || apt-get install -y -f \
&& google-chrome --version \
&& rm -rf /var/lib/apt/lists/* \
&& if [ "$CONTAINER_USER_UID" != "1000" ] || [ "$CONTAINER_USER_GID" != "1000" ]; then \
groupmod -g "$CONTAINER_USER_GID" vscode && \
usermod -u "$CONTAINER_USER_UID" -d "/home/vscode" vscode && \
chown -R "$CONTAINER_USER_UID:$CONTAINER_USER_GID" /home/vscode; \
fi \
&& groupmod -g ${DOCKER_GID} docker 2>/dev/null || groupadd -g ${DOCKER_GID} docker 2>/dev/null || true \
&& usermod -aG docker vscode \
&& mkdir -p $PROJECT_ROOT \
&& chown -R vscode:vscode $PROJECT_ROOT
# ============================================================================
# 阶段 3/4: 安装开发工具(普通用户)
# ============================================================================
FROM chrome_2_4 AS dev_tools_3_4
ENV BIN_PATH="$PROJECT_ROOT/.devcontainer/.volumes/bin" \
NVM_DIR="/home/vscode/.nvm" \
NODE_VERSION="22.22.0" \
UV_PYTHON_DIR="/home/vscode/.local/share/uv" \
UV_BIN="/home/vscode/.local/bin" \
PATH="/home/vscode/.local/bin:$PATH" \
UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
UV_LINK_MODE=copy \
PYTHONUNBUFFERED=1 \
COREPACK_ENABLE_DOWNLOAD_PROMPT=false \
LC_ALL=zh_CN.UTF-8 \
LC_TIME=zh_CN.UTF-8 \
LANG=zh_CN.UTF-8 \
DEVCONTAINER=true \
EDITOR=vim \
VISUAL=vim \
SHELL=/bin/bash \
DISPLAY=""
WORKDIR $PROJECT_ROOT
USER vscode
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
# 使用 --mount=type=bind 挂载缓存资源(避免 COPY 层残留临时文件)
RUN --mount=type=bind,source=.devcontainer/.cache/uv,target=/tmp/uv-cache \
--mount=type=bind,source=.devcontainer/.cache/node,target=/tmp/node-cache \
--mount=type=bind,source=.devcontainer/.cache/npm,target=/tmp/npm-cache \
--mount=type=bind,source=.devcontainer/.cache/vscode,target=/tmp/vscode-cache \
--mount=type=bind,source=.devcontainer/.cache/nvm,target=/tmp/nvm-cache \
--mount=type=bind,source=.devcontainer/.cache/spec-kit,target=/tmp/spec-kit-cache \
--mount=type=bind,source=.devcontainer/.cache/jj,target=/tmp/jj-cache \
mkdir -p "$UV_BIN" "$UV_PYTHON_DIR" "$NVM_DIR" \
&& tar -xzf /tmp/uv-cache/uv-x86_64-unknown-linux-gnu.tar.gz -C "$UV_BIN" --strip-components=1 \
&& tar -xzf /tmp/uv-cache/cpython-*-x86_64-unknown-linux-gnu-install_only.tar.gz -C "$UV_PYTHON_DIR" \
&& ln -sf "$UV_PYTHON_DIR/python/bin/python3.12" "$UV_BIN/python3" \
&& ln -sf "$UV_PYTHON_DIR/python/bin/python3.12" "$UV_BIN/python" \
&& "$UV_BIN/uv" tool install --python "$UV_PYTHON_DIR/python/bin/python3.12" /tmp/spec-kit-cache \
&& ln -sf "/home/vscode/.local/share/uv/tools/specify-cli/bin/specify" "$UV_BIN/specify" \
&& cp -r /tmp/nvm-cache/. "$NVM_DIR/" \
&& [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" \
&& mkdir -p "$NVM_DIR/versions/node" \
&& tar -xJf /tmp/node-cache/node-v*.tar.xz -C "$NVM_DIR/versions/node" \
&& NODE_DIR=$(ls -d "$NVM_DIR/versions/node"/node-v* | head -n1) \
&& mv "$NODE_DIR" "$NVM_DIR/versions/node/v${NODE_VERSION}" \
&& nvm use v${NODE_VERSION} \
&& nvm alias default v${NODE_VERSION} \
&& export PATH="$NVM_DIR/versions/node/v${NODE_VERSION}/bin:$PATH" \
&& export npm_config_registry=https://registry.npmmirror.com \
&& find /tmp/npm-cache/ -maxdepth 1 -name "npm-*.tgz" -exec npm install -g {} + \
&& find /tmp/npm-cache/ -maxdepth 1 -name "*.tgz" ! -name "npm-*.tgz" -exec npm install -g {} + \
# 安装 VSCode 扩展
&& mkdir -p "/home/vscode/.vscode-server/extensions" \
&& for ext in /tmp/vscode-cache/*.vsix; do \
[ -f "$ext" ] || continue; \
ext_name=$(basename "$ext" .vsix); \
temp_dir=$(mktemp -d); \
unzip -q "$ext" -d "$temp_dir" 2>/dev/null || true; \
[ -d "$temp_dir/extension" ] || { rm -rf "$temp_dir"; continue; }; \
version=$(jq -r '.version' "$temp_dir/extension/package.json" 2>/dev/null || echo "0.0.0"); \
ext_dir="/home/vscode/.vscode-server/extensions/${ext_name}-${version}"; \
mkdir -p "$ext_dir"; \
cp -r "$temp_dir/extension"/* "$ext_dir/"; \
rm -rf "$temp_dir"; \
done \
# 安装 Jujutsu (jj) 版本控制工具
&& jj_tar=$(ls /tmp/jj-cache/jj-*.tar.gz 2>/dev/null | head -n1) \
&& if [ -n "$jj_tar" ]; then \
mkdir -p /tmp/jj-extract \
&& tar -xzf "$jj_tar" -C /tmp/jj-extract \
&& cp /tmp/jj-extract/jj "$UV_BIN/jj" \
&& chmod +x "$UV_BIN/jj" \
&& jj --version; \
fi
# 将 Node.js 加入镜像级 PATH,确保非交互式进程也能找到 node/npm
ENV PATH="/home/vscode/.nvm/versions/node/v${NODE_VERSION}/bin:${PATH}"
# ============================================================================
# 阶段 4/4: 配置用户 shell 环境(.bashrc
# ============================================================================
FROM dev_tools_3_4 AS shell_env_4_4
SHELL ["/bin/bash", "-e", "-u", "-o", "pipefail", "-c"]
# 修复 /usr/local/bin/code 包装脚本
USER root
COPY .devcontainer/scripts/code-wrapper /usr/local/bin/code
RUN chmod +x /usr/local/bin/code
# 使用模板文件替代逐行 echo,通过 sed 替换占位符
COPY .devcontainer/templates/bashrc.tail.sh /tmp/bashrc.tail.sh
RUN sed "s|{{NODE_PATH}}|/home/vscode/.nvm/versions/node/v${NODE_VERSION}/bin|g; \
s|{{BIN_PATH}}|${BIN_PATH}|g" \
/tmp/bashrc.tail.sh >> /home/vscode/.bashrc \
&& rm -f /tmp/bashrc.tail.sh \
&& chown vscode:vscode /home/vscode/.bashrc
USER vscode
RUN mkdir -p /home/vscode/.ssh /home/vscode/.claude \
&& chmod 700 /home/vscode/.ssh
CMD ["sleep", "infinity"]