File size: 8,085 Bytes
3e22808
 
c788633
 
c8abac0
 
 
 
3e22808
c788633
0a2e169
944926e
3e22808
c8abac0
f4851b0
c8abac0
3e22808
c848d83
3e22808
c860065
c848d83
c860065
a6f5f9d
 
c848d83
14947a4
87572af
c848d83
 
 
87572af
c848d83
c788633
c848d83
 
 
 
d2ae842
3c4691b
c848d83
 
3c4691b
 
 
 
 
 
 
 
 
c848d83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3bd402b
c848d83
 
 
c788633
3c4691b
c848d83
 
 
7bd4402
 
 
 
 
e34cd33
c848d83
 
 
 
 
 
3c4691b
c848d83
 
 
7bd4402
 
 
 
 
 
e34cd33
7bd4402
 
 
 
3c4691b
c848d83
 
 
c788633
 
 
 
 
 
 
 
 
 
b62431c
 
c788633
 
b62431c
fdbaa4c
 
 
b62431c
fdbaa4c
b62431c
 
 
 
489bf6e
 
b62431c
 
 
c788633
 
 
 
 
 
 
3c4691b
 
 
 
 
 
 
 
 
 
 
 
c788633
 
 
c848d83
 
 
 
c788633
 
c848d83
 
3c4691b
 
c848d83
 
 
3c4691b
 
ec2586a
 
3c4691b
c848d83
87572af
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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"]