前言
这次做的是把一个原本面向本地运行的项目 StarRailCopilot 改造成可以部署到 Hugging Face Spaces 的 Docker Space 版本。
项目本身不是普通 Web 项目,它依赖本地配置、日志、截图目录,还带有更新器和自动化相关逻辑。直接丢到 Space 里跑,会遇到几个问题:
- Space 容器是临时环境,重启后本地文件可能丢失。
- 原项目默认更偏本地桌面/模拟器环境,WebUI 只是其中一部分。
- Hugging Face Space 仓库如果直接塞完整上游项目,后续同步上游会很难维护。
- Space 的构建和运行日志不一定能完整反映 GitHub 分支里的状态。
最后采用的方案是:GitHub fork + HF Space 轻量 wrapper + PostgreSQL 配置持久化。
整体方案
部署结构大概是这样:
1 | 上游仓库 LmeSzinc/StarRailCopilot |
这样做的好处是很明显的:
- Space 仓库保持很小,只负责拉代码和构建。
- 真正的改造集中在 fork 的
hf-space-docker分支。 - 以后同步上游时,冲突范围比较可控。
- Space 每次重建都会从 GitHub 分支拉最新代码。
Docker Space 改造
Hugging Face Docker Space 需要在 README front matter 里声明:
1 |
|
核心入口使用 hf_space_entrypoint.py,启动时做几件事:
- 生成适合 Space 的
config/deploy.yaml。 - 禁用自动更新、自动安装依赖、ADB 自动连接等本地环境行为。
- 强制 WebUI 监听
0.0.0.0:${PORT:-7860}。 - 如果设置了
SRC_WEBUI_PASSWORD,给 WebUI 加密码。
这一步的关键不是写一个复杂 Dockerfile,而是把原项目启动时默认假设的“本地桌面环境”收束成“只启动 WebUI 服务”。
为什么不用直接上传完整项目到 Space
一开始很容易想到把整个项目直接上传到 Hugging Face Space 仓库。
但这个做法后面会比较麻烦:
- Space 仓库会变得很大。
- 上游更新时很难区分哪些是原项目变化,哪些是部署改造。
- 如果每次都在 Space 仓库里改源码,fork 的价值会变小。
所以更合适的方式是:Space 仓库只做 wrapper。
wrapper 的 Dockerfile 类似这样:
1 | ARG SRC_REPO=https://github.com/imHansiy/StarRailCopilot.git |
这样 Space 实际部署的就是 fork 分支里的代码。
持久化问题
Space 默认文件系统不是可靠持久化存储。配置、日志、截图如果只放在容器内部,重启后可能丢失。
我尝试过 Hugging Face 的 persistent storage API:
1 | api.request_space_storage("user/space", storage="small") |
但实际调用时返回了 404 Not Found。这类能力现在和账号、Space 设置、计费流程相关,不一定能直接通过旧 API 开通。
所以后面改成了 PostgreSQL 保存配置。
PostgreSQL 配置同步
这里没有把整个项目状态都塞进数据库,只同步 config/。
原因很简单:
- 配置文件体积小,适合放数据库。
- 日志和截图会持续增长,不适合直接塞进 PG。
- 真正需要跨重启保留的是用户配置,而不是所有运行痕迹。
Space 里通过 Secret 设置:
1 | SRC_DATABASE_URL=postgresql://user:password@host:5432/dbname?sslmode=require |
入口脚本启动时会:
- 检查是否存在
SRC_DATABASE_URL。 - 自动创建表
src_space_files。 - 从数据库恢复
config/文件。 - WebUI 运行期间每 60 秒同步一次配置。
- 进程退出前再同步一次。
表结构很简单:
1 | CREATE TABLE IF NOT EXISTS src_space_files ( |
这里用 namespace 是为了以后同一个数据库能放多个 Space 或多个环境的配置。
遇到的坑
1. Space wrapper 和 fork 分支的 Dockerfile 不是一回事
这里踩过一个小坑。
我在 fork 分支的 Dockerfile 里加了 psycopg,但后来发现实际 Space 构建用的是 wrapper Dockerfile。wrapper clone 代码后安装的是 fork 分支里的 requirements.txt。
所以正确做法是把运行依赖加到 requirements.txt:
1 | psycopg[binary]>=3.2,<4 |
这样 wrapper 不需要频繁改,Space 构建时自然会安装 PG 驱动。
2. Hugging Face API 有时会被网络环境影响
过程中出现过几次 TLS EOF 或连接 reset:
1 | SSL: UNEXPECTED_EOF_WHILE_READING |
这个一般不是代码问题,而是当前网络/代理到 Hugging Face 或 GitHub 的连接不稳定。
处理方式就是:
- GitHub 推送失败就重试
git push。 - HF Python SDK 上传失败时,换
Invoke-RestMethod直接调 API。 - 重启 Space 用:
1 | POST https://huggingface.co/api/spaces/{repo_id}/restart?factory=true |
3. 更新器页面取不到 git 历史会崩
WebUI 里有一个“更新器”页面,它会读取本地和 upstream 的 git commit 历史。
但 Space 是浅克隆环境,而且 wrapper 构建后的 git remote 状态和普通本地仓库不一样。结果页面拿到:
1 | (None, None, None, None) |
然后 PyWebIO 的 put_table() 把 None 当成行数据渲染,触发:
1 | TypeError: 'NoneType' object is not iterable |
修复方式是让更新器 UI 对缺失 git 历史做容错:
1 | def commit_row(label, commit): |
详细历史为空时也显示占位行,而不是传空的 None 进去。
安全注意事项
部署过程中最需要注意的是密钥不要写进仓库。
这些都应该放在 Hugging Face Space Secret 里:
HF_TOKENSRC_DATABASE_URLSRC_WEBUI_PASSWORD- 其他第三方服务密钥
如果密钥已经在聊天记录、日志或终端输出里出现过,比较稳妥的做法是直接轮换。
还有一个点:数据库连接串里一般带有密码,博客、README 和 issue 里都不要贴真实值。示例里用占位符即可。
最终效果
最后部署出来的访问地址是:
1 | https://jankesw-starrailcopilot.hf.space/ |
当前实现的能力:
- Docker Space 可以正常启动 WebUI。
- Space 从 GitHub fork 的
hf-space-docker分支构建。 - 配置文件通过 PostgreSQL 持久化。
- 缺失 HF persistent storage 时也能保留主要配置。
- 更新器页面在 Space 环境下不会因为 git history 缺失崩溃。
总结
这次改造的核心不是“把项目塞进 Hugging Face Spaces”,而是把部署边界整理清楚:
- 上游项目继续保持上游项目的样子。
- fork 分支只承载部署适配。
- Space 仓库只做 wrapper。
- 持久化用外部 PostgreSQL 承接。
- 所有敏感配置走 Secret。
这个结构后续维护起来会舒服一些。上游有更新时,先同步 fork,再看 hf-space-docker 分支的少量部署文件是否冲突;Space 侧只需要重建即可。
如果只是临时跑 WebUI,直接 Docker Space 就够了。
如果希望长期可用,配置持久化这一步基本绕不开。