返回星港

Cloudreve 离线下载 panic 修复:在 RK3588 上自建 aria2 组件

OBSERVER又菜又爱玩的小朱 LAUNCH JUMPS0 次跃迁
Cloudreve 离线下载 panic 修复:在 RK3588 上自建 aria2 组件 · 封面

引子:点开离线下载就报错

Cloudreve 的离线下载是个很省事的功能:把 HTTP 直链或 BT 磁力丢给服务端,下载完直接转存进网盘。但这台 RK3588 盒子上的实例,离线下载一直用不起来。

排查后原因挺“结构性”:Cloudreve 4.19.1 的主节点配置里只保存了 provider=aria2,却没有任何 aria2 子配置,而服务器上原本也没有装下载器。官方构造函数在这个状态下会读取空指针并直接 panic。也就是说,这不是“下载慢”或“连不上”,而是配置与实现状态不匹配导致的崩溃。

修复思路有两种:升版本,或者把缺失的下载器补上。这次选后者——不升级 Cloudreve、不重启本体、不改用户组、站点 URL、搜索设置、原文件,也不去动那些既有的失败任务,只新建一个独立的 aria2 组件顶上去。

环境与前提

项目

值

平台

RK3588 ARM64(ztl A588),Ubuntu 24.04

Cloudreve

社区版 4.19.1,宿主机 systemd 服务

下载器

aria2 1.37.0,装在独立容器内

运行目录

/opt/DockerDate/cloudreve-aria2

临时大文件

/media/ztl/LinuxDate/cloudreve/offline-download(外盘)

实施:把 aria2 装进一个加固过的容器

基础镜像与安装方式

新建 Docker Compose 组件 cloudreve-aria2,基础镜像用 Ubuntu 24.04 ARM64 并按摘要固定,aria2 通过 APT 签名验证安装,版本锁定 1.37.0。

这里有个小坑:首次构建时基础镜像里没有 CA 证书,无法直接走 HTTPS。做法是先从“APT 签名验证过的 HTTP 仓库”装 CA,再恢复 HTTPS——全程没有关闭证书校验或 APT 签名校验。

Dockerfile 全文如下。镜像摘要与末尾的 UID/GID 都按本机固定(997:982 即 cloudreve 账号),换机器时请先核对自己环境里的摘要与账号:

