← 返回归档

NAS Calibre-Web 升级攻坚全记录(2026-09-20~21)

2026-09-21 7 分钟阅读 更新于 2026-10-03
数码折腾
NAS Calibre-Web 升级攻坚全记录(2026-09-20~21)
目录

时间:2026-09-20 20:00 → 2026-09-21 00:30 主机:NAS451DHU(QNAP TS-451D · QTS 5.2.10 · Docker 27.1.2-qnap8) 起因:Farmer 提问「web-calibre 容器新版本为什么不用?升级后服务起不来,也导致本地 Calibre 客户端不能升 9.x」


一、结论先行

问题结论
新版本为什么不能用Calibre 9 删除了 books.isbn/lccn/flags,而 Calibre-Web ≤0.6.25 在 ORM 里硬编码映射了这些列 → 页面 500
是"服务起不来"吗不是。容器正常启动,是页面报错
修好了吗✅ 已升到 0.6.27-ls402,实测全绿
阻塞 Calibre 桌面 9.x 吗✅ 已解锁,实测 Calibre 9.15 转换格式成功
自定义列要处理吗❌ 不用(是 composite,在 Calibre-Web 忽略名单里)

二、技术根因(源码级)

Calibre 9 做了什么

kovidgoyal/calibre → src/calibre/db/schema_upgrades.py → upgrade_version_26:

for x in {'flags', 'isbn', 'lccn'} & columns:
    statements.append(f'ALTER TABLE books DROP COLUMN {x};')

同时新建 books_pages_link、重建 meta 视图、PRAGMA application_id = 0x63616c69、user_version 26 → 27。

关键特性:Calibre 打开书库时在 BEGIN EXCLUSIVE TRANSACTION 里原地升级,不可逆。

Calibre-Web 为什么会崩

  • ≤0.6.25:cps/db.py 的 Books 模型有 isbn = Column(...) / flags = Column(...) → SQLAlchemy 生成 SELECT ... books.isbn ... → no such column: books.isbn → HTTP 500
  • 0.6.26 起:commit 7f7fc884c2(2026-01-31)把这两列注释掉 → 不再查询 → 修复
  • ⚠️ Calibre-Web 从不读 PRAGMA user_version(源码 5 处文件零命中)→ 无版本判断,只在查询时炸

为什么极难自查

错误只写进 /config/calibre-web.log(应用日志),不进 docker logs(容器 stdout)。 所以看容器日志只会看到"一切正常启动",真正原因藏在 config 目录的日志文件里。


三、自定义列排查(虚惊一场)

custom_columns 表(1 行)
  label     = formats
  name      = 格式
  datatype  = composite        ← 关键
  display   = {"composite_template": "{:'re(approximate_formats(), ',', ', ')'}", ...}

custom_column_1 表:0 行   ← composite 是计算列,不存数据

Calibre-Web cps/db.py:

cc_exceptions = ['composite', 'series']
if row.datatype not in cc_exceptions:   # ← composite 直接跳过

→ composite 落在忽略名单内,不会触发 books_custom_column_N_link 查询 → issue #3643(Calibre 9 + 自定义列 → 书详情页/OPDS 失败)对本库不适用

注:cc_exceptions 里之所以有 composite,正是源于 2016 年的 cbw #20 —— 那位报告人的列也叫 formats、也是 composite,janeczku 据此把它加进了忽略名单。


四、决定性复现实验

在库副本上施加 upgrade_version_26 的等效迁移(删三列 + 重建 meta 视图 + application_id/user_version),然后同一份库分别跑新旧两个 Calibre-Web:

URL0.6.20(旧)0.6.26(新)
/ 首页500200
/login200200
/opds200200
/opds/books/letter/00500200

→ 症状精确复现,修复确认。(/login 正常是因为它不查书)


五、升级执行(三步走,每步都带回滚)

① 备份          一致快照(SQLite backup API,非裸拷贝)
                → /share/CACHEDEV1_DATA/Public/openclaw-backups/
