FROM python:3.11-slim # 1. 安装 Node.js 及 Nginx 防盗门依赖组件 RUN apt-get update && apt-get install -y git curl nginx apache2-utils && \ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ apt-get install -y nodejs && \ rm -rf /var/lib/apt/lists/* COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ WORKDIR /app ENV UPDATE_CACHE_BUSTER="2026-06-11" RUN git clone https://github.com/nousresearch/hermes-agent.git . # 2. 编译 Web 界面并安装后端 RUN cd web && npm install && npm run build RUN uv pip install --system -e ".[all]" RUN uv pip install --system huggingface_hub # ================================================================= # 核心环境变量与 CPU 环境优化 # ================================================================= ENV HOST=0.0.0.0 ENV PORT=7860 ENV HERMES_PORT=8000 ENV HERMES_GATEWAY_PLATFORM=feishu ENV GATEWAY_ALLOW_ALL_USERS=true # 强制禁用 GPU 探测,优化纯 CPU 环境下的进程调度与内存占用 ENV CUDA_VISIBLE_DEVICES="-1" ENV OMP_NUM_THREADS=4 # ================================================================= # 3. 完美容灾、云端备份与防篡改启动脚本 # ================================================================= RUN cat <<'EOF' > /start.sh #!/bin/bash mkdir -p /root/.hermes # --- 步骤 A: 开机自动从云端 Dataset 恢复数据 (带try-except终极防卡死补丁) --- if [ -n "$HF_DATASET" ] && [ -n "$HF_TOKEN" ]; then echo ">>> 探测到云端备份配置,正在尝试恢复数据..." python3 -c " import os, shutil from huggingface_hub import snapshot_download try: snapshot_download(repo_id=\"$HF_DATASET\", repo_type=\"dataset\", local_dir=\"/root/.hermes\", token=\"$HF_TOKEN\") print('>>> 云端数据同步成功。') except Exception as e: print('>>> [警告] 云端历史数据存在大小不匹配或损坏,已自动跳过并执行干净启动。') " else echo ">>> 未完全配置 HF_DATASET 或 HF_TOKEN,跳过恢复。" fi # --- 步骤 B: 原有的防篡改与初始配置保底逻辑 --- if [ ! -f /root/.hermes/config.yaml ]; then echo ">>> 未发现本地配置,生成默认 Gemini 配置文件..." cat << 'CONF' > /root/.hermes/config.yaml provider: gemini model: gemini-2.5-flash auxiliary: title_generation: provider: gemini model: gemini-2.5-flash compression: provider: gemini model: gemini-2.5-flash session_search: provider: gemini model: gemini-2.5-flash CONF fi # --- 步骤 C: 定义优雅退出的 Trap 机制 (防止被杀时丢数据) --- cleanup() { echo ">>> 接收到终止信号 (SIGTERM)!准备冻结状态并执行紧急备份..." service nginx stop kill -SIGTERM $GATEWAY_PID $DASHBOARD_PID 2>/dev/null wait $GATEWAY_PID $DASHBOARD_PID 2>/dev/null if [ -n "$HF_DATASET" ] && [ -n "$HF_TOKEN" ]; then echo ">>> 执行终极数据持久化 (隔离运行锁)..." # 隔离可能导致 I/O 错误的锁文件 mv /root/.hermes/gateway.lock /tmp/gateway.lock.bak 2>/dev/null mv /root/.hermes/gateway.log /tmp/gateway.log.bak 2>/dev/null python3 -c "from huggingface_hub import HfApi; api=HfApi(); api.upload_folder(folder_path=\"/root/.hermes\", repo_id=\"$HF_DATASET\", repo_type=\"dataset\", ignore_patterns=[\"*.db-wal\", \"*.db-shm\", \"*.db-journal\", \"pairing*\", \"*.tmp\", \"*.mp4\", \"*.avi\", \"*.mov\", \"*.mkv\", \"*.wav\", \"*.mp3\", \"*.lock\"], commit_message=\"Emergency backup on exit\", token=\"$HF_TOKEN\")" fi echo ">>> 备份完成,容器安全退出。" exit 0 } trap cleanup SIGTERM SIGINT # --- 步骤 D: 启动后台定时备份 (每15分钟 - 完美闭合版) --- (while true; do sleep 900 if [ -n "$HF_DATASET" ] && [ -n "$HF_TOKEN" ]; then echo ">>> 触发每15分钟定时增量同步 (物理隔离锁文件)..." # 1. 移走文件,避开扫描 mv /root/.hermes/gateway.lock /tmp/gateway.lock.bak 2>/dev/null mv /root/.hermes/gateway.log /tmp/gateway.log.bak 2>/dev/null # 2. 执行上传 python3 -c "from huggingface_hub import HfApi; api=HfApi(); api.upload_folder(folder_path=\"/root/.hermes\", repo_id=\"$HF_DATASET\", repo_type=\"dataset\", ignore_patterns=[\"*.db-wal\", \"*.db-shm\", \"*.db-journal\", \"pairing*\", \"*.tmp\", \"*.mp4\", \"*.avi\", \"*.mov\", \"*.mkv\", \"*.wav\", \"*.mp3\", \"*.lock\"], commit_message=\"Auto routine backup\", token=\"$HF_TOKEN\")" # 3. 还原文件 mv /tmp/gateway.lock.bak /root/.hermes/gateway.lock 2>/dev/null mv /tmp/gateway.log.bak /root/.hermes/gateway.log 2>/dev/null fi done) & SYNC_PID=$! # --- 步骤 E: 配置并启动 Nginx 防盗门 --- echo ">>> 配置 Nginx 访问拦截策略..." cat << 'NGINX' > /etc/nginx/sites-available/default server { listen 7860; location / { auth_basic "Restricted Access - Please Login"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://127.0.0.1:7861; proxy_http_version 1.1; # 维持 WebSocket 长连接 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 核心修复:彻底欺骗后端,伪装成本地请求,绕过所有 Host 和 Origin 跨域拦截 proxy_set_header Host 127.0.0.1; proxy_set_header Origin "http://127.0.0.1:7861"; # 关闭缓冲,让 SSE 实时日志和打字机效果秒级透传 proxy_buffering off; proxy_cache off; chunked_transfer_encoding on; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 保持超长等待时间,防止大模型思考过久断开 proxy_read_timeout 3600; proxy_send_timeout 3600; } } NGINX if [ -n "$WEB_USER" ] && [ -n "$WEB_PASS" ]; then htpasswd -bc /etc/nginx/.htpasswd "$WEB_USER" "$WEB_PASS" echo ">>> 已设置自定义 Web 登录密码。" else RANDOM_PASS=$(tr -dc A-Za-z0-9 >> 启动飞书 Gateway 守护进程..." env PORT=8000 HERMES_PORT=8000 hermes gateway > /root/.hermes/gateway.log 2>&1 & GATEWAY_PID=$! echo ">>> 启动 Dashboard 面板 (绑定本地隐藏端口 7861)..." hermes dashboard --port 7861 --host 127.0.0.1 --insecure & DASHBOARD_PID=$! # 双进程看守,任何一个崩溃即触发全面紧急备份退出 wait $DASHBOARD_PID $GATEWAY_PID EOF RUN chmod +x /start.sh # 封装快捷一键手动备份口令(供未来飞书或Web终端直接调用) # 封装快捷一键手动备份口令(供未来飞书或Web终端直接调用) RUN echo '#!/bin/bash\nmv /root/.hermes/gateway.lock /tmp/gateway.lock.bak 2>/dev/null\nmv /root/.hermes/gateway.log /tmp/gateway.log.bak 2>/dev/null\npython3 -c "from huggingface_hub import HfApi; api=HfApi(); api.upload_folder(folder_path=\"/root/.hermes\", repo_id=\"$HF_DATASET\", repo_type=\"dataset\", ignore_patterns=[\"*.db-wal\", \"*.db-shm\", \"*.db-journal\", \"pairing*\", \"*.tmp\", \"*.mp4\", \"*.avi\", \"*.mov\", \"*.mkv\", \"*.wav\", \"*.mp3\", \"*.lock\"], commit_message=\"Manual backup via Feishu\", token=\"$HF_TOKEN\")"\nmv /tmp/gateway.lock.bak /root/.hermes/gateway.lock 2>/dev/null\nmv /tmp/gateway.log.bak /root/.hermes/gateway.log 2>/dev/null' > /usr/local/bin/force-backup && chmod +x /usr/local/bin/force-backup EXPOSE 7860 CMD ["/start.sh"]