在容器化的 203 主机上跑过 Gitea 后,我一直想给本地数据找个稳定的网盘落点。选了 OpenList 挂 115 网盘,配一套定时备份脚本。整个方案最后跑通了,但中间的坑比预期多得多——WebDAV 权限、rclone 对 115 的兼容、115 写入限流、伪目录陷阱、同路径冷却窗口。这篇文章把完整方案和踩坑处理一次写清楚。
1. 总体方案
1.1 架构
数据从本地 203 主机出发,经 OpenList 作为中转,落到 115 网盘。备份脚本做两件事:把 gitea 和 qwenpaw 的数据打成 tar 包,再通过 WebDAV PUT 推到 115。
flowchart LR src[203 本地目录] --> tar[打 tar 包] tar --> curl[WebDAV PUT] curl --> ol[OpenList 容器:5244] ol --> webdav[WebDAV /dav/] webdav --> api[115 Open API] api --> netdisk[115 网盘]
为什么选 WebDAV 而不是直接用 115 SDK:OpenList 已经把 WebDAV 暴露成文件系统,备份脚本只需要一套通用的 HTTP 客户端就能覆盖所有网盘,不依赖 115 的逆向接口。
1.2 备份策略
| 服务 | 模式 | 保留 |
|---|---|---|
| gitea | tar 归档 | 7 份轮转 |
| qwenpaw/working | tar 归档 | 7 份轮转 |
| qwenpaw/secret | tar 归档 | 7 份轮转 |
| qwenpaw/backups | 镜像同步 | 与源一致 |
前三个是每日打包上传,远端按 服务_日期.tar.gz 命名,超过 7 份自动删旧的。第四个 qwenpaw/backups 里放的是平台升级导出包,本身不频繁变化,用镜像同步保持远端和源完全一致。
2. 部署 OpenList
2.1 环境准备
203 主机已有 docker,镜像源走自建脚本,国内可达。
services:
openlist:
image: openlistteam/openlist:latest
container_name: openlist
restart: always
user: "0:0"
environment:
- UMASK=022
- TZ=Asia/Shanghai
ports:
- "5244:5244"
volumes:
- /data/openlist:/opt/openlist/data
拉镜像遇到第一个坑:默认镜像源 docker.1ms.run 对这个镜像会卡死挂起,换 dockerproxy.net 一次成功。
2.2 115 网盘挂载
登录 http://192.168.0.203:5244,进管理页(/@manage)→ 存储 → 添加存储,选 115 Open 驱动。
访问令牌和刷新令牌需要从 https://api.oplist.org 获取:下拉选「115 验证网盘」,勾选使用内置参数,客户端 ID 和应用秘钥留空,点「获取 Token」用 115 App 扫码授权。拿到的两个令牌分别填入访问令牌和刷新令牌字段,根文件夹 ID 填 0 表示 115 根目录。
⚠️ 同一账号在同一应用最多取 2 次刷新令牌,第 3 次会让最早那个失效。令牌等于 115 账号的钥匙,泄漏了去 115 网页端「设备登录管理」解除授权。
2.3 WebDAV 权限坑
115 挂载成功,列目录能看到文件,但用 WebDAV 写操作一律 403。
根因:OpenList 新版默认给 admin 的权限位里没有「WebDAV 管理」,只有「WebDAV 读取」。
修复方法二选一:
- 管理 UI:管理 → 用户 → admin → 权限 → 勾选「WebDAV 管理」
- API 一次性改:把权限位加 512,例如 29183 → 29695
curl -s -X POST http://127.0.0.1:5244/api/admin/user/update \
-H "Authorization: $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":1,"username":"admin","password":"","base_path":"/","role":2,"disabled":false,"permission":29695}'
判定点:原生 API /api/fs/mkdir 能成功说明令牌没问题,WebDAV 403 才是权限位的事。
3. 备份脚本设计
3.1 为什么最终用 curl 而不是 rclone
脚本第一版用 rclone copyto 上传 tar 包。本地验证全过,一上 115 就 404。排查了一整圈才发现:
- rclone mkdir 在 115 Open API 下建的是伪目录(0 字节文件),WebDAV 不认
- rclone copyto 遇到伪目录就 404
- rclone sync 对 115 伪目录也 404
- 115 空目录本身不可见,curl 删了同名文件后目录消失,rclone 又 404
- 115 不支持覆盖同名文件,第二次 copy 同名文件 404
而 curl WebDAV PUT 能自动创建中间目录、能覆盖同名文件、HTTP 状态码清晰。curl 直传是唯一稳定方案。
3.2 脚本结构
# 任务表:服务 -> "远端路径|源路径|模式"
declare -A JOBS=(
[gitea]="gitea|/data/gitea|tar"
[qwenpaw-working]="qwenpaw/working|/opt/qwenpaw/working|tar"
[qwenpaw-secret]="qwenpaw/secret|/opt/qwenpaw/working.secret|tar"
[qwenpaw-backups]="qwenpaw/backups|/opt/qwenpaw/working.backups|sync"
)
支持无参全量、指定单服务、多个服务,未知服务直接拒绝。
/opt/openlist/backup-to-115.sh # 全量
/opt/openlist/backup-to-115.sh gitea # 只备 gitea
/opt/openlist/backup-to-115.sh gitea qwenpaw-working # 指定多个
tar 模式的核心是打包 + curl PUT + 校验:
tar czf "$tarball" -C "$(dirname "$src")" "$(basename "$src")" 2>>"$LOG"
rc=$?
[ "$rc" -gt 1 ] && { log "打包失败 (tar rc=$rc)"; return 1; }
[ "$rc" -eq 1 ] && log "打包期间源目录有写入,归档仍可用"
http_code=$(curl -s -m 300 -u "$OL_USER:$OL_PASS" \
-T "$tarball" "http://127.0.0.1:5244/dav/$BASE/$dest/$fname" \
-w "%{http_code}" -o /dev/null)
校验用 WebDAV PROPFIND 读 Content-Length,确认远端文件大小与本地一致:
curl -s -X PROPFIND "http://127.0.0.1:5244/dav/$BASE/$dest/$fname" \
-H "Depth: 0" | grep -oP '(?<=<D:getcontentlength>)[0-9]+'
镜像同步模式(qwenpaw-backups)用 curl 逐文件 PUT,保持远端和源逐文件一致。
3.3 失败重试与服务间隔
115 同路径短时间内写入会 404,脚本做了两层缓解:
- 上传失败且是 404 时,等 60 秒重试一次,共 2 轮
- 多服务时,非最后一个服务完成后等 30 秒再进下一个,降低同令牌连续写入压力
3.4 空 targets 防护
调试阶段发现一个隐蔽 bug:无参执行时任务表里的关联数组在 bash 下没展开,脚本静默空跑、fail=0。加了防护:# targets == 0 直接 fail=1,避免静默漏备份。
3.5 本地验证纪律
脚本和策略变更的验证一律在本地做——用 rclone 的 local remote 指向 /tmp/rclone-localtest 跑通全部逻辑,包括 tar 上传、镜像同步、轮转、校验、参数化分发。本地全绿后才动 115。115 的写入能力已由首次全量备份证明,不重复消耗它的额度与带宽。
4. 踩坑记录
4.1 115 写入限流
症状:rclone/curl 写入同一路径,隔 60 秒重试仍 404,隔 5 分钟才恢复。
根因:115 对同令牌的同一路径写入有冷却窗口,比 120 秒更长。今天密集测试时新令牌额度被打光,脚本 3 轮 × 120 秒全 404,陷入死循环。
处理:把重试间隔从 120 秒改回 60 秒、轮数从 3 减到 2,避免死等;服务间加 30 秒间隔;手动触发改为只在 cron 窗口跑。
4.2 rclone 的 readonly 变量问题
症状:RCLONE=/usr/local/bin/rclone 定义成 readonly 变量,脚本里 $RCLONE copyto ... 返回 rc=1 且报 404;用 /usr/local/bin/rclone 字面路径就成功。
根因:203 的 CentOS 7 上,sudo 环境下 readonly 变量引用的 rclone 行为异常。
处理:所有 rclone 调用改用字面路径,最终因 115 兼容性问题整块弃用了 rclone 上传,但这个问题值得记录。
4.3 115 伪目录陷阱
症状:用 rclone mkdir 创建的目录,rclone 能列,但 WebDAV PUT 进去就 404。
根因:115 Open API 的目录实现与 WebDAV 不一致,mkdir 建的是 0 字节文件而非真正目录。curl PUT 自动建目录时才会创建真正的 WebDAV 目录。
处理:目录创建改由 curl PUT 自动完成,脚本不再主动 mkdir。
4.4 路径拼接遗漏
症状:curl 上传返回 201,但 115 里找不到文件,rclone 列目录也看不到。
根因:curl URL 少拼了 备份/home-203/ 这一段,文件写到了 115 根目录。
处理:统一用 $BASE 常量拼装路径,dav/$BASE/$dest/$fname,避免每处手拼。
4.5 后台启动被 ssh 会话关闭连带杀死
症状:setsid nohup 启动的备份脚本,等会儿看发现根本没跑。
根因:把部署命令和后台启动写在同一条 ssh 链里,& 把整条链丢进后台,会话一断全没了。
处理:部署和启动分离到两个 ssh 调用;或者用 setsid ... < /dev/null 确保 stdin 已断开。
5. 最终交付
- 脚本
/opt/openlist/backup-to-115.sh,700 权限,root 运行 - cron 任务
/etc/cron.d/backup-to-115:每天 03:00 全量执行 - 日志
/var/log/backup-to-115.log,超 10MB 自动截断保留末尾 2000 行 - 远端目录
115:/备份/home-203/{gitea/, qwenpaw/working|secret|backups/}
⚠️ 改 OpenList 的 admin 密码必须同步更新 203 上的 rclone 配置(/root/.config/rclone/rclone.conf),否则备份会静默失败。建议给备份单独建一个受限账号,只开备份目录的 WebDAV 权限,更稳。
版权声明:如无特殊说明,文章均为本站原创,转载请注明出处
本文链接:https://tendcode.com/subject/article/openlist-115-backup/
许可协议:署名-非商业性使用 4.0 国际许可协议