Spaces:
Paused
Paused
| 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 </dev/urandom | head -c 16) | |
| htpasswd -bc /etc/nginx/.htpasswd admin "$RANDOM_PASS" | |
| echo "=================================================================" | |
| echo "⚠️ 警告: 未检测到 WEB_USER 或 WEB_PASS 环境变量!" | |
| echo "已触发安全保护机制,自动生成临时随机密码 (每次重启都会更换)。" | |
| echo "默认账号: admin" | |
| echo "随机密码: $RANDOM_PASS" | |
| echo "=================================================================" | |
| fi | |
| service nginx start | |
| # --- 步骤 F: 启动核心业务进程 --- | |
| echo ">>> 启动飞书 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"] |