Skip to content

[Bug] WebDAV 备份同步功能多处逻辑缺陷 #262

Description

@andy0532

问题概述

lxserver 的 WebDAV 备份同步功能(src/utils/webdavSync.tssrc/index.ts)存在多处逻辑缺陷,导致备份不稳定、配置不生效、首次启动只备份 config.js 等问题。


🔴 缺陷 1:云端为空时只上传 config.js,其他文件不传

文件: src/utils/webdavSync.ts 第 652-662 行
严重程度: 高

restoreFromRemote() 中,当云端没有任何散文件和备份时(首次使用 / 清空云端后),代码仅上传 config.js,不调用 syncAllFiles() 上传其余数据文件。

// 第 656-660 行
if (global.lx && global.lx.saveConfig) {
    global.lx.saveConfig()
}
await this.uploadFile(config.js)  // 只传了 config.js!
return false

影响: 首次启动或清空云端后重启,只有 config.js 被备份到云端,其他文件(users.json、用户歌单快照、自定义源等)永远不会上传,除非用户手动调用 /api/webdav/sync API 或等到 24 小时后的自动备份周期。

修复建议: 此处应调用 await this.syncAllFiles() 替代仅上传 config.js。


🔴 缺陷 2:sync.interval 配置项实际不生效

文件: src/utils/webdavSync.ts 第 43 行
严重程度: 高

this.syncInterval = (config.interval || 60) * 60 * 1000

存在两个问题:

  1. 单位错误config.interval 乘以了 60 * 60 * 1000(即 360 万毫秒),如果用户配置 sync.interval: 60,代码将其理解为 60 分钟而非预期的 60 秒
  2. 从未使用this.syncInterval 赋值后在整个类中没有任何地方被引用。实际的文件变化检测周期是硬编码的 watchInterval = 60000(第 29 行),固定每 60 秒运行一次,与用户配置无关

影响: 用户配置的 sync.interval 完全无效,无论设多少,自动同步始终每 60 秒运行一次。


🔴 缺陷 3:saveConfigToFile() 触发热重载循环

文件: src/index.ts 第 444 行、第 448-475 行
严重程度: 高

启动时序存在竞态条件:

  1. 第 444 行:saveConfigToFile() 写入 config.js
  2. 第 452 行:fs.watch() 开始监听 config.js 变化
  3. 第 444 行的写入事件被 watcher 捕获 → 触发 updateConfig()
  4. updateConfig() 调用 stopAutoSync()startAutoSync()(第 749-753 行)
  5. 自动同步的 60 秒定时器被重置

如果 restoreFromRemote()(第 381 行,异步启动)在执行过程中也调用了 saveConfig()(第 619/643/657 行),则会再次触发 watcher,形成循环。

影响: 每次容器启动都会触发至少一次不必要的热重载,导致自动同步定时器重置。严重时可能出现"启动→写 config.js→热重载→停同步→重开→再写 config.js→再热重载"的循环。

修复建议:

  • saveConfigToFile() 应放在第 448 行 fs.watch() 调用之前执行,或
  • watcher 应校验文件内容是否真正变化后再触发重载

🟡 缺陷 4:fs.watch 不校验内容是否真实变化

文件: src/index.ts 第 452-474 行
严重程度: 中

fs.watch 仅检测文件写入事件(event === change),不计算文件 hash 或对比内容。而 saveConfigToFile() 每次都是完整序列化写入磁盘,即使配置没有实质变化也会触发写入,进而触发热重载。


🟡 缺陷 5:updateConfig() 不做变更检测,每次无条件重启同步

文件: src/index.ts 第 461-467 行、src/utils/webdavSync.ts 第 742-755 行
严重程度: 中

热重载时无论 WebDAV 的 url/username/password/interval 是否真正变化,都调用 updateConfig(),导致每次都关闭 WebDAV 客户端并重新初始化、重新扫描所有文件、重置 60 秒定时器。


🟡 缺陷 6:syncAllFiles() 上传失败静默吞掉,无控制台输出

文件: src/utils/webdavSync.ts 第 218-233 行、第 445-455 行
严重程度: 中

uploadFile() 内部 catch 了所有错误,仅写入内部 syncLogs 缓冲区(最多 100 条),不打印到 console。用户查看 docker logs 看不到任何上传失败的信息。必须通过 /api/webdav/logs API 才能查看。


🟡 缺陷 7:restoreFromRemote() 恢复后不做数据一致性校验

文件: src/utils/webdavSync.ts 第 581-669 行
严重程度: 中

从云端成功恢复数据到本地后(success=true),只重载了 config.js、users.json 和自定义源,没有对比本地和云端的数据是否完整。如果云端因共享路径或其他原因缺少部分文件,本地的多余文件不会被补传上去。


🟡 缺陷 8:远程路径 /lx-sync//lx-sync-backups/ 完全硬编码

文件: src/utils/webdavSync.ts 第 147、175、241、360、469、587 行
严重程度: 中

所有 WebDAV 请求的路径都是硬编码的字符串,无法通过配置修改。当两个 lxserver 实例指向同一个 webdav.url 时,它们会互相覆盖文件。用户只能通过在 webdav.url 中手动拼接路径前缀(如 /dav/instance1/)来规避。

修复建议: 将路径前缀改为可配置项,或从 webdav.url 自动派生唯一路径。


🟢 缺陷 9:备份 zip 先在本地创建再上传

文件: src/utils/webdavSync.ts 第 291-337 行
严重程度: 低

createBackup() 先在本地 /server/data/ 下创建 zip 文件,然后上传,最后删除。若 data 目录磁盘空间不足(虽然实际场景不太可能)或文件被占用,会导致备份失败。可使用流式压缩直接上传避免临时文件。


🟢 缺陷 10:cleanOldBackups() 的备份排序依赖字符串比较

文件: src/utils/webdavSync.ts 第 472 行
严重程度: 低

.sort((a, b) => b.lastmod.localeCompare(a.lastmod))

不同 WebDAV 服务返回的 lastmod 格式可能不同(如 RFC 1123 vs ISO 8601),字符串比较在某些格式下排序不准确,可能导致删除的是错误的备份。


环境信息

  • lxserver 版本: latest (Docker 镜像)
  • Node.js: v24.16.0
  • 测试场景: Docker 容器运行,WebDAV 后端为 TeraCloud(日本节点)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions