yun tianming commited on
Commit
9b05b31
·
verified ·
1 Parent(s): cf3c9e4

Upload 9 files

Browse files
Files changed (9) hide show
  1. .github/workflows/deploy.yml +88 -0
  2. .gitignore +137 -0
  3. Dockerfile +61 -0
  4. LICENSE +21 -0
  5. README.md +213 -12
  6. entrypoint.sh +70 -0
  7. requirements.txt +24 -0
  8. src/__init__.py +0 -0
  9. src/data_sync.py +279 -0
.github/workflows/deploy.yml ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build and Deploy to Hugging Face Spaces
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ env:
10
+ SPACE_REPOSITORY: ${{ secrets.HF_SPACE_REPO }}
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Docker Buildx
20
+ uses: docker/setup-buildx-action@v3
21
+
22
+ - name: Build Docker image
23
+ uses: docker/build-push-action@v5
24
+ with:
25
+ context: .
26
+ push: false
27
+ tags: hermes-spaces:test
28
+ cache-from: type=gha
29
+ cache-to: type=gha,mode=max
30
+
31
+ deploy:
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
35
+
36
+ steps:
37
+ - name: Checkout code
38
+ uses: actions/checkout@v4
39
+ with:
40
+ fetch-depth: 0
41
+
42
+ - name: Configure Git
43
+ run: |
44
+ git config --global user.name "GitHub Actions"
45
+ git config --global user.email "actions@github.com"
46
+
47
+ - name: Deploy to Hugging Face Spaces
48
+ env:
49
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
50
+ run: |
51
+ # Install Hugging Face CLI
52
+ pip install huggingface-hub
53
+
54
+ # Clone the Space repository
55
+ git clone https://user:$HF_TOKEN@huggingface.co/spaces/${{ secrets.HF_SPACE_REPO }} space-repo
56
+
57
+ # Copy files to Space repository
58
+ cp -r . space-repo/
59
+ cd space-repo
60
+
61
+ # Remove .git directory to avoid conflicts
62
+ rm -rf .git
63
+
64
+ # Initialize git and push
65
+ git init
66
+ git add .
67
+ git commit -m "Deploy from GitHub Actions - $(date)"
68
+ git push --force https://user:$HF_TOKEN@huggingface.co/spaces/${{ secrets.HF_SPACE_REPO }} main
69
+
70
+ - name: Wait for Space to be ready
71
+ env:
72
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
73
+ run: |
74
+ echo "Waiting for Space to be ready..."
75
+ sleep 30
76
+
77
+ # Check if Space is running
78
+ SPACE_URL="https://${{ secrets.HF_SPACE_REPO }}.hf.space/health"
79
+ for i in {1..10}; do
80
+ if curl -f $SPACE_URL > /dev/null 2>&1; then
81
+ echo "✅ Space is ready!"
82
+ exit 0
83
+ fi
84
+ echo "⏳ Waiting... ($i/10)"
85
+ sleep 10
86
+ done
87
+
88
+ echo "⚠️ Space may not be fully ready yet, but deployment was successful"
.gitignore ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ *.manifest
29
+ *.spec
30
+
31
+ # Installer logs
32
+ pip-log.txt
33
+ pip-delete-this-directory.txt
34
+
35
+ # Unit test / coverage reports
36
+ htmlcov/
37
+ .tox/
38
+ .nox/
39
+ .coverage
40
+ .coverage.*
41
+ .cache
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+ .hypothesis/
46
+ .pytest_cache/
47
+
48
+ # Translations
49
+ *.mo
50
+ *.pot
51
+
52
+ # Django stuff:
53
+ *.log
54
+ local_settings.py
55
+ db.sqlite3
56
+
57
+ # Flask stuff:
58
+ instance/
59
+ .webassets-cache
60
+
61
+ # Scrapy stuff:
62
+ .scrapy
63
+
64
+ # Sphinx documentation
65
+ docs/_build/
66
+
67
+ # PyBuilder
68
+ target/
69
+
70
+ # Jupyter Notebook
71
+ .ipynb_checkpoints
72
+
73
+ # IPython
74
+ profile_default/
75
+ ipython_config.py
76
+
77
+ # pyenv
78
+ .python-version
79
+
80
+ # celery beat schedule file
81
+ celerybeat-schedule
82
+
83
+ # SageMath parsed files
84
+ *.sage.py
85
+
86
+ # Environments
87
+ .env
88
+ .venv
89
+ env/
90
+ venv/
91
+ ENV/
92
+ env.bak/
93
+ venv.bak/
94
+
95
+ # Spyder project settings
96
+ .spyderproject
97
+ .spyproject
98
+
99
+ # Rope project settings
100
+ .ropeproject
101
+
102
+ # mkdocs documentation
103
+ /site
104
+
105
+ # mypy
106
+ .mypy_cache/
107
+ .dmypy.json
108
+ dmypy.json
109
+
110
+ # Pyre type checker
111
+ .pyre/
112
+
113
+ # Hermes specific
114
+ .hermes/
115
+ /data/
116
+ /tmp/hermes_*/
117
+
118
+ # Logs
119
+ logs/
120
+ *.log
121
+
122
+ # IDE
123
+ .vscode/
124
+ .idea/
125
+ *.swp
126
+ *.swo
127
+ *~
128
+
129
+ # OS
130
+ .DS_Store
131
+ Thumbs.db
132
+
133
+ # Docker
134
+ .dockerignore
135
+
136
+ # Hugging Face
137
+ .hf_token
Dockerfile ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ LABEL maintainer="Hermes Agent Community"
4
+ LABEL version="0.9.0"
5
+ LABEL description="Hermes Agent v0.9.0 on Hugging Face Spaces with persistent storage"
6
+
7
+ # 设置环境变量
8
+ ENV PYTHONUNBUFFERED=1
9
+ ENV DEBIAN_FRONTEND=noninteractive
10
+ ENV HERMES_HOME=/data/.hermes
11
+ ENV PYTHONPATH=/app
12
+
13
+ # 安装系统依赖
14
+ RUN apt-get update && apt-get install -y \
15
+ build-essential \
16
+ nodejs \
17
+ npm \
18
+ ffmpeg \
19
+ git \
20
+ curl \
21
+ unzip \
22
+ && rm -rf /var/lib/apt/lists/*
23
+
24
+ # 安装 Python 依赖
25
+ COPY requirements.txt /tmp/
26
+ RUN pip install --no-cache-dir -r /tmp/requirements.txt
27
+
28
+ # 安装 Hermes Agent [web] 包含 Dashboard 依赖
29
+ RUN pip install --no-cache-dir hermes-agent[all,web]==0.9.0
30
+
31
+ # 安装 Playwright(使用精简版以减小镜像体积)
32
+ RUN npx playwright install chromium --with-deps --only-shell
33
+
34
+ # 创建应用目录
35
+ WORKDIR /app
36
+
37
+ # 复制应用代码
38
+ COPY src/ /app/src/
39
+ COPY entrypoint.sh /app/
40
+
41
+ # 创建数据目录
42
+ RUN mkdir -p /data/.hermes && \
43
+ mkdir -p /app/logs && \
44
+ chmod +x /app/entrypoint.sh
45
+
46
+ # 设置非 root 用户(Hugging Face Spaces 要求)
47
+ RUN useradd -m -u 1000 appuser && \
48
+ chown -R appuser:appuser /data && \
49
+ chown -R appuser:appuser /app
50
+
51
+ USER appuser
52
+
53
+ # 暴露端口(Hugging Face Spaces 默认 7860)
54
+ EXPOSE 7860
55
+
56
+ # 健康检查
57
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
58
+ CMD curl -f http://localhost:7860/health || exit 1
59
+
60
+ # 启动脚本
61
+ ENTRYPOINT ["/app/entrypoint.sh"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Hermes Agent Community
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,213 @@
1
- ---
2
- title: Hermes
3
- emoji: 🏢
4
- colorFrom: green
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: agent
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hermes Agent v0.9.0 on Hugging Face Spaces
2
+
3
+ 智能AI代理,支持16个消息平台,持久化记忆,和Web管理界面。
4
+
5
+ ## 功能
6
+
7
+ - 🤖 AI 对话与工具调用
8
+ - 🌐 Web Dashboard 管理
9
+ - 💾 数据持久化到 Hugging Face Dataset
10
+ - 🔔 支持消息网关(Telegram/Discord/Slack等)
11
+ - ⚡ 自动唤醒保持在线
12
+
13
+ ## 技术栈
14
+
15
+ - Hermes Agent v0.9.0
16
+ - FastAPI Web Dashboard
17
+ - Hugging Face Datasets 持久化
18
+ - Docker Spaces
19
+
20
+ ## 数据目录映射
21
+
22
+ | Hermes 目录 | Dataset 路径 | 说明 |
23
+ |------------|-------------|------|
24
+ | `~/.hermes/config.yaml` | `/config/config.yaml` | 核心配置 |
25
+ | `~/.hermes/.env` | `/config/.env` | 环境变量 |
26
+ | `~/.hermes/auth.json` | `/config/auth.json` | OAuth认证 |
27
+ | `~/.hermes/SOUL.md` | `/personality/SOUL.md` | 代理人格 |
28
+ | `~/.hermes/memories/` | `/memories/` | 持久记忆 |
29
+ | `~/.hermes/skills/` | `/skills/` | 自定义技能 |
30
+ | `~/.hermes/sessions/` | `/sessions/` | 会话历史 |
31
+ | `~/.hermes/state.db` | `/state/state.db` | SQLite数据库 |
32
+ | `~/.hermes/logs/` | `/logs/` | 日志文件 |
33
+ | `~/.hermes/cron/` | `/cron/` | 定时任务 |
34
+
35
+ ## 环境变量
36
+
37
+ ```bash
38
+ # 必需
39
+ HF_DATASET_REPO=your-username/hermes-data
40
+
41
+ # 可选
42
+ HERMES_HOME=/data/.hermes
43
+ SYNC_INTERVAL=300 # 同步间隔(秒)
44
+ HF_TOKEN=your-huggingface-token # 用于访问私有 dataset
45
+ ```
46
+
47
+ ## 快速开始
48
+
49
+ ### 1. 创建 Hugging Face Dataset
50
+
51
+ ```bash
52
+ # 创建一个新的 dataset 用于存储数据
53
+ huggingface-cli repo create hermes-data --type dataset
54
+ ```
55
+
56
+ ### 2. 设置环境变量
57
+
58
+ 在 Hugging Face Spaces 的 Settings 中设置:
59
+ - `HF_DATASET_REPO`: 你的 dataset 名称 (例如: username/hermes-data)
60
+ - `HF_TOKEN`: Hugging Face access token
61
+
62
+ ### 3. 配置唤醒服务
63
+
64
+ 使用 UptimeRobot 或 Cron-job.org 定期访问 Space URL 以保持在线。
65
+
66
+ ## 部署步骤
67
+
68
+ ### 部署到 Hugging Face Spaces
69
+
70
+ #### 方式一:通过 Web 界面部署
71
+
72
+ 1. **创建 Space**: 访问 https://huggingface.co/new-space
73
+ - 选择 "Docker" SDK
74
+ - 设置 Space 名称
75
+ - 选择硬件配置(免费版即可开始)
76
+
77
+ 2. **克隆 Space 仓库**:
78
+ ```bash
79
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
80
+ cd YOUR_SPACE_NAME
81
+ ```
82
+
83
+ 3. **复制项目文件**:
84
+ ```bash
85
+ # 复制所有项目文件到 Space 目录
86
+ cp -r /path/to/hermes-spaces/* .
87
+ ```
88
+
89
+ 4. **配置环境变量**:
90
+ - 在 Space Settings → Variables 中设置:
91
+ - `HF_DATASET_REPO`: `your-username/hermes-data`
92
+ - `HF_TOKEN`: `your-huggingface-token`
93
+
94
+ 5. **提交并推送**:
95
+ ```bash
96
+ git add .
97
+ git commit -m "Initial deployment"
98
+ git push
99
+ ```
100
+
101
+ 6. **等待部署完成**:
102
+ - 访问 `https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME`
103
+ - 查看 Build 日志,等待部署完成
104
+
105
+ #### 方式二:通过 GitHub Actions 自动部署
106
+
107
+ 1. **Fork 本仓库** 到你的 GitHub 账号
108
+
109
+ 2. **在 Hugging Face 创建 Space**:
110
+ - 访问 https://huggingface.co/new-space
111
+ - 选择 Docker SDK
112
+ - 记下 Space 名称
113
+
114
+ 3. **配置 GitHub Secrets**:
115
+ - 进入 GitHub 仓库 → Settings → Secrets and variables → Actions
116
+ - 添加以下 secrets:
117
+ - `HF_TOKEN`: 你的 Hugging Face access token
118
+ - `HF_SPACE_REPO`: `YOUR_USERNAME/YOUR_SPACE_NAME`
119
+
120
+ 4. **推送代码到 main 分支**:
121
+ ```bash
122
+ git push origin main
123
+ ```
124
+
125
+ 5. **GitHub Actions 会自动部署**:
126
+ - 查看 Actions 标签页查看部署进度
127
+ - 部署完成后访问 Space URL
128
+
129
+ ## 配置唤醒服务
130
+
131
+ 由于 Hugging Face Spaces 免费版会在 48 小时无活动后休眠,建议配置外部唤醒服务:
132
+
133
+ ### 使用 UptimeRobot(推荐)
134
+
135
+ 1. 访问 https://uptimerobot.com 并注册账号
136
+ 2. 点击 "Add New Monitor"
137
+ 3. 配置如下:
138
+ - **Monitor Type**: HTTP(s)
139
+ - **Friendly Name**: Hermes Agent
140
+ - **URL**: `https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/health`
141
+ - **Monitoring Interval**: 5 minutes
142
+ 4. 点击 "Create Monitor"
143
+
144
+ ### 使用 Cron-job.org(备选)
145
+
146
+ 1. 访问 https://cron-job.org
147
+ 2. 创建新的 cron job
148
+ 3. 配置 URL: `https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/health`
149
+ 4. 设置每 5 分钟执行一次
150
+
151
+ ## 首次使用
152
+
153
+ 1. 访问 Space URL: `https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space`
154
+ 2. 首次启动会创建默认配置,从 Dataset 恢复数据(如果存在)
155
+ 3. 在 Web Dashboard 中配置:
156
+ - API Keys(OpenAI, OpenRouter, etc.)
157
+ - 消息网关(Telegram, Discord, etc.)
158
+ - 代理人格(SOUL.md)
159
+
160
+ ## 故障排除
161
+
162
+ ### Space 无法启动
163
+
164
+ 1. **检查构建日志**:
165
+ - 访问 Space 页面 → Files → "Build logs"
166
+ - 查看错误信息
167
+
168
+ 2. **常见问题**:
169
+ - **内存不足**: 升级到付费硬件(CPU Upgrade)
170
+ - **依赖安装失败**: 检查 requirements.txt 格式
171
+ - **权限错误**: 确保 Dockerfile 中设置了 USER appuser
172
+
173
+ ### 数据未持久化
174
+
175
+ 1. **检查环境变量**:
176
+ - 确认 `HF_DATASET_REPO` 已正确设置
177
+ - 确认 `HF_TOKEN` 有写入 Dataset 的权限
178
+
179
+ 2. **检查 Dataset 权限**:
180
+ - 确保 Dataset 是私有的(推荐)
181
+ - 确保 Token 有 `write` 权限
182
+
183
+ 3. **查看同步日志**:
184
+ - 在 Space 的 Logs 中查看 data-sync 相关日志
185
+
186
+ ### 消息网关断开
187
+
188
+ 1. **这是正常现象**: Spaces 休眠后所有连接会断开
189
+ 2. **解决方案**:
190
+ - 升级到付费版保持持续运行
191
+ - 或配置网关自动重连(在 Hermes 配置中设置)
192
+
193
+ ## 文件说明
194
+
195
+ - `Dockerfile` - Docker 镜像定义
196
+ - `entrypoint.sh` - 容器启动脚本
197
+ - `requirements.txt` - Python 依赖
198
+ - `src/data_sync.py` - 数据同步服务
199
+ - `.github/workflows/deploy.yml` - GitHub Actions 自动部署
200
+
201
+ ## 许可证
202
+
203
+ MIT License - 详见 LICENSE 文件
204
+
205
+ ## 贡献
206
+
207
+ 欢迎提交 Issue 和 Pull Request!
208
+
209
+ ## 相关链接
210
+
211
+ - [Hermes Agent 官方仓库](https://github.com/NousResearch/hermes-agent)
212
+ - [Hugging Face Spaces 文档](https://huggingface.co/docs/hub/spaces-overview)
213
+ - [Hermes Agent 文档](https://hermes-agent.com)
entrypoint.sh ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Entrypoint script for Hermes Agent on Hugging Face Spaces
3
+
4
+ set -e
5
+
6
+ echo "🚀 Hermes Agent v0.9.0 - Hugging Face Spaces"
7
+ echo "=============================================="
8
+
9
+ # 检查必要的环境变量
10
+ if [ -z "$HF_DATASET_REPO" ]; then
11
+ echo "⚠️ 警告: HF_DATASET_REPO 未设置,数据将不会持久化到 Dataset"
12
+ echo " 设置方式: export HF_DATASET_REPO=your-username/hermes-data"
13
+ fi
14
+
15
+ # 创建必要的目录
16
+ echo "📁 初始化目录结构..."
17
+ mkdir -p /data/.hermes/{cron,sessions,logs,memories,skills,pairing,hooks,image_cache,audio_cache,whatsapp/session}
18
+ mkdir -p /app/logs
19
+
20
+ # 从 Dataset 恢复数据(如果配置了)
21
+ if [ -n "$HF_DATASET_REPO" ]; then
22
+ echo "📥 从 Dataset 恢复数据..."
23
+ python -m src.data_sync restore || {
24
+ echo "⚠️ 数据恢复失败,使用空配置启动"
25
+ echo " 首次运行时会创建新的配置文件"
26
+ }
27
+ fi
28
+
29
+ # 设置同步间隔(默认5分钟)
30
+ SYNC_INTERVAL=${SYNC_INTERVAL:-300}
31
+ echo "🔄 数据同步间隔: ${SYNC_INTERVAL}秒"
32
+
33
+ # 启动后台数据同步服务(守护进程模式)
34
+ echo "🔄 启动数据同步服务..."
35
+ python -m src.data_sync daemon &
36
+ SYNC_PID=$!
37
+ echo " 同步服务 PID: $SYNC_PID"
38
+
39
+ # 注册优雅关闭处理函数
40
+ cleanup() {
41
+ echo ""
42
+ echo "🛑 收到关闭信号,执行清理..."
43
+
44
+ # 停止数据同步服务
45
+ if kill -0 $SYNC_PID 2>/dev/null; then
46
+ echo " 停止数据同步服务..."
47
+ kill $SYNC_PID 2>/dev/null || true
48
+ wait $SYNC_PID 2>/dev/null || true
49
+ fi
50
+
51
+ # 执行最终备份
52
+ if [ -n "$HF_DATASET_REPO" ]; then
53
+ echo " 执行最终数据备份..."
54
+ python -m src.data_sync backup --force || echo " 备份失败"
55
+ fi
56
+
57
+ echo "👋 再见!"
58
+ exit 0
59
+ }
60
+
61
+ trap cleanup SIGTERM SIGINT
62
+
63
+ echo ""
64
+ echo "🌐 启动 Hermes Web Dashboard..."
65
+ echo " 访问地址: http://localhost:7860"
66
+ echo " 健康检查: http://localhost:7860/health"
67
+ echo ""
68
+
69
+ # 启动 Hermes Web Dashboard
70
+ exec hermes web --host 0.0.0.0 --port 7860 --no-open
requirements.txt ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hermes Agent on Hugging Face Spaces
2
+ # Python 依赖
3
+
4
+ # 核心依赖
5
+ huggingface-hub>=0.20.0
6
+ datasets>=2.14.0
7
+ requests>=2.31.0
8
+ pydantic>=2.0.0
9
+ python-dotenv>=1.0.0
10
+
11
+ # 数据处理和加密
12
+ cryptography>=41.0.0
13
+
14
+ # 日志和监控
15
+ loguru>=0.7.0
16
+ psutil>=5.9.0
17
+
18
+ # 异步支持
19
+ aiohttp>=3.9.0
20
+ aiocron>=1.8
21
+
22
+ # 工具
23
+ click>=8.1.0
24
+ rich>=13.0.0
src/__init__.py ADDED
File without changes
src/data_sync.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hermes Agent Data Sync Service
4
+ Handles data persistence to/from Hugging Face Dataset
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import json
11
+ import shutil
12
+ import tarfile
13
+ import argparse
14
+ from pathlib import Path
15
+ from datetime import datetime
16
+ from typing import Optional, Dict, List
17
+
18
+ from huggingface_hub import HfApi, hf_hub_download, upload_folder
19
+ from loguru import logger
20
+
21
+
22
+ class DatasetManager:
23
+ """Manages data synchronization with Hugging Face Dataset"""
24
+
25
+ def __init__(self, dataset_repo: Optional[str] = None, token: Optional[str] = None):
26
+ self.dataset_repo = dataset_repo or os.environ.get('HF_DATASET_REPO')
27
+ self.token = token or os.environ.get('HF_TOKEN') or os.environ.get('HUGGING_FACE_HUB_TOKEN')
28
+ self.api = HfApi(token=self.token)
29
+ self.hermes_home = Path(os.environ.get('HERMES_HOME', '/data/.hermes'))
30
+ self.temp_dir = Path('/tmp/hermes_sync')
31
+
32
+ # 数据路径映射
33
+ self.path_mapping = {
34
+ 'config': self.hermes_home / 'config.yaml',
35
+ 'env': self.hermes_home / '.env',
36
+ 'auth': self.hermes_home / 'auth.json',
37
+ 'soul': self.hermes_home / 'SOUL.md',
38
+ 'memories': self.hermes_home / 'memories',
39
+ 'skills': self.hermes_home / 'skills',
40
+ 'sessions': self.hermes_home / 'sessions',
41
+ 'state_db': self.hermes_home / 'state.db',
42
+ 'logs': self.hermes_home / 'logs',
43
+ 'cron': self.hermes_home / 'cron',
44
+ }
45
+
46
+ def validate(self) -> bool:
47
+ """验证配置是否正确"""
48
+ if not self.dataset_repo:
49
+ logger.error("HF_DATASET_REPO not set")
50
+ return False
51
+
52
+ if not self.token:
53
+ logger.warning("HF_TOKEN not set, will try public dataset")
54
+
55
+ return True
56
+
57
+ def prepare_backup_data(self) -> Path:
58
+ """准备备份数据到临时目录"""
59
+ logger.info("Preparing backup data...")
60
+
61
+ # 清理并创建临时目录
62
+ if self.temp_dir.exists():
63
+ shutil.rmtree(self.temp_dir)
64
+ self.temp_dir.mkdir(parents=True)
65
+
66
+ # 创建目录结构
67
+ (self.temp_dir / 'config').mkdir()
68
+ (self.temp_dir / 'personality').mkdir()
69
+ (self.temp_dir / 'memories').mkdir()
70
+ (self.temp_dir / 'skills').mkdir()
71
+ (self.temp_dir / 'sessions').mkdir()
72
+ (self.temp_dir / 'state').mkdir()
73
+ (self.temp_dir / 'logs').mkdir()
74
+ (self.temp_dir / 'cron').mkdir()
75
+
76
+ # 复制文件
77
+ try:
78
+ # 配置文件
79
+ if self.path_mapping['config'].exists():
80
+ shutil.copy2(self.path_mapping['config'], self.temp_dir / 'config' / 'config.yaml')
81
+
82
+ # 环境变量(敏感信息)
83
+ if self.path_mapping['env'].exists():
84
+ shutil.copy2(self.path_mapping['env'], self.temp_dir / 'config' / '.env')
85
+
86
+ # OAuth 认证
87
+ if self.path_mapping['auth'].exists():
88
+ shutil.copy2(self.path_mapping['auth'], self.temp_dir / 'config' / 'auth.json')
89
+
90
+ # 人格定义
91
+ if self.path_mapping['soul'].exists():
92
+ shutil.copy2(self.path_mapping['soul'], self.temp_dir / 'personality' / 'SOUL.md')
93
+
94
+ # 记忆
95
+ if self.path_mapping['memories'].exists():
96
+ shutil.copytree(self.path_mapping['memories'], self.temp_dir / 'memories', dirs_exist_ok=True)
97
+
98
+ # 技能
99
+ if self.path_mapping['skills'].exists():
100
+ shutil.copytree(self.path_mapping['skills'], self.temp_dir / 'skills', dirs_exist_ok=True)
101
+
102
+ # 会话
103
+ if self.path_mapping['sessions'].exists():
104
+ shutil.copytree(self.path_mapping['sessions'], self.temp_dir / 'sessions', dirs_exist_ok=True)
105
+
106
+ # 数据库
107
+ if self.path_mapping['state_db'].exists():
108
+ shutil.copy2(self.path_mapping['state_db'], self.temp_dir / 'state' / 'state.db')
109
+
110
+ # 日志
111
+ if self.path_mapping['logs'].exists():
112
+ shutil.copytree(self.path_mapping['logs'], self.temp_dir / 'logs', dirs_exist_ok=True)
113
+
114
+ # 定时任务
115
+ if self.path_mapping['cron'].exists():
116
+ shutil.copytree(self.path_mapping['cron'], self.temp_dir / 'cron', dirs_exist_ok=True)
117
+
118
+ # 添加元数据
119
+ metadata = {
120
+ 'timestamp': datetime.now().isoformat(),
121
+ 'version': '0.9.0',
122
+ 'hermes_home': str(self.hermes_home)
123
+ }
124
+ with open(self.temp_dir / 'metadata.json', 'w') as f:
125
+ json.dump(metadata, f, indent=2)
126
+
127
+ logger.success(f"Backup prepared at {self.temp_dir}")
128
+ return self.temp_dir
129
+
130
+ except Exception as e:
131
+ logger.error(f"Failed to prepare backup: {e}")
132
+ raise
133
+
134
+ def upload_to_dataset(self, force: bool = False) -> bool:
135
+ """上传数据到 Hugging Face Dataset"""
136
+ try:
137
+ backup_dir = self.prepare_backup_data()
138
+
139
+ logger.info(f"Uploading to dataset: {self.dataset_repo}")
140
+
141
+ # 上传文件夹到 dataset
142
+ self.api.upload_folder(
143
+ folder_path=str(backup_dir),
144
+ repo_id=self.dataset_repo,
145
+ repo_type="dataset",
146
+ commit_message=f"Hermes Agent backup - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
147
+ )
148
+
149
+ logger.success("Backup uploaded successfully")
150
+ return True
151
+
152
+ except Exception as e:
153
+ logger.error(f"Failed to upload to dataset: {e}")
154
+ return False
155
+
156
+ def download_from_dataset(self) -> bool:
157
+ """从 Hugging Face Dataset 下载数据"""
158
+ try:
159
+ logger.info(f"Downloading from dataset: {self.dataset_repo}")
160
+
161
+ # 创建临时下载目录
162
+ download_dir = Path('/tmp/hermes_download')
163
+ if download_dir.exists():
164
+ shutil.rmtree(download_dir)
165
+ download_dir.mkdir(parents=True)
166
+
167
+ # 下载所有文件
168
+ self.api.snapshot_download(
169
+ repo_id=self.dataset_repo,
170
+ repo_type="dataset",
171
+ local_dir=str(download_dir)
172
+ )
173
+
174
+ logger.success("Download completed")
175
+
176
+ # 恢复数据到 Hermes 目录
177
+ self.restore_from_download(download_dir)
178
+ return True
179
+
180
+ except Exception as e:
181
+ logger.error(f"Failed to download from dataset: {e}")
182
+ return False
183
+
184
+ def restore_from_download(self, download_dir: Path):
185
+ """从下载的目录恢复数据"""
186
+ logger.info("Restoring data to Hermes home...")
187
+
188
+ # 确保目标目录存在
189
+ self.hermes_home.mkdir(parents=True, exist_ok=True)
190
+
191
+ # 恢复各个部分
192
+ restore_mapping = {
193
+ 'config/config.yaml': self.path_mapping['config'],
194
+ 'config/.env': self.path_mapping['env'],
195
+ 'config/auth.json': self.path_mapping['auth'],
196
+ 'personality/SOUL.md': self.path_mapping['soul'],
197
+ 'memories': self.path_mapping['memories'],
198
+ 'skills': self.path_mapping['skills'],
199
+ 'sessions': self.path_mapping['sessions'],
200
+ 'state/state.db': self.path_mapping['state_db'],
201
+ 'logs': self.path_mapping['logs'],
202
+ 'cron': self.path_mapping['cron'],
203
+ }
204
+
205
+ for src_rel, dst in restore_mapping.items():
206
+ src = download_dir / src_rel
207
+ if src.exists():
208
+ try:
209
+ if src.is_file():
210
+ dst.parent.mkdir(parents=True, exist_ok=True)
211
+ shutil.copy2(src, dst)
212
+ logger.info(f"Restored: {src_rel}")
213
+ elif src.is_dir():
214
+ if dst.exists():
215
+ shutil.rmtree(dst)
216
+ shutil.copytree(src, dst)
217
+ logger.info(f"Restored directory: {src_rel}")
218
+ except Exception as e:
219
+ logger.error(f"Failed to restore {src_rel}: {e}")
220
+ else:
221
+ logger.warning(f"Not found in backup: {src_rel}")
222
+
223
+ logger.success("Data restoration completed")
224
+
225
+
226
+ def run_daemon():
227
+ """后台守护进程模式 - 定期同步"""
228
+ logger.info("Starting data sync daemon...")
229
+
230
+ sync_interval = int(os.environ.get('SYNC_INTERVAL', '300')) # 默认5分钟
231
+ manager = DatasetManager()
232
+
233
+ if not manager.validate():
234
+ logger.error("Configuration invalid, exiting")
235
+ sys.exit(1)
236
+
237
+ logger.info(f"Sync interval: {sync_interval} seconds")
238
+
239
+ while True:
240
+ try:
241
+ time.sleep(sync_interval)
242
+ logger.info("Performing scheduled backup...")
243
+ manager.upload_to_dataset()
244
+ except KeyboardInterrupt:
245
+ logger.info("Daemon stopped")
246
+ break
247
+ except Exception as e:
248
+ logger.error(f"Sync error: {e}")
249
+
250
+
251
+ def main():
252
+ parser = argparse.ArgumentParser(description='Hermes Agent Data Sync')
253
+ parser.add_argument('action', choices=['backup', 'restore', 'daemon'],
254
+ help='Action to perform')
255
+ parser.add_argument('--force', '-f', action='store_true',
256
+ help='Force backup even if no changes')
257
+
258
+ args = parser.parse_args()
259
+
260
+ manager = DatasetManager()
261
+
262
+ if not manager.validate():
263
+ logger.error("Configuration invalid")
264
+ sys.exit(1)
265
+
266
+ if args.action == 'backup':
267
+ success = manager.upload_to_dataset(force=args.force)
268
+ sys.exit(0 if success else 1)
269
+
270
+ elif args.action == 'restore':
271
+ success = manager.download_from_dataset()
272
+ sys.exit(0 if success else 1)
273
+
274
+ elif args.action == 'daemon':
275
+ run_daemon()
276
+
277
+
278
+ if __name__ == '__main__':
279
+ main()