FROM ubuntu@sha256:534baea6a22c03a63003dbc8dbe78fe34bc0d7e595d9a9dc9834884ff530eb55
# 基础镜像没有CA;先用APT签名验证的仓库安装CA,再切回HTTPS。
RUN sed -i 's@http://ports.ubuntu.com/ubuntu-ports/@http://mirrors.aliyun.com/ubuntu-ports/@g' /etc/apt/sources.list.d/ubuntu.sources \
    && apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates \
    && sed -i 's@http://mirrors.aliyun.com/@https://mirrors.aliyun.com/@g' /etc/apt/sources.list.d/ubuntu.sources \
    && apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends aria2=1.37.0+debian-1build3 curl \
    && rm -rf /var/lib/apt/lists/*
USER 997:982
ENTRYPOINT ["/usr/bin/aria2c"]
CMD ["--conf-path=/etc/aria2/aria2.conf"]

网络与凭据

只把 127.0.0.1:6800/tcp 的 RPC 端口发布到宿主机,令牌随机生成,配置文件权限 600、属主为 cloudreve;不开放 BT 入站端口,也没有任何公网映射。容器内部是 rpc-listen-all,那只是为了配合 Docker 的端口转发,并不等于宿主机上通配发布。

需要提醒的一点:如果同一独立网桥内的其他容器也加入进来,理论上可以连到 RPC,但必须通过认证。RPC 的实际令牌保存在运行目录的 aria2.conf 以及节点数据库里,不要把它复制进聊天、Git 或普通备份。

aria2.conf 的实际内容大致如下。其中 rpc-secret 我已经换成了占位符 <随机 RPC 令牌>:真实值由 install.py 用 Python 的 secrets.token_hex(32) 生成(等价于命令行 openssl rand -hex 32),生成后文件权限 600、属主 cloudreve。照着复刻时请自己生成一份,不要沿用别处的令牌:

enable-rpc=true
rpc-listen-all=true
rpc-listen-port=6800
rpc-allow-origin-all=false
disable-ipv6=true
rpc-secret=<随机 RPC 令牌>
dir=/media/ztl/LinuxDate/cloudreve/offline-download
continue=true
allow-overwrite=false
auto-file-renaming=true
file-allocation=none
max-concurrent-downloads=3
max-connection-per-server=4
split=4
connect-timeout=20
timeout=60
max-tries=5
retry-wait=5
check-certificate=true
seed-time=0
seed-ratio=0
enable-dht=true
enable-dht6=false
enable-peer-exchange=true
bt-enable-lpd=false
listen-port=6888
summary-interval=0
console-log-level=warn
disk-cache=16M
input-file=/state/session.txt
save-session=/state/session.txt
save-session-interval=30
force-save=true
dht-file-path=/state/dht.dat
dht-listen-port=6888

运行目录里的 .env 只有一个用途:把容器运行身份交给 Compose。变量名如下,值就是 cloudreve 账号的 UID/GID(本机是 997/982),并不含密钥:

CLOUDREVE_UID=<cloudreve 服务账号 UID>
CLOUDREVE_GID=<cloudreve 服务账号 GID>

容器加固与资源限制

  • 唯一新增容器 cloudreve-aria2,restart=no(由 USB 守护负责恢复);

  • CPU 绑定 0–3(Cortex-A55 小核),内存上限 512 MiB,进程数 128,句柄数 65535;

  • 以非 root 运行,UID/GID 与 cloudreve 匹配;

  • 只读 rootfs、ALL capability drop、no-new-privileges。

临时大文件目录 /media/ztl/LinuxDate/cloudreve/offline-download 属主为 cloudreve、权限 700,以同一绝对路径 bind 进容器。因为 Cloudreve 本体的 systemd 可写范围本来就覆盖这里,所以不需要额外放宽权限。会话与 DHT 状态放在运行目录的 state/(系统盘,只存元数据),30 秒保存一次会话、正常退出时也会保存;异常断电仍可能丢失最近的会话,也不保证所有类型的任务都能自动续传。

下载参数按“别把机械盘搞乱”来设:并行下载 3、每服务器连接 4、分片 4、不预分配空间、不覆盖现有文件,下载完成后不持续做种。BT 只启用出站 DHT/PEX,宿主机没有 6888 入站映射,因此连接性和速度会受 NAT、资源热度影响。

上面这些限制与挂载都写在同一份 compose.yaml 里,全文如下(create_host_path: false 是为了让缺盘时不会在 eMMC 上悄悄建目录):

name: cloudreve-aria2
services:
  aria2:
    image: cloudreve-aria2:1.37.0-arm64
    build:
      context: .
    container_name: cloudreve-aria2
    restart: "no"
    user: "${CLOUDREVE_UID:-997}:${CLOUDREVE_GID:-982}"
    cpuset: "0-3"
    mem_limit: 512m
    pids_limit: 128
    read_only: true
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    ulimits:
      nofile:
        soft: 65535
        hard: 65535
    ports:
      - "127.0.0.1:6800:6800/tcp"
    volumes:
      - type: bind
        source: /opt/DockerDate/cloudreve-aria2/aria2.conf
        target: /etc/aria2/aria2.conf
        read_only: true
        bind:
          create_host_path: false
      - type: bind
        source: /media/ztl/LinuxDate/cloudreve/offline-download
        target: /media/ztl/LinuxDate/cloudreve/offline-download
        bind:
          create_host_path: false
      - type: bind
        source: /opt/DockerDate/cloudreve-aria2/state
        target: /state
        bind:
          create_host_path: false
    tmpfs:
      - /tmp:size=16m,mode=1777
    healthcheck:
      test: [CMD, curl, --silent, --max-time, "5", --output, /dev/null, http://127.0.0.1:6800/jsonrpc]
      interval: 30s
      timeout: 6s
      retries: 3
    logging:
      driver: json-file
      options:
        max-size: 5m
        max-file: "2"

与外盘守护的配合

外盘相关服务沿用原有的 usb-storage-guard 自动发现机制:先 compose create,再由守护确认真实 UUID、ext4 以及容器实际挂载之后才启动,bind 使用长语法并设置 create_host_path=false,缺盘时不会在 eMMC 上悄悄建目录。整个过程没有修改守护的源码或配置,也没有重启其他容器。

服务菜单 /root/AI/manage-services.sh 能自动发现 Docker / cloudreve-aria2,不需要改固定宿主机清单;停止/启动通过守护 pause/resume。1Panel 里可以查看这个容器,但本次没有通过 1Panel API 登记编排,后续不要重复创建同名组件。

让 Cloudreve 认识这个下载器

组件跑起来只是第一步,还要把主节点配置改成“既声明 provider=aria2,又有完整 aria2 子配置”的合法状态。管理脚本读取本机私密 conf.ini,只用只读方式查询数据库,临时签发 120 秒的管理员访问凭据、仅在内存中使用,不保存访问令牌、不重置密码、不创建账户。configure 的顺序是:先保存 600 权限的节点私密快照 → 通信测试 → 通过官方 API 更新并刷新节点池,全程无需重启 Cloudreve。快照只存 private 目录,不进普通归档。

# 只读通信/认证检查
/opt/cloudreve-offline/venv/bin/python -B /root/AI/cloudreve/offline-download/manage.py check
# 先预演,显式 --apply 才更新主节点
/opt/cloudreve-offline/venv/bin/python -B /root/AI/cloudreve/offline-download/manage.py configure
/opt/cloudreve-offline/venv/bin/python -B /root/AI/cloudreve/offline-download/manage.py configure --apply
# 停止下载器(只影响离线下载)
usb-storage-guard pause cloudreve-aria2
docker stop -t 30 cloudreve-aria2
# 恢复;由守护核验盘后启动,不直接 docker start
usb-storage-guard resume cloudreve-aria2
# 状态与日志
/root/AI/manage-services.sh --status
docker ps --filter name=cloudreve-aria2
docker logs --tail 50 cloudreve-aria2
journalctl -u cloudreve -n 50 --no-pager

该管理工具需要 root 权限,应与其他运维脚本一样保持仅 root 可写。日志里可能包含用户的下载 URL,不要放进普通归档。

验证结果

  • 配置 API 通信返回 1.37.0;认证 RPC 正常,未认证请求被拒绝;从 Windows 经局域网访问 192.168.10.62:6800 被拒绝。

  • 真实 Cloudreve 工作流 ndoCz 状态 completed:通过 HTTPS 下载官方 LICENSE,35146 字节,外盘转存实体与源逐字节相等,SHA256=c53a65c2fd561c87eaabf1072ef5dcab8653042bc15308465f52413585eb6271。

  • 测试目录为 离线下载验证-20261012-220509-deae6d;只新增了该目录和小文件,原有失败任务没有被重试。

  • 维护 pause、正常停止时保存会话、resume 后由守护自动启动、同 GID 暂停任务恢复均通过,探针任务已移除;重启后全部 1 个线程仍绑定 CPU0–3。

  • 只读 rootfs、内存上限、restart=no 及回环端口检查通过;5 项模板/语法/默认预演测试、38 项既有 USB 守护单元测试通过。

  • 整个过程无需重启 Cloudreve 本体。

日常使用与回退

刷新 Cloudreve,通过原来的离线下载入口新建任务即可。此前失败的任务不会自动重试,需要逐项重新创建。注意临时目录不是网盘最终存储目录,下载完成后仍按目标文件夹的存储策略转存;独立的测试目录保留着供检查,可以在网页上手工删除。

回退的顺序很重要:先在 Cloudreve 后台关闭主节点的离线下载能力,再 pause/stop 本下载器。关闭时不要“清空 aria2 子配置却保留 provider”,那正好会重新触发 panic。不要删除下载临时文件或 session 状态。若必须恢复原节点配置,使用 private 目录中最近的 node-before 快照,通过 manage.py 的 api('PUT', '/admin/node/1', {'node': snapshot}) 保存;但旧快照本身就是故障配置,不建议恢复后继续使用离线下载。回退不会重放业务任务、不恢复整库数据库,也不影响已经转存的文件。

边界与未测项

本次未真实拔盘或做缺盘整机重启,未做 BT 真实下载,未做大文件、长期并发与断电测试;缺盘行为依赖守护已有的检查与测试,因此不宣称“零丢失”。安装与还原方面,前提是先恢复 Cloudreve 本体/数据库/Blob 存储、cloudreve 系统账号、Docker Compose、USB 守护和真实 UUID 挂载;默认路径及“主节点 1 / 管理员用户 1”只适配这台既有实例,换机器需要先审阅 manage.py,不要盲目运行:

python3 -m venv /opt/cloudreve-offline/venv
/opt/cloudreve-offline/venv/bin/pip install hashids==1.3.1
# 默认预演,不启动、不修改节点
/opt/cloudreve-offline/venv/bin/python -B /root/AI/cloudreve/offline-download/install.py
# 明确写入;已有不同 Compose/Dockerfile 会拒绝覆盖,配置/状态不覆盖
/opt/cloudreve-offline/venv/bin/python -B /root/AI/cloudreve/offline-download/install.py --apply
docker compose -f /opt/DockerDate/cloudreve-aria2/compose.yaml build --pull=false
docker compose -f /opt/DockerDate/cloudreve-aria2/compose.yaml create

实际安装会联网下载锁定的 aria2 版本及发行版更新依赖,未来仓库移除该版本时需要人工评估升级。归档四件套 cloudreve-offline-download 默认预演,不含实际 RPC 令牌、数据库、用户文件、session/DHT 状态、私密节点快照、虚拟环境或上游源码缓存;镜像可由 Dockerfile 重新构建。

附:完整脚本

上面步骤里用到的完整脚本不再整段贴在正文,已作为附件上传到对象存储,点击文件名即可下载:

  • manage.py —— 下载器管理脚本

  • install.py —— 安装脚本,含随机令牌生成

对象存储策略会自动把文件名改写成 UUID(例如 95D96A8A-FB38-4CCB-AB42-F6E906E70760.py),这是存储策略的行为,不影响文件内容;下载后按正文里的路径改名放好即可。