② 沙箱验证      库副本 + config 副本 + 独立端口(8093/8094/8095)
                不碰生产容器/配置/书库
③ 生产切换      docker rename 旧容器保留为回滚锚点 → 同规格起新容器
                → 等待 → 验证 → 失败自动回滚

升级链:0.6.20-ls225 → 0.6.26-ls388 → 0.6.27-ls402(当日发布,含 2026-09 安全批次)

最终验证(生产 8083):

/  200    /login  200    /opds  200
/book/1  200   ← 旧版必崩的页面
/opds/books/letter/00  200
restarts=0   stdout 错误签名=0   app.db 21 表 / 3 用户完整保留

终局实测(最有说服力的一条)

Farmer 升 Calibre 桌面 9.15 并成功转换一本书的格式 → 书库已升到 schema 27:

user_version     : 27
application_id   : 0x63616c69
books_pages_link : PRESENT(1065 行)
books.isbn/lccn/flags : 全部已删除
books 行数       : 1065(一本书没丢)

而 Calibre-Web 0.6.27 完全扛住:/book/1 与 /opds/* 全 200、零错误签名。 → 如果当初没升级,此刻这两个页面必定 500。整条链路验证通过。


六、镜像加速的真相(重要可复用结论)

registry-mirrors 只对 Docker Hub(docker.io)生效。

✅ linuxserver/calibre-web:0.6.27-ls402            → 命中加速,实测 5 秒
❌ lscr.io/linuxserver/calibre-web:0.6.27-ls402    → 绕过加速,直连境外 → 卡死
  • 本机 daemon 配置:/share/CACHEDEV1_DATA/.qpkg/container-station/etc/docker.json
    "registry-mirrors": ["https://docker.1ms.run", "https://docker.xuanyuan.me"]
    
  • 实测 docker pull hello-world 5 秒完成 → 加速本身完全正常
  • 规则:拉 LinuxServer 镜像一律用 Docker Hub 路径,不要用 lscr.io(其镜像同时发布在 Hub 上)
  • 不需要改 daemon.json —— 改它要重启 dockerd,会导致所有容器重启,代价大而无必要
  • ⚠️ 容器 Station UI 走的是 Hub + 加速,所以 UI 能拉、命令行用 lscr.io 不能拉 —— 差异就在仓库路径

七、顺带完成的安全审计(8083 暴露问题)

修正我自己的错误说法

曾把这些扫描称为"境外扫描器" —— 核对 IP 后发现是错的:

MGLNDD_116.237.***.*** / 116.237.***.*** / 116.237.***.***   ← 中国电信·上海(国内)

防火墙实际状态

QuFirewall 已启用(firewall_status = 1,profile = Basic protection),规则:

IPv4  ① Allow 192.168.***.***/24    ② Allow src_country=["CN"]    ③ Deny Any
IPv6  ① Allow 240e:389:8a18:c100::/64 (eth0)
      ② Allow fe80::/64 (eth0)  ③ Allow src_country=["CN"]   ④ Deny Any

🎯 答案:策略是「允许整个中国」→ 国内扫描器被合法放行,不是防火墙失效。

但发现 3 个真问题

  1. geo-IP 库过期:geoip_date = 20251104(约 10.5 个月前)→ 国家判定可能失准
  2. 丢弃日志已死:ulogd 于 2024-09-13 退出(Terminal signal received, exiting)→ 看不到防火墙拦了什么,这正是"感觉拦不住"的根源
  3. 规则面过宽:允许任何中国 IP 访问任意端口(含 8083)

1 个未验证的怀疑

Docker 映射端口可能绕过主机防火墙(经典 DOCKER-USER 问题:DNAT 在 nat PREROUTING,不走 filter INPUT 常规路径)。 claw 无 root、可用镜像均无 iptables → 未能证实,不能当结论。

