维护一个个人版 SaveXTube:从陈旧 Fork 到可持续更新的下载服务

从旧 Fork 重建到当前社区版基线,补上认证、路径、回调、Telegram 登录、回归测试与 CI 的完整维护实践。

工程笔记 · 2026-07-24 · 约 6 分钟阅读 · 标签:Docker、SaveXTube、安全、CI/CD

自托管下载工具常见的一种状态是:线上容器一直能用,GitHub Fork 却停留在几个月甚至几年前。等到真正需要修漏洞、加功能或重新构建镜像时,才发现仓库和生产版本早已不是同一套架构。

这篇文章记录一次 SaveXTube 个人维护版的重建过程:以当前社区版源码重新建立基线,保留个人部署需要的改动,并补上安全边界、回归测试和 CI。

上游项目采用 MIT License。个人维护版仓库:

https://github.com/liuweiqiang0523/savextube

问题不是“落后几个提交”,而是基线已经不同

最初的 Fork 来自较早时期,目录结构、Web API、下载任务和配置方式都与正在运行的社区版不一致。如果直接在旧 Fork 上合并新代码,常见结果是:

  • 大量无意义冲突;
  • 旧补丁重复实现上游已有功能;
  • Dockerfile、依赖和启动脚本互相不匹配;
  • 看似构建成功,运行时才暴露路径或数据库问题。

所以这次没有做机械式 git merge,而是先确认生产镜像的版本和许可证,再以当前社区版源码替换旧基线,最后逐项重新应用仍然必要的个人补丁。

建立新基线前先做四件事

1. 提取并核对当前运行版本

不要仅相信镜像标签。latest 可能变化,容器标签也可能不完整。需要同时记录:

  • 镜像 ID 和 digest;
  • 应用版本;
  • 源码 revision;
  • Compose 配置;
  • 实际挂载路径。

2. 检查许可证

维护公开 Fork 前必须确认上游许可。MIT 允许修改和再分发,但应保留版权与许可证文本,并明确标注个人维护性质。

3. 扫描凭据

生产镜像、补丁目录和旧 Fork 都可能包含 Cookie、Session、Token 或临时调试配置。迁移前运行 gitleaks,并人工检查:

  • .env
  • Cookie 文件;
  • Telegram Session;
  • 代理地址;
  • 数据库导出;
  • 日志和测试样本。

4. 不触碰正在运行的容器

代码仓库重建和生产升级是两件事。先在隔离目录完成构建与测试,不要边整理 Git 历史边替换线上容器。

这次补上的安全边界

个人下载服务常被误认为“只有自己用,不需要安全设计”。但它通常能访问 Cookie、Telegram 会话、下载目录和回调地址,恰恰需要明确边界。

管理员初始化

首次启动不再创建弱默认账号。管理员密码必须显式设置并满足最低长度,避免 admin/admin 之类的默认凭据被长期遗忘。

JWT 密钥

JWT Secret 必须达到足够长度;认证模块尚未初始化时返回服务不可用,而不是使用空值继续签发 Token。Compose、入口脚本和文档中的自动生成行为也必须一致。

回调 URL

允许用户提供 callback URL 时,不能只检查字符串开头:

  • 使用精确主机白名单;
  • 禁止 URL 内携带用户名和密码;
  • 禁止自动跟随重定向;
  • 默认拒绝未配置的外部主机。

这能降低 SSRF 和重定向逃逸风险。

文件下载路径

Job 返回的文件路径必须解析到下载根目录之下。除了 ../,还要防止绝对路径和符号链接逃逸。不能因为数据库里保存了某个路径,就直接交给 Web 框架发送。

Telegram 登录

动态拼接 Python 代码执行 Telegram 登录既难审计,也容易出现注入和敏感信息回显。更稳妥的方法是固定 worker:

Web API → JSON stdin → 固定 Worker → JSON stdout

API ID、Hash、验证码和二次验证密码只作为数据传递,不拼接进源码;错误响应也不直接回显底层凭据。

下载任务取消的一个反直觉问题

阻塞式下载通常在线程池中执行。取消外层 Future 并不意味着底层线程会停止。如果立即释放并发槽,会出现:

界面显示已取消
但下载线程仍在运行
新的任务又占用一个槽
真实并发数超过限制

在不重写为可控 subprocess 的前提下,更安全的语义是:

  • 标记任务已取消;
  • 不再把结果写成成功;
  • 等底层线程真实退出后再释放并发槽;
  • 丢弃取消后的最终结果。

这不是“立即杀死下载”,但能保证状态和资源限制不撒谎。若业务必须支持强制终止,应把下载器重构为独立子进程并管理其进程组。

Docker 构建也要减少单点依赖

原 Dockerfile 从第三方静态站点下载 FFmpeg。该站点临时返回错误时,整个 CI 构建失败,尽管项目源码没有问题。

后来改为使用 Ubuntu 软件源中的 FFmpeg:

  • 版本可能不如静态包新;
  • 但来源稳定、架构适配明确;
  • CI 和本地构建更可重复。

对于个人服务,“稳定可重建”通常比追逐最新工具版本更重要。

回归测试与 CI

本次新增的安全回归测试覆盖:

  • callback 精确白名单;
  • 下载路径不能逃逸根目录;
  • 取消标记生命周期;
  • Job 取消必须按用户隔离;
  • 管理员弱密码拒绝;
  • 短 JWT Secret 拒绝。

CI 顺序为:

Python 编译
→ 安全回归测试
→ gitleaks
→ Docker 完整构建

只有完整镜像构建通过,才能证明 Dockerfile、依赖和源码真正兼容。单纯 compileall 通过远远不够。

生产升级为什么要另做一次决策

维护版仓库通过 CI,不代表应该立即替换生产。生产切换前还应:

  1. 构建带明确标签的新镜像;
  2. 使用复制的数据目录做隔离启动;
  3. 验证登录、下载、Telegram、文件搬运和健康检查;
  4. 保留旧镜像 digest;
  5. 在受控窗口替换容器;
  6. 验证后再清理旧资源。

代码准备完成和生产切流应分开汇报、分开确认。

小结

重建个人 Fork 的核心,不是让 GitHub 看起来“更新了”,而是重新建立可信链条:

可追溯的上游基线
→ 明确的个人补丁
→ 无凭据泄露
→ 自动化回归测试
→ 可重复镜像构建
→ 独立的生产升级决策

如果你的自托管项目也出现“容器能跑、仓库却不敢碰”的状态,与其继续在旧 Fork 上打补丁,不如先重建基线,再把真正有价值的修改一项项带回来。

全部文章 · 返回 OneMJJ 首页

OneMJJ · 少踩坑,多留传家宝 · Public tools first.