# ============================================================================
# 基础镜像 Dockerfile - rcoder-agent-base
# ============================================================================
# 此镜像包含所有系统软件、工具链和环境配置,不包含业务代码
# 构建命令: make docker-build-agent-base
# 平时不需要重新构建,只有在修改系统依赖时才需要重新构建
# ============================================================================
ARG DOCKER_MIRROR
# GitHub 镜像加速配置(默认启用)
ARG USE_GITHUB_MIRROR=true
# 基础镜像: buildpack-deps:bookworm-curl
# - 基于 debian:12 (bookworm), 与 debian:12 100% 兼容
# - 预装 curl + wget + ca-certificates + gnupg, 无需先 apt 安装
# - Docker 官方维护, 与 debian:12 共享底层 layer, 拉取几乎零增量
# - 避免"先用海外 apt 源装 curl 再配置镜像"的鸡生蛋死循环
FROM ${DOCKER_MIRROR}buildpack-deps:bookworm-curl
# GitHub 镜像加速环境变量(必须在 FROM 之后设置)
ENV GITHUB_MIRROR_URL=https://gh-proxy.org/https://github.com/
# Base image with desktop environment and applications
# Environment variables:
ENV \
# Avoid system prompts: \
DEBIAN_FRONTEND=noninteractive \
DEBIAN_PRIORITY=high \
# Timezone: \
TZ=Asia/Shanghai \
# Pip settings: \
PIP_DEFAULT_TIMEOUT=100 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1
# 使用 LinuxMirrors 一键配置阿里云镜像源
# 参考: https://github.com/SuperManito/LinuxMirrors
# buildpack-deps:bookworm-curl 已预装 curl,可直接执行脚本配置镜像源
RUN curl -sSL https://linuxmirrors.cn/main.sh | bash -s -- \
--source mirrors.aliyun.com \
--protocol https \
--use-intranet-source false \
--install-epel false \
--backup false \
--upgrade-software false \
--clean-cache true \
--ignore-backup-tips
# 复制 npmrc 配置镜像(使用国内镜像加速)
COPY .npmrc /root/.npmrc
# Desktop environment and applications (merged for smaller image size):
RUN apt-get update && \
# Install all packages in one layer to reduce image size
apt-get install -y \
# X window server:
xserver-xorg xorg x11-xserver-utils xvfb x11-utils xauth \
# XFCE desktop environment:
xfce4 xfce4-goodies \
# 中文输入法(fcitx5 完整安装)
fcitx5 fcitx5-pinyin fcitx5-frontend-gtk3 fcitx5-frontend-gtk2 fcitx5-frontend-qt5 fcitx5-module-xorg fcitx5-config-qt im-config dbus-x11 \
# Basic system utilities:
util-linux sudo curl git \
# Python pip:
python3-pip \
# Network analysis tools:
iputils-ping traceroute dnsutils tcpdump mtr-tiny nmap iperf3 httping telnet \
# Desktop SDK tools:
xdotool scrot \
# FFmpeg 8 编译依赖:
yasm nasm pkg-config libx264-dev libx265-dev libnuma-dev libvpx-dev libmp3lame-dev libopus-dev libspeex-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev texinfo zlib1g-dev \
# VNC and streaming:
tigervnc-standalone-server tigervnc-xorg-extension net-tools netcat-openbsd \
# Audio streaming (PulseAudio + pcmflux dependencies):
pulseaudio pulseaudio-utils libpulse-dev libopus-dev \
# Standard applications:
x11-apps xpdf gedit xpaint tint2 galculator pcmanfm \
# Dependencies for later installations:
apt-transport-https ca-certificates gnupg \
socat supervisor \
xclip \
wget software-properties-common \
# Suna dependencies:
poppler-utils wkhtmltopdf antiword unrtf catdoc gawk file jq csvkit xmlstarlet zip unzip tmux vim tree rsync && \
# Configure X11 wrapper to allow any user:
sed -i 's/allowed_users=console/allowed_users=anybody/' /etc/X11/Xwrapper.config && \
# Set the default terminal
ln -sf /usr/bin/xfce4-terminal.wrapper /etc/alternatives/x-terminal-emulator
# ============================================================================
# 🎬 编译安装 FFmpeg 8.0 (最新版本)
# ============================================================================
# Debian 12 官方仓库的 FFmpeg 版本是 5.1.8,这里从源码编译安装最新的 FFmpeg 8
RUN echo "Building FFmpeg 8.0 from source..." && \
cd /tmp && \
if [ "${USE_GITHUB_MIRROR}" = "true" ]; then \
git clone --depth 1 --branch release/8.0 ${GITHUB_MIRROR_URL}FFmpeg/FFmpeg.git ffmpeg-8.0; \
else \
git clone --depth 1 --branch release/8.0 https://github.com/FFmpeg/FFmpeg.git ffmpeg-8.0; \
fi && \
cd ffmpeg-8.0 && \
./configure \
--extra-version="8.0" \
--disable-debug \
--disable-doc \
--disable-ffplay \
--enable-gpl \
--enable-libx264 \
--enable-libx265 \
--enable-libvpx \
--enable-libmp3lame \
--enable-libopus && \
make -j$(nproc) && \
make install && \
ldconfig && \
echo "✅ FFmpeg 8.0 installed: $(ffmpeg -version | head -1)" && \
# Clean up build artifacts:
rm -rf /tmp/ffmpeg-8.0 && \
# Clean up package cache and temporary files to reduce image size:
apt-get autoremove -y && \
apt-get autoclean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /root/.cache
# ============================================================================
# 🔊 配置 PulseAudio 系统模式(容器环境优化)
# ============================================================================
# 配置 PulseAudio 系统模式,支持以 root 身份运行
RUN mkdir -p /etc/pulse && \
# 创建系统模式配置
printf '[General]\n\
default-sample-format = s16le\n\
default-sample-rate = 48000\n\
default-sample-channels = 2\n\
default-fragments = 2\n\
default-fragment-size-msec = 125\n' > /etc/pulse/daemon.conf && \
# 允许所有用户连接系统模式 PulseAudio
printf '[General]\n\
allow-autospawn-for-root = yes\n\
default-server = unix:/var/run/pulse/native\n' > /etc/pulse/client.conf && \
# 创建系统模式 pulse 用户组
groupadd -r pulse-access 2>/dev/null || true && \
usermod -aG pulse-access root 2>/dev/null || true && \
usermod -aG pulse-access user 2>/dev/null || true && \
# 创建 PulseAudio 运行时目录
mkdir -p /var/run/pulse && \
chmod 1777 /var/run/pulse && \
echo "✅ PulseAudio system mode configured"
# ============================================================================
# 🐍 配置 pip 镜像源 (系统级配置,所有用户共享)
# ============================================================================
# 使用阿里云 PyPI 镜像源(稳定可靠,无限流限制)
RUN mkdir -p /etc/pip && \
printf '[global]\n\
index-url = https://mirrors.aliyun.com/pypi/simple/\n\
trusted-host = mirrors.aliyun.com\n\
timeout = 120\n\
\n\
[install]\n\
trusted-host = mirrors.aliyun.com\n' > /etc/pip/pip.conf && \
# 同时为 root 用户创建配置(备用)
mkdir -p /root/.config/pip && \
cp /etc/pip/pip.conf /root/.config/pip/pip.conf && \
# 为 user 用户创建配置目录(后续会设置权限)
mkdir -p /home/user/.config/pip && \
cp /etc/pip/pip.conf /home/user/.config/pip/pip.conf && \
echo "✅ pip 镜像源配置完成: https://mirrors.aliyun.com/pypi/simple/"
# ============================================================================
# 📦 安装常用 Python 数据科学库 (系统级,所有用户可用)
# ============================================================================
# 🎯 最佳实践:指定稳定版本,避免版本冲突
# 1. 升级基础工具确保依赖解析正确
# 2. 按依赖关系顺序安装(numpy -> pandas/scipy -> matplotlib -> 其他)
# 3. 使用经过验证的稳定版本组合
# 🎯 使用 requirements.txt 方式安装依赖
# ======================================
# 优势:版本锁定、可重现构建、便于维护
# 复制 requirements.txt 文件到容器
COPY requirements.txt /tmp/requirements.txt
# 升级基础工具并安装依赖
RUN pip3 install --break-system-packages --upgrade pip setuptools wheel && \
pip3 install --break-system-packages -r /tmp/requirements.txt && \
echo "✅ Python 数据科学库安装完成" && \
echo "已安装的包:" && \
pip3 list | grep -E '(numpy|pandas|scipy|matplotlib|openpyxl|python-docx|PyMuPDF|xlrd|xlwt|seaborn|requests|BeautifulSoup|lxml|scikit-learn)'
# ============================================================================
# 📺 noVNC 远程桌面客户端(based on official v1.6.0, with customized vnc.html):
# ============================================================================
# 添加构建参数以强制打破缓存(每次构建时传入不同的 CACHEBUST_NOVNC)
ARG CACHEBUST_NOVNC=1
RUN echo "noVNC cache bust: ${CACHEBUST_NOVNC}" && \
if [ "${USE_GITHUB_MIRROR}" = "true" ]; then \
echo "Using GitHub mirror: ${GITHUB_MIRROR_URL}"; \
echo "Cloning noVNC from: ${GITHUB_MIRROR_URL}nuwax-ai/noVNC.git"; \
git clone --depth 1 -b release ${GITHUB_MIRROR_URL}nuwax-ai/noVNC.git /opt/noVNC && \
echo "Cloning websockify from: ${GITHUB_MIRROR_URL}novnc/websockify"; \
git clone --depth 1 -b v0.12.0 ${GITHUB_MIRROR_URL}novnc/websockify /opt/noVNC/utils/websockify; \
else \
echo "Using direct GitHub (no mirror)"; \
git clone --depth 1 -b release https://github.com/nuwax-ai/noVNC.git /opt/noVNC && \
git clone --depth 1 -b v0.12.0 https://github.com/novnc/websockify /opt/noVNC/utils/websockify; \
fi && \
cd /opt/noVNC && \
ln -s /opt/noVNC/vnc.html /opt/noVNC/index.html
# ============================================================================
# Install LibreOffice
# ============================================================================
# Installation logic:
# 1. If local tar.gz exists in downloads/ (pre-downloaded via Makefile) -> use it
# 2. Otherwise -> download from official URL (fallback)
#
# This allows:
# - Fast build with pre-downloaded files (avoid slow external download)
# - Fallback to online download if local file is missing
# ============================================================================
ARG LIBREOFFICE_FILE
ARG TARGETARCH
COPY downloads /tmp/downloads
# Determine URL based on architecture
RUN echo "Installing LibreOffice..." && \
case "${TARGETARCH}" in \
"amd64") \
LIBREOFFICE_URL="https://download.documentfoundation.org/libreoffice/stable/25.8.5/deb/x86_64/${LIBREOFFICE_FILE}" \
;; \
"arm64") \
LIBREOFFICE_URL="https://download.documentfoundation.org/libreoffice/stable/25.8.5/deb/aarch64/${LIBREOFFICE_FILE}" \
;; \
*) echo "❌ Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
esac && \
# Check if local pre-downloaded file exists
if [ -f "/tmp/downloads/${LIBREOFFICE_FILE}" ]; then \
echo "📦 Using pre-downloaded file: /tmp/downloads/${LIBREOFFICE_FILE}"; \
cp /tmp/downloads/${LIBREOFFICE_FILE} /tmp/libreoffice.tar.gz; \
else \
echo "🌐 No local file found, downloading from official URL..."; \
wget -q -O /tmp/libreoffice.tar.gz "${LIBREOFFICE_URL}" || { \
echo "❌ ERROR: Failed to download LibreOffice from ${LIBREOFFICE_URL}"; \
exit 1; \
}; \
fi && \
# Extract and install
cd /tmp && \
tar -xzf libreoffice.tar.gz && \
cd LibreOffice_*/DEBS && \
dpkg -i *.deb && \
apt-get update && \
apt-get install -f -y && \
# Create symlinks for LibreOffice executables
LIBREOFFICE_BIN=$(find /opt -name "soffice" -path "*/program/*" 2>/dev/null | head -1) && \
if [ -n "$LIBREOFFICE_BIN" ]; then \
LIBREOFFICE_DIR=$(dirname "$LIBREOFFICE_BIN") && \
ln -sf "$LIBREOFFICE_BIN" /usr/bin/soffice && \
ln -sf "$LIBREOFFICE_DIR/libreoffice" /usr/bin/libreoffice 2>/dev/null || true && \
echo "✅ LibreOffice installed: $LIBREOFFICE_BIN -> /usr/bin/soffice"; \
else \
echo "❌ ERROR: LibreOffice soffice binary not found in /opt"; \
exit 1; \
fi && \
# Clean up
rm -rf /tmp/LibreOffice_* /tmp/libreoffice.tar.gz /tmp/downloads && \
apt-get autoremove -y && \
apt-get autoclean && \
rm -rf /var/lib/apt/lists/*
# User applications:
# ~ Make your changes to this template BELOW this line ~
# Install Chromium from Debian official repository (optimized single layer)
RUN apt-get update && \
apt-get install -y chromium chromium-driver && \
# Clean up package cache to reduce image size:
apt-get autoremove -y && \
apt-get autoclean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Configure Chromium to skip first-run experience and enable CDP
RUN mkdir -p /etc/chromium/policies/managed/ && \
echo '{"CommandLineFlagSecurityWarningsEnabled": false, "AllowInsecureLocalConnections": true, "RemoteDebuggingAllowed": true, "RemoteDebuggingPort": "9222", "RemoteDebuggingAddress":"0.0.0.0", "AutoplayAllowed": true, "MetricsReportingEnabled": false, "BrowserSignin": 0, "SyncDisabled": true, "PromotionalTabsEnabled": false, "DefaultBrowserSettingEnabled": false, "DefaultSearchProviderEnabled": false, "HideWebStoreIcon": true, "PasswordManagerEnabled": false, "PromptForDownloadLocation": false, "SafeBrowsingEnabled": false, "SafeBrowsingExtendedReportingEnabled": false, "UrlKeyedAnonymizedDataCollectionEnabled": false, "CloudPrintSubmitEnabled": false, "CloudManagementEnrollmentToken": "", "SpellcheckEnabled": false}' > /etc/chromium/policies/managed/policies.json
# Create user and configure Chrome (merged for smaller image size)
RUN apt-get update && \
apt-get install -y gnome-keyring policykit-1-gnome policykit-1 && \
# Create default user
useradd -ms /bin/bash user && \
usermod -aG sudo user && \
passwd -d user && \
echo "user ALL=(ALL:ALL) NOPASSWD: ALL" >>/etc/sudoers && \
# Create Chromium configuration directories
mkdir -p /home/user/.config/chromium/Default/ && \
echo '{ "browser": { "custom_chrome_frame": true, "has_seen_welcome_page": true }, "profile": { "default_content_setting_values": { "geolocation": 1 }, "exit_type": "Normal", "exited_cleanly": true, "password_manager_enabled": false }, "credentials_enable_service": false, "credentials_enable_autosignin": false }' > /home/user/.config/chromium/Default/Preferences && \
# Configure GNOME keyring with empty password (禁用密码提示)
mkdir -p /home/user/.local/share/keyrings && \
# 创建 Default keyring(无密码,禁用锁定)
printf '[keyring]\ndisplay-name=Default keyring\nctime=1732780800\nmtime=1732780800\nlock-on-idle=false\nlock-after=false\n' > /home/user/.local/share/keyrings/Default_keyring.keyring && \
# 设置为默认 keyring
echo 'Default_keyring' > /home/user/.local/share/keyrings/default && \
chmod 600 /home/user/.local/share/keyrings/Default_keyring.keyring && \
chmod 600 /home/user/.local/share/keyrings/default && \
# Configure PolicyKit to auto-allow Chromium operations
mkdir -p /etc/polkit-1/localauthority/50-local.d && \
# Create comprehensive policy for user
printf '[Allow all for user]\nIdentity=unix-user:user\nAction=*\nResultAny=yes\nResultInactive=yes\nResultActive=yes\n\n[Allow all for user - specific patterns]\nIdentity=unix-user:user\nAction=org.freedesktop.policykit.*\nResultAny=yes\nResultInactive=yes\nResultActive=yes\n\n[Allow Chromium specific]\nIdentity=unix-user:user\nAction=org.chromium.*\nResultAny=yes\nResultInactive=yes\nResultActive=yes' > /etc/polkit-1/localauthority/50-local.d/comprehensive-allow.pkla && \
# ========== 系统级自启动配置 (不受用户目录挂载影响) ==========
mkdir -p /etc/xdg/autostart && \
# PolicyKit authentication agent autostart
printf "[Desktop Entry]\nType=Application\nName=PolicyKit Auth\nExec=/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1\nHidden=false\nNoDisplay=false\nX-GNOME-Autostart-enabled=true\nStartupNotify=false" > /etc/xdg/autostart/polkit-gnome-auth.desktop && \
# D-Bus session autostart
printf "[Desktop Entry]\nType=Application\nName=D-Bus Session\nExec=/usr/bin/dbus-launch --sh-syntax\nHidden=false\nNoDisplay=false\nX-GNOME-Autostart-enabled=true" > /etc/xdg/autostart/dbus-session.desktop && \
# PolicyKit daemon autostart
printf "[Desktop Entry]\nType=Application\nName=PolicyKit Daemon\nExec=/usr/lib/policykit-1/polkitd --no-debug\nHidden=false\nNoDisplay=false\nX-GNOME-Autostart-enabled=true" > /etc/xdg/autostart/polkitd.desktop && \
# Fcitx5 autostart
printf '[Desktop Entry]\nType=Application\nName=Fcitx 5\nComment=Input Method Framework\nExec=fcitx5 -d --replace\nIcon=fcitx5\nStartupNotify=false\nTerminal=false\nCategories=System;Utility;\nX-GNOME-Autostart-enabled=true\nX-XFCE-Autostart-enabled=true' > /etc/xdg/autostart/fcitx5.desktop && \
# 创建用户运行时目录
mkdir -p /run/user/$(id -u user) && \
# ========== 系统级 fcitx5 输入法配置 ==========
mkdir -p /etc/xdg/fcitx5/conf && \
# fcitx5 全局配置
printf '[Hotkey]\nEnumerateWithTriggerKeys=True\nAltTriggerKeys=\nEnumerateForwardKeys=\nEnumerateBackwardKeys=\nEnumerateSkipFirst=False\n\n[Hotkey/TriggerKeys]\n0=Control+space\n1=Shift+Control+space\n\n[Hotkey/EnumerateGroupForwardKeys]\n0=Super+space\n\n[Hotkey/EnumerateGroupBackwardKeys]\n0=Shift+Super+space\n\n[Hotkey/ActivateKeys]\n0=Hangul_Hanja\n\n[Hotkey/DeactivateKeys]\n0=Hangul_Romaja\n\n[Hotkey/PrevPage]\n0=Up\n\n[Hotkey/NextPage]\n0=Down\n\n[Hotkey/PrevCandidate]\n0=Shift+Tab\n\n[Hotkey/NextCandidate]\n0=Tab\n\n[Hotkey/TogglePreedit]\n0=Control+Alt+P\n\n[Behavior]\nActiveByDefault=False\nShareInputState=No\nPreloadInputMethod=True\nShowInputMethodInformation=True\nShowInputMethodInformationWhenFocusIn=False\nCompactInputMethodInformation=True\nShowFirstInputMethodInformation=True\nDefaultPageSize=5\nOverrideXkbOption=False\nCustomXkbOption=\nEnabledAddons=\nDisabledAddons=\nPreeditEnabledByDefault=True' > /etc/xdg/fcitx5/config && \
# 拼音配置
printf '[Addon]\nEnabled=True\n\n[Behavior]\nPreeditInApplication=False\n\n[PinyinEngine]\nShuangpinProfile=Ziranma\nShowActualPinyinInHint=False\nPredictionEnabled=True\nPredictionSize=10\nCloudPinyinEnabled=False\nCloudPinyinIndex=2' > /etc/xdg/fcitx5/conf/pinyin.conf && \
# 禁用云拼音提示
printf '[Addon]\nEnabled=False\n\n[Behavior]\nShowNotification=False' > /etc/xdg/fcitx5/conf/cloudpinyin.conf && \
# 禁用通知
printf '[Addon]\nEnabled=False\n\n[Behavior]\nHiddenNotifications=' > /etc/xdg/fcitx5/conf/notifications.conf && \
# fcitx5 profile (系统默认: 英文键盘,用户可通过 Ctrl+Space 切换到中文拼音)
printf '[Groups/0]\nName=Default\nDefault Layout=us\nDefaultIM=keyboard-us\n\n[Groups/0/Items/0]\nName=keyboard-us\nLayout=\n\n[Groups/0/Items/1]\nName=pinyin\nLayout=\n\n[GroupOrder]\n0=Default\n\n[Behavior]\nActiveByDefault=False\nShareInputState=No' > /etc/xdg/fcitx5/profile && \
# ========== 系统级环境变量配置 (替代 .bashrc/.xprofile) ==========
mkdir -p /etc/profile.d && \
printf '#!/bin/sh\n# Fcitx5 Input Method Environment Variables\nexport GTK_IM_MODULE=fcitx5\nexport QT_IM_MODULE=fcitx5\nexport XMODIFIERS=@im=fcitx5\nexport INPUT_METHOD=fcitx5\nexport SDL_IM_MODULE=fcitx5\nexport GLFW_IM_MODULE=fcitx5\n' > /etc/profile.d/fcitx5-env.sh && \
chmod +x /etc/profile.d/fcitx5-env.sh && \
# ========== 系统级 GTK 配置 ==========
mkdir -p /etc/gtk-3.0 && \
printf '[Settings]\ngtk-im-module=fcitx5' > /etc/gtk-3.0/settings.ini && \
# ========== 隐藏 root 用户警告 ==========
# 为 root 用户创建 GTK CSS 配置,彻底隐藏 Thunar 和 XFCE 的各种 root 警告
mkdir -p /root/.config/gtk-3.0 && \
printf '/* Hide Thunar root warnings completely */\n.thunar-window infobar.warning { min-height: 0; max-height: 0; padding: 0; margin: 0; opacity: 0; }\n.thunar-window infobar.warning * { min-height: 0; max-height: 0; padding: 0; margin: 0; opacity: 0; }\n.thunar-window infobar.warning button { min-height: 0; min-width: 0; max-height: 0; padding: 0; margin: 0; opacity: 0; }\ninfobar.warning { min-height: 0; max-height: 0; padding: 0; margin: 0; opacity: 0; }\ninfobar.warning * { min-height: 0; max-height: 0; padding: 0; margin: 0; opacity: 0; }\n/* Hide XFCE root warning */\n.root-warning { display: none !important; opacity: 0 !important; }\n.root-warning * { display: none !important; opacity: 0 !important; }\n' > /root/.config/gtk-3.0/gtk.css && \
# 同时为 /home/user 添加配置(因为 HOME=/home/user)
mkdir -p /home/user/.config/gtk-3.0 && \
cp /root/.config/gtk-3.0/gtk.css /home/user/.config/gtk-3.0/gtk.css && \
# 禁用 XFCE 会话的 root 警告(系统级配置)
mkdir -p /root/.config/xfce4/xfconf/xfce-perchannel-xml && \
echo '\n\n \n \n \n' > /root/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-session.xml && \
# 同时为 user 配置
mkdir -p /home/user/.config/xfce4/xfconf/xfce-perchannel-xml && \
cp /root/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-session.xml /home/user/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-session.xml && \
# ========== 系统级 im-config ==========
echo 'fcitx5' > /etc/X11/xinit/xinputrc 2>/dev/null || true && \
# ========== 用户目录 - 只保留需要持久化的配置 ==========
# Chromium 配置 (用户可能需要持久化浏览器数据)
mkdir -p /home/user/.config/chromium/Default/ && \
echo '{ "browser": { "custom_chrome_frame": true, "has_seen_welcome_page": true }, "profile": { "default_content_setting_values": { "geolocation": 1 }, "exit_type": "Normal", "exited_cleanly": true, "password_manager_enabled": false }, "credentials_enable_service": false, "credentials_enable_autosignin": false }' > /home/user/.config/chromium/Default/Preferences && \
# GNOME keyring (禁用密码提示)
mkdir -p /home/user/.local/share/keyrings && \
printf '[keyring]\ndisplay-name=Default keyring\nctime=1732780800\nmtime=1732780800\nlock-on-idle=false\nlock-after=false\n' > /home/user/.local/share/keyrings/Default_keyring.keyring && \
echo 'Default_keyring' > /home/user/.local/share/keyrings/default && \
chmod 600 /home/user/.local/share/keyrings/Default_keyring.keyring && \
chmod 600 /home/user/.local/share/keyrings/default && \
# 创建基本的用户 .bashrc (只包含别名,不包含输入法配置)
printf '# User aliases\nalias chromium="/usr/local/bin/chromium"\nalias chrome="/usr/local/bin/chromium"\nalias code="code --no-sandbox"\n' > /home/user/.bashrc && \
# 设置权限(安全配置:755 允许读取和执行,所有者可写)
chown -R user:user /home/user /run/user/$(id -u user) && \
chmod 755 /home/user && \
chmod 700 /run/user/$(id -u user) && \
chown -R 1000:1000 /home/user/.config && \
chown -R 1000:1000 /home/user/.local && \
# Clean up package cache
apt-get autoremove -y && \
apt-get autoclean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Copy Chromium browser launcher script with fcitx support (runs as root, HOME=/home/user)
# IMPORTANT: Replace /usr/bin/chromium with our launcher so XFCE's exo-open uses it
COPY chromium-browser-launcher.sh /usr/bin/chromium-browser-launcher
RUN chmod 755 /usr/bin/chromium-browser-launcher && \
# Backup original chromium wrapper
mv /usr/bin/chromium /usr/bin/chromium-bin && \
# Replace /usr/bin/chromium with our launcher (XFCE uses this path)
cp /usr/bin/chromium-browser-launcher /usr/bin/chromium && \
ln -sf /usr/bin/chromium-browser-launcher /usr/local/bin/chromium
# Copy MCP-specific Chromium wrapper script (runs as user to connect to fcitx5)
COPY chromium-for-mcp.sh /usr/local/bin/chromium-for-mcp
RUN chmod 755 /usr/local/bin/chromium-for-mcp
# Create a socat service with supervisor to ensure port forwarding is always running
# RUN mkdir -p /etc/supervisor/conf.d && \
# echo '[supervisord]\nnodaemon=true\n\n[program:socat]\ncommand=socat TCP4-LISTEN:9223,fork,reuseaddr TCP4:127.0.0.1:9222\nautostart=true\nautorestart=true\nstdout_logfile=/var/log/socat.log\nstdout_logfile_maxbytes=1MB\nstdout_logfile_backups=10\nstderr_logfile=/var/log/socat.err\nstderr_logfile_maxbytes=1MB\nstderr_logfile_backups=10' > /etc/supervisor/conf.d/socat.conf
# Start the socat service at system boot
# RUN echo '#!/bin/bash\n# Start supervisor to manage socat service\n/usr/bin/supervisord -c /etc/supervisor/supervisord.conf &' > /usr/local/bin/start-port-forward.sh && \
# chmod +x /usr/local/bin/start-port-forward.sh && \
# echo "[Desktop Entry]\nType=Application\nName=PortForward\nExec=/usr/local/bin/start-port-forward.sh\nHidden=false\nNoDisplay=false\nX-GNOME-Autostart-enabled=true" > /home/user/.config/autostart/port-forward.desktop
# 添加自动启动supervisord的桌面启动项 (使用系统级配置)
RUN echo '#!/bin/bash\n# 启动supervisord服务\nsudo /usr/bin/supervisord -c /etc/supervisor/supervisord.conf &' > /usr/local/bin/start-supervisord.sh && \
chmod +x /usr/local/bin/start-supervisord.sh && \
echo "[Desktop Entry]\nType=Application\nName=Supervisord\nExec=/usr/local/bin/start-supervisord.sh\nHidden=false\nNoDisplay=false\nX-GNOME-Autostart-enabled=true" > /etc/xdg/autostart/supervisord.desktop && \
chmod 644 /etc/xdg/autostart/supervisord.desktop
# Install VS Code - 使用 apt 仓库方式(比直接下载 deb 更可靠)
RUN echo "📦 Adding Microsoft VS Code repository..." && \
# 添加微软 GPG 密钥
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg && \
# 添加 apt 仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list && \
echo "📥 Installing VS Code via apt..." && \
apt-get update && \
apt-get install -y code && \
echo "🔍 Checking installed VS Code..." && \
which code && \
ls -la /usr/bin/code && \
echo "✅ VS Code installed via apt!" && \
# Cleanup
apt-get autoremove -y && \
apt-get autoclean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# 验证关键软件安装是否成功
RUN echo "🔍 Verifying installations..." && \
# VS Code - 检查多个可能的路径
CODE_PATH="" && \
if [ -x /usr/bin/code ]; then CODE_PATH="/usr/bin/code"; \
elif [ -x /usr/share/code/bin/code ]; then CODE_PATH="/usr/share/code/bin/code"; \
elif command -v code >/dev/null 2>&1; then CODE_PATH="$(command -v code)"; \
fi && \
if [ -z "$CODE_PATH" ]; then \
echo "❌ VS Code installation failed! No 'code' binary found" && \
echo "📂 Searching for code binary..." && \
find /usr -name "code" -type f 2>/dev/null | head -10 && \
exit 1; \
fi && \
echo "✅ VS Code found at: $CODE_PATH" && \
# 如果 /usr/bin/code 不存在,创建软链接
if [ ! -x /usr/bin/code ] && [ -n "$CODE_PATH" ]; then \
ln -sf "$CODE_PATH" /usr/bin/code && \
echo "🔗 Created symlink: /usr/bin/code -> $CODE_PATH"; \
fi && \
# Chromium
if [ ! -x /usr/bin/chromium ]; then \
echo "❌ Chromium installation failed!" && exit 1; \
fi && \
echo "✅ Chromium: $(/usr/bin/chromium --version 2>/dev/null || echo 'installed')" && \
echo "🎉 All critical software verified!"
RUN mkdir -p /home/user/.config/Code/User
COPY ./settings.json /home/user/.config/Code/User/settings.json
# Copy desktop background for XFCE
COPY ./wallpaper.jpeg /usr/share/backgrounds/xfce/wallpaper.jpeg
# Copy xfce4-desktop.xml to system-level config
RUN mkdir -p /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/
COPY ./xfce4-desktop.xml /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml
# ========== 系统级 XFCE 配置 (不受用户目录挂载影响) ==========
RUN mkdir -p /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/ && \
# Disable screen saver
echo '\n\n \n \n \n \n \n \n \n' > /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-screensaver.xml && \
# Disable power management screen dimming
echo '\n\n \n \n \n \n \n \n \n \n \n' > /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-power-manager.xml && \
# Configure XFCE panel with top bar (panel-1) and bottom dock (panel-2)
echo '\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n' > /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml && \
# Create launcher directories for bottom dock (系统级)
mkdir -p /etc/xdg/xfce4/panel/launcher-17 && \
mkdir -p /etc/xdg/xfce4/panel/launcher-18 && \
mkdir -p /etc/xdg/xfce4/panel/launcher-19 && \
mkdir -p /etc/xdg/xfce4/panel/launcher-20 && \
# Create terminal launcher (plugin-17)
printf '[Desktop Entry]\nVersion=1.0\nType=Application\nExec=xfce4-terminal\nIcon=org.xfce.terminalemulator\nStartupNotify=true\nTerminal=false\nCategories=Utility;X-XFCE;\nName=Terminal Emulator\nComment=Use the command line\n' > /etc/xdg/xfce4/panel/launcher-17/xfce4-terminal-emulator.desktop && \
# Create file manager launcher (plugin-18)
printf '[Desktop Entry]\nVersion=1.0\nType=Application\nExec=thunar\nIcon=org.xfce.filemanager\nStartupNotify=true\nTerminal=false\nCategories=Utility;X-XFCE;\nName=File Manager\nComment=Browse the file system\n' > /etc/xdg/xfce4/panel/launcher-18/xfce4-file-manager.desktop && \
# Create Chromium launcher (plugin-19) - replaces default Web Browser
printf '[Desktop Entry]\nVersion=1.0\nType=Application\nName=Chromium\nComment=Web Browser\nExec=/usr/bin/chromium-browser-launcher\nIcon=chromium\nTerminal=false\nCategories=Network;WebBrowser;\nStartupNotify=true\n' > /etc/xdg/xfce4/panel/launcher-19/chromium.desktop && \
# Create app finder launcher (plugin-20)
printf '[Desktop Entry]\nVersion=1.0\nType=Application\nExec=xfce4-appfinder\nIcon=org.xfce.appfinder\nStartupNotify=true\nTerminal=false\nCategories=Utility;X-XFCE;\nName=Application Finder\nComment=Find and launch applications\n' > /etc/xdg/xfce4/panel/launcher-20/xfce4-appfinder.desktop && \
# Create system-wide Chromium desktop entry
mkdir -p /usr/share/applications && \
printf "[Desktop Entry]\nVersion=1.0\nType=Application\nName=Chromium\nComment=Web Browser\nExec=/usr/bin/chromium-browser-launcher\nIcon=chromium\nTerminal=false\nCategories=Network;WebBrowser;" > /usr/share/applications/chromium-browser.desktop && \
ln -sf /usr/share/applications/chromium-browser.desktop /usr/share/applications/chromium.desktop && \
chmod 644 /usr/share/applications/chromium-browser.desktop /usr/share/applications/chromium.desktop && \
# Set Chromium as default web browser (系统级)
mkdir -p /usr/share/applications && \
echo "[Default Applications]\ntext/html=chromium.desktop\ntext/xml=chromium.desktop\napplication/xhtml+xml=chromium.desktop\napplication/xml=chromium.desktop\napplication/rss+xml=chromium.desktop\napplication/rdf+xml=chromium.desktop\nimage/gif=chromium.desktop\nimage/jpeg=chromium.desktop\nimage/png=chromium.desktop\nx-scheme-handler/http=chromium.desktop\nx-scheme-handler/https=chromium.desktop\nx-scheme-handler/ftp=chromium.desktop\nx-scheme-handler/chrome=chromium.desktop\nvideo/webm=chromium.desktop\napplication/x-xpinstall=chromium.desktop" > /usr/share/applications/mimeapps.list
# Create desktop icons for terminal, VS Code, and Chrome
RUN mkdir -p /home/user/Desktop /usr/share/applications && \
# Create safe desktop files in system directory (not in user's home)
printf "[Desktop Entry]\nVersion=1.0\nType=Application\nName=Terminal\nComment=Terminal Emulator\nExec=xfce4-terminal\nIcon=utilities-terminal\nTerminal=false\nCategories=System;TerminalEmulator;" > /usr/share/applications/user-terminal.desktop && \
printf "[Desktop Entry]\nVersion=1.0\nType=Application\nName=Visual Studio Code\nComment=Code Editor\nExec=/usr/bin/code --no-sandbox --user-data-dir=/home/user/.config/Code\nIcon=vscode\nTerminal=false\nCategories=Development;IDE;" > /usr/share/applications/user-vscode.desktop && \
chmod 644 /usr/share/applications/user-terminal.desktop /usr/share/applications/user-vscode.desktop && \
# Create symbolic links from Desktop to system apps (安全软链接)
ln -sf /usr/share/applications/user-terminal.desktop /home/user/Desktop/terminal.desktop && \
ln -sf /usr/share/applications/user-vscode.desktop /home/user/Desktop/vscode.desktop && \
ln -sf /usr/share/applications/chromium-browser.desktop /home/user/Desktop/chromium.desktop && \
chown -R user:user /home/user/Desktop
# ========== 创建骨架目录 /etc/skel-user-desktop ==========
# 将 /home/user 下的关键配置备份到此目录
# 启动时,如果检测到 /home/user 是空的(被挂载覆盖),则从这里恢复
# 这样可以解决:
# 1. 挂载空目录到 /home/user 导致桌面图标消失
# 2. 挂载空目录到 /home/user 导致 VNC 花屏(缺少关键缓存目录)
RUN mkdir -p /etc/skel-user-desktop && \
# 复制整个 /home/user 目录结构到骨架目录
cp -a /home/user/. /etc/skel-user-desktop/ && \
# 确保骨架目录只读(防止意外修改)
chmod 755 /etc/skel-user-desktop && \
echo "✅ Skeleton directory created at /etc/skel-user-desktop"
# Copy firefox policies
COPY firefox-policies.json /usr/lib/firefox-esr/distribution/policies.json
COPY firefox-autoconfig.js /usr/lib/firefox-esr/defaults/pref/autoconfig.js
COPY firefox.cfg /usr/lib/firefox-esr/firefox.cfg
# Code interpretor environment setup
# 系统依赖和Python 3安装 (merged for smaller image size)
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -y --no-install-recommends \
build-essential curl git util-linux jq sudo fonts-noto-cjk fonts-noto-color-emoji \
systemd ca-certificates chrony r-base software-properties-common \
libzmq3-dev libzmq5 pkg-config && \
# 使用 Debian 12 默认的 Python 3.11 (更稳定)
apt-get install -y python3 python3-dev python3-venv python3-pip && \
ln -sf /usr/bin/python3 /usr/bin/python && \
# Python/Jupyter services and kernels removed, but Python3 environment kept
# Clean up package cache to reduce image size:
apt-get autoremove -y && \
apt-get autoclean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# 安装 Node.js 22.x (现在支持,因为不再依赖 ijavascript)
RUN apt-get update && \
apt-get install -y curl gnupg ca-certificates && \
# 添加 NodeSource PPA for Node.js 22.x
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
# 安装 Node.js (npm 会一同被安装)
apt-get install -y nodejs && \
# 更新证书
update-ca-certificates && \
# Clean up package cache to reduce image size:
apt-get autoremove -y && \
apt-get autoclean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# 使用 npm 安装 pnpm(使用阿里云镜像)
RUN npm install -g pnpm@latest && \
# 创建 pnpm 环境配置脚本(用户通过终端使用时会加载)
echo '#!/bin/sh' > /etc/profile.d/pnpm-env.sh && \
echo '# PNPM environment configuration' >> /etc/profile.d/pnpm-env.sh && \
echo 'export PNPM_HOME="/home/user/.local/share/pnpm"' >> /etc/profile.d/pnpm-env.sh && \
echo 'export npm_config_registry="https://registry.npmmirror.com"' >> /etc/profile.d/pnpm-env.sh && \
chmod +x /etc/profile.d/pnpm-env.sh && \
# Clean up npm cache
npm cache clean --force && \
echo "Node.js version: $(node -v)" && \
echo "npm version: $(npm -v)" && \
echo "pnpm version: $(pnpm -v)"
# 环境变量设置
ENV R_VERSION=4.4.2
ENV R_HOME=/opt/R/${R_VERSION}
# # 创建默认用户
# RUN useradd -ms /bin/bash user && \
# usermod -aG sudo user && \
# passwd -d user && \
# echo "user ALL=(ALL:ALL) NOPASSWD: ALL" >>/etc/sudoers
# 设置目录权限(安全配置)
RUN mkdir -p /home/user && \
chown -R user:user /home/user && \
chmod 755 /home/user && \
chmod 755 /usr/local
# Python/Jupyter components removed
# JavaScript/TypeScript 使用 Deno kernel (不需要安装 ijavascript)
# Deno kernel 在上面已经安装并配置完成
# Deno runtime (for JavaScript/TypeScript support)
COPY --from=denoland/deno:bin-2.5.6 /deno /usr/bin/deno
RUN chmod +x /usr/bin/deno && \
# Clean up deno cache
rm -rf /root/.cache/deno
# Note: deno.json not copied as Jupyter is removed
# Bash Kernel (Python-based) removed
# R Kernel
# RUN R -e "install.packages('IRkernel', repos='https://cloud.r-project.org'); IRkernel::installspec(user = FALSE, name = 'r', displayname = 'R')"
# envd 服务已删除 - 不再需要环境守护进程
# 配置 chrony
RUN mkdir -p /etc/chrony
RUN echo 'makestep 1 -1' > /etc/chrony/chrony.conf
RUN mkdir -p /etc/systemd/system/chrony.service.d
RUN echo '[Service]\n\
ExecStart=\n\
ExecStart=/usr/sbin/chronyd\n\
User=root\n\
Group=root' > /etc/systemd/system/chrony.service.d/override.conf
RUN systemctl enable chrony
# Java 配置 (完整 JDK 工具集)
RUN apt-get update && \
apt-get install -y openjdk-17-jdk && \
# 验证安装
java -version && \
javac -version && \
echo "✅ OpenJDK 17 installed with full toolset" && \
# 确认 javac 存在
which javac && \
ls -la /usr/lib/jvm/java-17-openjdk-*/bin/javac && \
# 清理缓存(但不 autoremove,避免删除 JDK 依赖)
rm -rf /var/lib/apt/lists/*
# ============================================================================
# 📦 Maven 配置(使用阿里云镜像加速)
# ============================================================================
# 安装 Maven
RUN apt-get update && apt-get install -y maven && \
rm -rf /var/lib/apt/lists/* && \
echo "✅ Maven installed: $(mvn --version | head -1)"
# 创建 Maven 配置目录(系统级和用户级)
RUN mkdir -p /home/user/.m2 && \
mkdir -p /etc/maven
# 复制 Maven 配置文件(阿里云镜像已配置)
COPY settings.xml /home/user/.m2/settings.xml
COPY settings.xml /etc/maven/settings.xml
# 设置权限
RUN chown -R user:user /home/user/.m2 && \
chmod 644 /home/user/.m2/settings.xml && \
echo "✅ Maven configured with Aliyun mirror"
# 配置自动登录
RUN mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d
RUN echo '[Service]\n\
ExecStart=\n\
ExecStart=-/sbin/agetty --noissue --autologin root %I 115200,38400,9600 vt102' \
> /etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf
# Python server components removed
# IPython/Jupyter config files removed
ENV DISPLAY=:0
# 设置全局输入法环境变量(容器级别 - 纯 fcitx)
ENV GTK_IM_MODULE=fcitx \
QT_IM_MODULE=fcitx \
XMODIFIERS=@im=fcitx \
INPUT_METHOD=fcitx \
SDL_IM_MODULE=fcitx \
GLFW_IM_MODULE=ibus
# 配置 D-Bus 和 PolicyKit(已在前面安装 dbus-x11)
RUN mkdir -p /etc/dbus-1/session.d && \
printf '\n\n\n \n \n \n \n \n \n' > /etc/dbus-1/session.d/user-session.conf && \
printf '\n\n\n \n \n \n \n \n \n \n' > /etc/dbus-1/session.d/allow-root.conf && \
# Set proper permissions
chown -R root:root /etc/polkit-1 /etc/dbus-1 && \
chmod 755 /etc/polkit-1/localauthority/50-local.d /etc/dbus-1/session.d && \
chmod 644 /etc/polkit-1/localauthority/50-local.d/*.pkla /etc/dbus-1/session.d/*.conf
# 安装 agent_runner 运行时依赖(与 rcoder/Dockerfile-runner 保持一致)
RUN apt-get update && apt-get install -y \
ca-certificates \
curl \
wget \
gnupg \
unzip \
zip \
tzdata \
lsof \
iproute2 \
net-tools \
nginx \
&& rm -rf /var/lib/apt/lists/*
# 设置系统时区为东八区(Asia/Shanghai)
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
# 清理默认nginx配置以避免冲突
RUN rm -f /etc/nginx/sites-enabled/default && \
rm -f /etc/nginx/conf.d/default.conf
# 配置 npm 使用国内镜像源(系统级配置)
RUN npm config set registry https://registry.npmmirror.com && \
# 创建 npm 环境配置脚本(用户通过终端使用时会加载)
echo '#!/bin/sh' > /etc/profile.d/npm-env.sh && \
echo '# NPM environment configuration' >> /etc/profile.d/npm-env.sh && \
echo 'export npm_config_registry=https://registry.npmmirror.com' >> /etc/profile.d/npm-env.sh && \
chmod +x /etc/profile.d/npm-env.sh
# 设置工作目录并确保权限
RUN mkdir -p /app/computer-project-workspace /home/user/.cache && \
chown -R user:user /app /home/user
# 安装 Bun 到全局路径(需要 root)
ENV BUN_CONFIG_REGISTRY="https://registry.npmmirror.com" \
BUN_CONFIG_INSTALL_CACHE_DIR="/home/user/.cache/bun/install" \
BUN_CONFIG_GLOBAL_CACHE_DIR="/home/user/.cache/bun/global" \
BUN_INSTALL="/usr/local" \
PATH="/usr/local/bin:$PATH"
# 使用 npm 从阿里云 npmmirror 安装 bun 的 prebuilt 二进制,避免从 bun.sh/install 走 GitHub 下载不稳定
# bun 的 npm 包通过 optionalDependencies 引入平台特定二进制(如 @oven/bun-linux-x64),npmmirror 已镜像
# --prefix=/usr/local 让 bun 安装到 /usr/local/bin/bun(与上方 BUN_INSTALL 路径保持一致)
RUN npm install -g --prefix=/usr/local --registry=https://registry.npmmirror.com bun && \
/usr/local/bin/bun --version && \
# 创建 bun 环境配置脚本(用户通过终端使用时会加载)
echo '#!/bin/sh' > /etc/profile.d/bun-env.sh && \
echo '# Bun environment configuration' >> /etc/profile.d/bun-env.sh && \
echo 'export BUN_CONFIG_REGISTRY="https://registry.npmmirror.com"' >> /etc/profile.d/bun-env.sh && \
echo 'export BUN_CONFIG_INSTALL_CACHE_DIR="/home/user/.cache/bun/install"' >> /etc/profile.d/bun-env.sh && \
echo 'export BUN_CONFIG_GLOBAL_CACHE_DIR="/home/user/.cache/bun/global"' >> /etc/profile.d/bun-env.sh && \
echo 'export BUN_INSTALL="/usr/local"' >> /etc/profile.d/bun-env.sh && \
chmod +x /etc/profile.d/bun-env.sh && \
# Clean up npm cache
npm cache clean --force
# 安装全局 npm 包(需要 root,因为安装到 /usr/lib/node_modules)
RUN npm install -g @zed-industries/claude-code-acp@latest && \
npm install -g @anthropic-ai/claude-code@latest && \
npm install -g chrome-devtools-mcp@latest && \
# 安装 pptxgenjs 和 sharp(全局安装,不受 /home/user 挂载影响)
npm install -g pptxgenjs sharp && \
# Clean up npm cache
npm cache clean --force && \
echo "✅ pptxgenjs and sharp installed globally"
# # 安装 Playwright Chromium(独立 RUN 以利用 Docker 缓存)
# RUN npx playwright install chromium && \
# echo "✅ Playwright Chromium installed"
# 安装 OpenSSL 开发包和构建依赖(mcp-stdio-proxy 需要)
RUN apt-get update && apt-get install -y \
libssl-dev \
pkg-config \
build-essential && \
rm -rf /var/lib/apt/lists/*
# 安装 Rust toolchain(需要 root)
# 注意:mcp-stdio-proxy 的安装已移至 Dockerfile,以便频繁更新时无需重建基础镜像
ENV RUSTUP_HOME=/opt/rustup \
CARGO_HOME=/opt/cargo \
PATH="/opt/cargo/bin:$PATH"
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
chmod -R 755 /opt/rustup /opt/cargo && \
# 确保 user 可以使用 cargo
chmod -R o+rX /opt/cargo /opt/rustup
# 安装 uv 到全局路径(需要 root)
# 注意:UV_CACHE_DIR 在后续会根据 root/user 模式分别设置,这里不设置全局默认
# 使用阿里云 PyPI 镜像源(与 pip 保持一致,稳定可靠,无限流限制)
# 使用 pip 安装(避免从 astral.sh/GitHub 下载二进制慢/不稳定),pip 全局安装默认放 /usr/local/bin
ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple
RUN pip3 install --break-system-packages --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple uv && \
uv --version && \
# 创建 uv 环境配置脚本(用户通过终端使用时会加载)
echo '#!/bin/sh' > /etc/profile.d/uv-env.sh && \
echo '# UV environment configuration' >> /etc/profile.d/uv-env.sh && \
echo 'export UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple' >> /etc/profile.d/uv-env.sh && \
echo 'export UV_TOOL_DIR=/home/user/.local/share/uv/tools' >> /etc/profile.d/uv-env.sh && \
echo 'export UV_CACHE_DIR=/home/user/.cache/uv' >> /etc/profile.d/uv-env.sh && \
chmod +x /etc/profile.d/uv-env.sh
# 添加 cargo bin 和 uv 到 PATH
# 添加所有工具安装路径到 PATH:
# - /root/.local/bin: root 用户使用 uv tool install 时的可执行文件位置
# - /home/user/.local/bin: user 用户使用 uv tool install 时的可执行文件位置
# - /opt/cargo/bin: Rust cargo 安装的工具
# - /usr/local/bin: 系统级工具(bun, uv, pnpm 等)
ENV PNPM_HOME="/home/user/.local/share/pnpm" \
PATH="/root/.local/bin:/home/user/.local/bin:/opt/cargo/bin:/usr/local/bin:${PATH}"
# ============================================================================
# 🔊 pcmflux 音频流服务安装
# ============================================================================
# 安装 pcmflux Python 包(需要 root,用于编译 C++ 扩展)
# 使用 --break-system-packages 绕过 Debian 12 的 PEP 668 限制
RUN pip3 install --break-system-packages websockets && \
if [ "${USE_GITHUB_MIRROR}" = "true" ]; then \
pip3 install --break-system-packages git+${GITHUB_MIRROR_URL}linuxserver/pcmflux.git; \
else \
pip3 install --break-system-packages git+https://github.com/linuxserver/pcmflux.git; \
fi && \
echo "✅ pcmflux audio streaming installed"
# 暴露音频流端口
EXPOSE 6090
EXPOSE 6089
# 暴露 IME 服务端口
EXPOSE 6091
# ============================================================================
# 预安装全局工具(root 模式)- 系统级别,不受 /home/user 挂载影响
# ============================================================================
# 预安装 context7 MCP 工具(Bun 全局安装)
# 代码里通过 bunx 调用 MCP,预装到 bun 全局后 bunx 可直接命中 ~/.bun/install/global,无需运行时再下载
RUN bun add -g @upstash/context7-mcp
# 预安装 mcp-server-fetch(UV 全局安装到系统目录)
ENV UV_TOOL_DIR=/usr/local/share/uv/tools \
UV_CACHE_DIR=/var/cache/uv
RUN mkdir -p "$UV_TOOL_DIR" "$UV_CACHE_DIR" && \
uv tool install mcp-server-fetch && \
echo "✅ mcp-server-fetch installed to $UV_TOOL_DIR"
# ============================================================================
# 🎯 切换到普通用户 - 后续操作以 user 身份执行
# ============================================================================
USER user
WORKDIR /home/user
# 配置 pnpm(用户级配置)
RUN pnpm config set registry https://registry.npmmirror.com && \
pnpm config set store-dir /home/user/.cache/pnpm
# 配置 Bun 用户配置文件
RUN mkdir -p /home/user/.cache/bun/install /home/user/.cache/bun/global && \
echo '[install]' > /home/user/.bunfig.toml && \
echo 'registry = "https://registry.npmmirror.com"' >> /home/user/.bunfig.toml && \
echo '' >> /home/user/.bunfig.toml && \
echo '[install.cache]' >> /home/user/.bunfig.toml && \
echo 'dir = "/home/user/.cache/bun"' >> /home/user/.bunfig.toml && \
cat /home/user/.bunfig.toml && \
echo "✅ Bun configured for user"
# 配置 UV 用户环境(用户自己安装的工具会保存在这里,挂载后保留)
ENV UV_TOOL_DIR=/home/user/.local/share/uv/tools \
UV_CACHE_DIR=/home/user/.cache/uv
RUN mkdir -p "$UV_TOOL_DIR" "$UV_CACHE_DIR" && \
echo "✅ UV configured for user: tools in $UV_TOOL_DIR, cache in $UV_CACHE_DIR"
# 创建 Claude Code 配置目录
RUN mkdir -p /home/user/.claude && \
echo '{"permissions":{"deny":["WebFetch", "WebSearch"]}}' > /home/user/.claude/settings.json
# ============================================================================
# 🎯 切回 root - 更新骨架目录
# ============================================================================
USER root
# 更新骨架目录(包含 user 阶段创建的配置)
# 这样当宿主机挂载空目录到 /home/user 时,start-up.sh 可以从这里恢复
RUN echo "📦 Updating skeleton directory with user configs..." && \
cp -a /home/user/. /etc/skel-user-desktop/ && \
echo "✅ Skeleton directory updated at /etc/skel-user-desktop"
# ============================================================================
# 基础镜像构建完成
# ============================================================================
# 此处不设置 ENTRYPOINT,由最终镜像设置
# 暴露 agent_runner 端口(供最终镜像使用)
EXPOSE 8086
# 设置 agent_runner 默认端口环境变量
ENV AGENT_RUNNER_PORT=8086
# 设置默认工作目录
WORKDIR /home/user