建议(按有效性)

  1. ⭐ 路由器层面不转发 8083(从入口掐掉最可靠)
  2. ⭐ 收紧"允许中国"规则(家庭 NAS 通常不需要全国可访问)
  3. 更新 geo-IP 库
  4. 恢复 ulogd 丢弃日志(否则永远无法自查)

八、我在本次工作中犯的错误(教训)

  1. 备份脚本路径用错:把宿主机路径当容器内路径 → 备份写进 --rm 容器内部、随容器蒸发(宿主零副作用,但白做一次)
  2. 探测方式有缺陷:docker pull | tail 会把输出缓冲到结束才显示 → 把「慢」误判成「挂死」,过早 kill
  3. 误判镜像为假:把「拉取中途 tag 指向旧镜像」的瞬时状态判成「镜像站返回假镜像」→ 被 Farmer 纠正
  4. 未核对就下结论:把国内扫描 IP 说成"境外扫描器"

共同教训:下判断前先确认「状态是否已经稳定」(有没有进行中的任务),再判断数据本身。 辅助教训:| tail 这类缓冲会掩盖进度;长任务探测要有可见进度或分层确认;不要用未经核对的形容词下结论。


九、可复用的技术要点

NAS 接入

  • claw 已在 administrators 组;SSH 用 SSH_ASKPASS 免交互(tmp/askpass.cmd + askpass.py,从 .env 的 QNAP_SSH_CREDENTIAL 读,密码不进模型上下文)
  • 脚本传输最可靠方式:把脚本 pipe 给远端 sh 的 stdin(base64 会截断、sh -c 引号易错)
  • 容器路径 vs 宿主路径必须分清(见教训 1)
  • PowerShell 脚本必须纯 ASCII(PS 5.1 无 BOM 时按 GBK 读 UTF-8 中文会破坏语法)

关键路径

书库        /share/Multimedia/book/calibre-db/books/library/metadata.db
config      /share/Container/calibre-svr/config/
备份        /share/CACHEDEV1_DATA/Public/openclaw-backups/
镜像配置    /share/CACHEDEV1_DATA/.qpkg/container-station/etc/docker.json
防火墙配置  /etc/config/QuFirewall.conf · QuFirewall_Profile.conf

容器规格(calibre-web-1)

image      linuxserver/calibre-web:<tag>
env        PUID=1000 PGID=1000
ports      8083:8083
volumes    /share/Container/calibre-svr/config:/config
           /share/Multimedia/book/calibre-db/books:/books
restart    always
log        json-file, max-file=10, max-size=10m
labels     com.qnap.qcs.gpu=False · com.qnap.qcs.network.mode=nat

Container Station 不单独存容器定义(ctstation.sqlite 只有 backgroundtask 历史记录与 registry/keyvalue),UI 实时反映 Docker 状态 → 用 docker run 重建不会产生"UI 管不到的野容器"。


十、遗留事项

优先级事项
🔴8083 暴露 —— 路由器是否转发待确认;Docker 是否绕过防火墙待验证(需 root)
🟡calibre 转换引擎未装 —— 镜像内无 calibre 二进制;Web 端转换不可用(需 DOCKER_MODS=linuxserver/mods:universal-calibre)
🟡Docker 另有约 3.28 GB 可回收 —— 死容器遗留镜像(wizserver 2.37G / movie-robot 1.59G / openjdk / portainer)
🟡movie-robot 死因未查 —— Exited (137),疑 OOM
🟡QuFirewall 三项整改 —— geo-IP 更新 / ulogd 恢复 / 规则收紧

十一、最终状态

calibre-web-1   linuxserver/calibre-web:0.6.27-ls402   Up   restarts=0
回滚锚点        已按 Farmer 要求清理
旧镜像          0.6.20 / 0.6.26 已删除(释放 1.47 GB)
备份            两份完整快照保留于 openclaw-backups/
© 2026 Farmer · 静态站(Hugo) · 分享生命的体验 · 热爱度过的每一天
无广告 · 无跟踪脚本 · 可能主要就自己看😄