888 lines
54 KiB
Docker
888 lines
54 KiB
Docker
# ============================================================================
|
||
# 基础镜像 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 '<?xml version="1.0" encoding="UTF-8"?>\n<channel name="xfce4-session" version="1.0">\n <property name="general" type="empty">\n <property name="ShowRootWarning" type="bool" value="false"/>\n </property>\n</channel>' > /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 '<?xml version="1.0" encoding="UTF-8"?>\n<channel name="xfce4-screensaver" version="1.0">\n <property name="saver" type="empty">\n <property name="enabled" type="bool" value="false"/>\n <property name="mode" type="int" value="0"/>\n </property>\n <property name="lock" type="empty">\n <property name="enabled" type="bool" value="false"/>\n </property>\n</channel>' > /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-screensaver.xml && \
|
||
# Disable power management screen dimming
|
||
echo '<?xml version="1.0" encoding="UTF-8"?>\n<channel name="xfce4-power-manager" version="1.0">\n <property name="xfce4-power-manager" type="empty">\n <property name="blank-on-ac" type="int" value="0"/>\n <property name="blank-on-battery" type="int" value="0"/>\n <property name="dpms-enabled" type="bool" value="false"/>\n <property name="dpms-on-ac-sleep" type="uint" value="0"/>\n <property name="dpms-on-ac-off" type="uint" value="0"/>\n <property name="dpms-on-battery-sleep" type="uint" value="0"/>\n <property name="dpms-on-battery-off" type="uint" value="0"/>\n </property>\n</channel>' > /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 '<?xml version="1.0" encoding="UTF-8"?>\n<channel name="xfce4-panel" version="1.0">\n <property name="configver" type="int" value="2"/>\n <property name="panels" type="array">\n <value type="int" value="1"/>\n <value type="int" value="2"/>\n <property name="dark-mode" type="bool" value="true"/>\n <property name="panel-1" type="empty">\n <property name="position" type="string" value="p=6;x=0;y=0"/>\n <property name="length" type="uint" value="100"/>\n <property name="position-locked" type="bool" value="true"/>\n <property name="icon-size" type="uint" value="16"/>\n <property name="size" type="uint" value="26"/>\n <property name="plugin-ids" type="array">\n <value type="int" value="1"/>\n <value type="int" value="2"/>\n <value type="int" value="3"/>\n <value type="int" value="4"/>\n <value type="int" value="5"/>\n <value type="int" value="6"/>\n <value type="int" value="7"/>\n <value type="int" value="8"/>\n </property>\n </property>\n <property name="panel-2" type="empty">\n <property name="autohide-behavior" type="uint" value="1"/>\n <property name="position" type="string" value="p=10;x=0;y=0"/>\n <property name="length" type="uint" value="1"/>\n <property name="position-locked" type="bool" value="true"/>\n <property name="size" type="uint" value="48"/>\n <property name="plugin-ids" type="array">\n <value type="int" value="15"/>\n <value type="int" value="16"/>\n <value type="int" value="17"/>\n <value type="int" value="18"/>\n <value type="int" value="19"/>\n <value type="int" value="20"/>\n <value type="int" value="21"/>\n </property>\n </property>\n </property>\n <property name="plugins" type="empty">\n <property name="plugin-1" type="string" value="applicationsmenu"/>\n <property name="plugin-2" type="string" value="tasklist">\n <property name="grouping" type="uint" value="1"/>\n </property>\n <property name="plugin-3" type="string" value="separator">\n <property name="expand" type="bool" value="true"/>\n <property name="style" type="uint" value="0"/>\n </property>\n <property name="plugin-4" type="string" value="systray">\n <property name="square-icons" type="bool" value="true"/>\n </property>\n <property name="plugin-5" type="string" value="separator">\n <property name="style" type="uint" value="0"/>\n </property>\n <property name="plugin-6" type="string" value="notification-plugin"/>\n <property name="plugin-7" type="string" value="clock"/>\n <property name="plugin-8" type="string" value="actions"/>\n <property name="plugin-15" type="string" value="showdesktop"/>\n <property name="plugin-16" type="string" value="separator"/>\n <property name="plugin-17" type="string" value="launcher">\n <property name="items" type="array">\n <value type="string" value="xfce4-terminal-emulator.desktop"/>\n </property>\n </property>\n <property name="plugin-18" type="string" value="launcher">\n <property name="items" type="array">\n <value type="string" value="xfce4-file-manager.desktop"/>\n </property>\n </property>\n <property name="plugin-19" type="string" value="launcher">\n <property name="items" type="array">\n <value type="string" value="chromium.desktop"/>\n </property>\n </property>\n <property name="plugin-20" type="string" value="launcher">\n <property name="items" type="array">\n <value type="string" value="xfce4-appfinder.desktop"/>\n </property>\n </property>\n <property name="plugin-21" type="string" value="directorymenu"/>\n </property>\n</channel>' > /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 '<!-- Session bus for user -->\n<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">\n<busconfig>\n <policy user="user">\n <allow own="*"/>\n <allow own_prefix="*"/>\n <allow send_destination="*"/>\n <allow receive_sender="*"/>\n </policy>\n</busconfig>' > /etc/dbus-1/session.d/user-session.conf && \
|
||
printf '<!-- Allow root to access user session bus for fcitx5 input method -->\n<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">\n<busconfig>\n <policy user="root">\n <allow own="*"/>\n <allow own_prefix="*"/>\n <allow send_destination="*"/>\n <allow receive_sender="*"/>\n <allow eavesdrop="true"/>\n </policy>\n</busconfig>' > /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
|