想把服务器上的文件存到 115,又不想每次都打开网页上传,可以用 OpenList 接入网盘,再通过 rclone 传输。需要给其他程序一个文件夹时,把它挂载到 /mnt/115 就行。
这篇从安装开始,先跑通上传、下载和开机挂载,再加一个 Telegram 文件转存机器人。只需要传文件的话,做到第三步就够了,不必为了“挂载”多装一层服务。
示例环境:Debian 13、Docker Compose、OpenList v4.2.6。以下服务器命令按 root 用户编写,普通用户执行时加 sudo。示例使用网盘目录
/VPS,可以换名字,但后面的配置要保持一致。本文不会公开任何实际账号、密码或授权令牌。
开始前,先选对传输方式
| 手里有什么 | 应该怎么处理 | 是否经过服务器 |
|---|---|---|
| 服务器或 Telegram 上的文件 | 上传到 115 | 经过,需要带宽;落盘模式还需要临时空间 |
| magnet、ed2k、torrent、公开下载直链 | 优先交给 115 的原生离线下载 | 115 离线不经过服务器;磁力和 ed2k 仅在额度耗尽时由 CCS 兜底 |
| 115 分享链接 | 直接在 115 内转存 | 不经过,不用先下载再上传 |
不要把这三件事混在一起。比如收到一个 100 GB 的 115 分享,优先做云端转存,而不是给 VPS 找 100 GB 的空闲磁盘。
第一步:把 OpenList 跑起来
先确认 Docker 和 Compose 可用。如果还没安装,按 Docker 的 Debian 安装说明处理。
docker --version
docker compose version
apt update
apt install -y rclone fuse3 openssl git python3
创建目录并生成首次管理员密码。下面生成的是你自己的随机密码,不是本文网站的密码。
install -d -m 700 /srv/openlist
install -d -m 700 -o 1001 -g 1001 /srv/openlist/data
cd /srv/openlist
umask 077
printf 'OPENLIST_ADMIN_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
用编辑器新建 /srv/openlist/compose.yaml,填入:
services:
openlist:
image: openlistteam/openlist:v4.2.6
container_name: openlist
user: "1001:1001"
ports:
- "127.0.0.1:5244:5244"
volumes:
- ./data:/opt/openlist/data
environment:
TZ: Asia/Shanghai
UMASK: "077"
OPENLIST_ADMIN_PASSWORD: ${OPENLIST_ADMIN_PASSWORD}
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
mem_limit: 512m
cpus: "1.0"
restart: unless-stopped
logging:
driver: local
options:
max-size: "10m"
max-file: "2"
docker compose config -q
docker compose pull
docker compose up -d
docker compose ps
正常结果:OpenList 容器处于运行状态,没有反复重启。数据目录要属于 UID/GID 1001;这是当前官方镜像的运行用户,不能沿用旧教程里的 PUID、PGID 设置。详情见 OpenList Docker 文档。
第一次怎么打开后台?
5244 只监听服务器本机,所以不要直接在浏览器输入“服务器 IP:5244”。在自己的电脑上开一个终端,执行下面的 SSH 转发,并把 YOUR_SERVER_IP 换成自己的地址:
ssh -N -L 15244:127.0.0.1:5244 root@YOUR_SERVER_IP
SSH 如果不是 22 端口,在命令里加 -p 你的端口。保持这个终端开着,再在本机浏览器打开 http://127.0.0.1:15244。
用户名是 admin,首次密码保存在服务器的 /srv/openlist/.env。只在自己的终端查看,别把这个文件、初始化日志或截图发到公开网站。OPENLIST_ADMIN_PASSWORD 用于首次初始化;修改已有实例的 .env 不等于修改后台密码。
长期使用时再给 OpenList 配一个 HTTPS 域名。我的入口是 files.bismih520.com,这是私人服务,不是供读者共用的网盘。外面还有 GitHub 登录保护,内部传输仍走本机接口。
第二步:接入 115,创建传输账号
进入 OpenList 后台的“存储”,添加一个 115 Open 存储。不要选成其他 115 驱动,授权方式不一样。
| 配置项 | 填写内容 |
|---|---|
| 挂载路径 | /115 |
| 驱动 | 115 Open |
| 根目录 ID | 0,先接入自己的网盘根目录 |
| Access Token / Refresh Token | 通过官方授权流程取得,不使用别人分享的值 |
| 接口限频 | 先设为每秒 1 次,不靠堆请求加速 |
按 115 Open 驱动文档,从 OpenList 令牌入口登录授权。保存后,回到文件页打开 /115;能列出自己的文件,才继续下一步。
在网盘里新建 /VPS,然后到后台“用户”创建一个普通账号,例如 transfer115:
- 基本路径:
/115/VPS。 - 开启 WebDAV 读取、WebDAV 管理,以及创建目录、上传所需的权限。
- 先不授予无关权限;禁用游客访问,不用管理员账号给日常脚本传文件。
这样,传输账号的 WebDAV 根目录就是网盘的 /VPS。后面上传到 vps115:hello.txt,实际落在 /VPS/hello.txt,不用再重复拼一次 115/VPS。权限名称以当前后台为准,可对照 WebDAV 配置说明。
OpenList 会维护这份 Open 授权。不要让另外一个脚本同时刷新同一份 Refresh Token,否则两个程序容易互相使授权失效。
第三步:用 rclone 上传和下载
在服务器上运行:
install -d -m 700 /etc/rclone
rclone config --config /etc/rclone/115.conf
选择新建远端,按下面的值填写。rclone 菜单序号可能变化,按名称选择,不照抄序号。
| 提示 | 填写内容 |
|---|---|
| name | vps115 |
| Storage | webdav |
| url | http://127.0.0.1:5244/dav/ |
| vendor | other |
| user | 刚创建的 transfer115 |
| pass | 该用户的密码 |
高级配置先使用默认值,确认保存后,检查能不能列目录:
chmod 600 /etc/rclone/115.conf
rclone --config /etc/rclone/115.conf lsf vps115:
目录为空时没有输出也正常,但不能出现 401、403 或连接失败。WebDAV 的配置细节见 rclone WebDAV 文档。
先用一个小文件走完整流程
test_dir=$(mktemp -d)
printf 'Hello, 115!\n' > "$test_dir/hello.txt"
rclone --config /etc/rclone/115.conf copyto \
"$test_dir/hello.txt" vps115:tests/hello.txt --immutable
rclone --config /etc/rclone/115.conf copyto \
vps115:tests/hello.txt "$test_dir/returned.txt" --immutable
sha256sum "$test_dir/hello.txt" "$test_dir/returned.txt"
正常结果:两行 SHA-256 一样,而且在 115 官方客户端里能看到 /VPS/tests/hello.txt。
日常使用就沿用这两个命令,把路径换成自己的文件。--immutable 用来避免覆盖云端已有的不同内容;目标已经有同名文件时,先核实再处理。不要把 copy 随手换成 sync,后者可能删除目标中源端没有的文件。
第四步:需要文件夹时,再挂载到 /mnt/115
rclone 已经能传文件后,再配置挂载。先检查 FUSE:
test -c /dev/fuse && echo "FUSE 可用"
install -d -m 700 /mnt/115 /srv/115-cache
没有 /dev/fuse 的 VPS 或受限容器,不能直接照做 FUSE 挂载;但前面的 rclone 上传、下载仍然可以使用。
新建 /etc/systemd/system/mount115.service:
[Unit]
Description=115 drive via OpenList WebDAV
After=network-online.target docker.service
Wants=network-online.target
Requires=docker.service
[Service]
Type=notify
UMask=0077
ExecStart=/usr/bin/rclone mount vps115: /mnt/115 --config /etc/rclone/115.conf --vfs-cache-mode writes --cache-dir /srv/115-cache --vfs-cache-max-size 1G --vfs-cache-max-age 1h --vfs-write-back 5s --buffer-size 8M --dir-cache-time 1m --poll-interval 0 --tpslimit 1 --log-level ERROR
ExecStop=/usr/bin/fusermount3 -u /mnt/115
Restart=on-failure
RestartSec=10
TimeoutStartSec=120
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now mount115.service
systemctl status mount115.service --no-pager
mountpoint /mnt/115
ls -lah /mnt/115
正常结果:服务为 active,mountpoint 确认它是挂载点,文件列表与 WebDAV 根目录一致。Docker 比 OpenList 准备好得更早,所以开机短暂读取失败时先检查容器状态,不要直接重装。
现在可以像访问文件夹一样使用它,但它仍是网盘,不是本地硬盘。写入先进入缓存,关闭文件不代表云端已经保存好;大文件更适合用 rclone copyto 并等待完成。缓存的 1 GiB 是清理目标,不是活跃写入的硬上限。不要把数据库、Docker 数据目录或虚拟机磁盘放进去。参数说明见 rclone mount 文档。
第五步:加一个 Telegram 文件转存机器人
文件转存可以使用开源的 TG2Cloud。这里采用只部署 Bot的方式,复用前面已经安装的 OpenList,不再安装第二套网盘后台。
先准备自己的 Bot Token、Telegram 数字用户 ID,以及 Telegram API ID / API Hash。Bot Token 从 BotFather获取。不要同时让两个转存程序长期使用同一个 Bot Token。
在 115 新建 /VPS/Telegram,为 Bot 另建一个 OpenList 用户 tg115,基本路径设为 /115/VPS/Telegram。除 WebDAV 读写、创建和上传外,TG2Cloud 的取消与清理还需要删除能力,因此只把这个专用子目录交给它。
取源码,固定版本
install -d -m 700 /srv/tg2cloud
git clone https://github.com/LuoPoJunZi/TG2Cloud.git /srv/tg2cloud/source
git -C /srv/tg2cloud/source checkout 2e26407427216c52e46ac684167715388b97ba38
install -d -m 700 -o 10001 -g 10001 \
/srv/tg2cloud/data /srv/tg2cloud/downloads \
/srv/tg2cloud/logs /srv/tg2cloud/config
这个提交中的 Bot 核心在 payload_clouddrive2 目录,名字虽然带 CloudDrive2,但代码也支持 OpenList,靠后面的 TG2CLOUD_STORAGE_BACKEND 选择。
填入 .env
新建 /srv/tg2cloud/.env。下面的中文占位符都要替换,数字用户 ID 要填自己的,不能填 Bot 的 ID。
TELEGRAM_API_ID=你的API_ID
TELEGRAM_API_HASH_B64=你的API_HASH经Base64编码后的值
BOT_TOKEN_B64=你的BOT_TOKEN经Base64编码后的值
ALLOWED_USER_ID=你的Telegram数字用户ID
WEBDAV_URL_B64=aHR0cDovLzEyNy4wLjAuMTo1MjQ0L2Rhdi8=
WEBDAV_USERNAME_B64=dGcxMTU=
WEBDAV_PASSWORD_B64=tg115密码经Base64编码后的值
WEBDAV_TARGET_PATH_B64=
TG2CLOUD_STORAGE_BACKEND=openlist
LOCAL_TEMP_BUDGET_GB=4
MIN_FREE_DISK_GB=10
MAX_RETRIES=3
MEMORY_SOFT_MIN_MB=768
MEMORY_HARD_MIN_MB=384
预填的 URL 对应 http://127.0.0.1:5244/dav/,用户名对应 tg115。目标子路径留空,文件就写入该用户根目录,不要再填一遍 115/VPS/Telegram。
需要编码一个值时,在自己的终端运行下面的命令,输入原值后复制输出。原值不会进入 Shell 命令历史:
python3 -c 'import base64,getpass; print(base64.b64encode(getpass.getpass("原值:").encode()).decode())'
chmod 600 /srv/tg2cloud/.env
Base64 不是加密,编码后的值也不能公开。4 GiB 是临时任务预算,不是上游的单文件上限;上游对超预算文件有流式处理。OpenList 自己的缓存也不受这个预算控制,要给服务器留出余量。
创建 Bot 容器
新建 /srv/tg2cloud/compose.yaml:
services:
bot:
build:
context: ./source/payload_clouddrive2
dockerfile: Dockerfile
container_name: tg2cloud-bot
network_mode: host
env_file: .env
user: "10001:10001"
environment:
TZ: Asia/Shanghai
DATA_DIR: /data
DOWNLOAD_DIR: /downloads
LOG_DIR: /logs
RCLONE_CONFIG_PATH: /config/rclone.conf
volumes:
- ./data:/data
- ./downloads:/downloads
- ./logs:/logs
- ./config:/config
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=64m
mem_limit: 512m
cpus: "1.0"
restart: unless-stopped
stop_grace_period: 45s
cd /srv/tg2cloud
docker compose config -q
docker compose build
docker compose up -d
docker compose ps
这里使用 host 网络,是为了访问本机 OpenList 的 5244 端口;Bot 本身不需要开放公网管理端口。先私聊自己的 Bot 发送 /start,再转发一个允许转发的小文件,等队列完成后去 115 检查能否打开。
上游用文件大小复验传输结果,并不是对每个文件做内容哈希。重要文件可以按第三步重新下载校验。上述部署得到的是文件转存 Bot,不会因为装了 TG2Cloud 就自动具备 115 分享链接转存能力。
磁力、种子和 115 分享,怎样不绕远路?
拿到 magnet、ed2k 或 .torrent 时,可以直接在 115 官方客户端创建离线任务,选择网盘目标目录,再等它下载完成。需要接入程序时,参考 115 SDK 的离线接口:链接使用 offline/add_task_urls,种子使用 offline/torrent 解析后再提交 offline/add_task_bt。任务受理之后还要查询 offline/get_task_list,不能把“提交成功”当成“下载完成”。
115 分享更简单:在官方客户端打开分享,输入提取码,选择“转存”和目标目录。程序实现走分享接收接口,也不需要下载资源主体。但它的网页登录 Cookie 与 Open 驱动的 OAuth 令牌是两套授权,不能混用。
我的私人 Bot 在文件核心之外加了链接队列,可以发送 magnet、ed2k、下载直链和带提取码的 115 分享;使用 /links 看进度,明确失败的任务才用 /link_retry 编号 重试。这部分是另外编写的扩展,不包含在上面的上游部署步骤中。分享云端转存已跑通:接收接口成功后,还会检查目标目录是否真的出现文件,再发完成通知。文件受 115 限制时,以平台返回结果为准,不绕过限制。
额度耗尽时,怎样让 CCS 接手?
在私人 Bot 发送 /quota,可以查看 115 剩余离线次数、额度到期时间,以及 CCS 安全缓存容量。程序读取 offline/get_quota_info 返回的配额,不把网络超时或缺少字段解释成零额度。
完整的 magnet 和标准 ed2k 文件链接都优先交给 115。只有确认离线额度已耗尽,才创建 CCS 本地任务:磁力使用 aria2,ed2k 使用 aMule。aria2 的协议列表不包含 ed2k,不能把电驴链接直接丢给 aria2;aMule负责 eD2k/Kad。
本地任务按“排队 → 下载 → 上传 115 → 回读校验 → 清理缓存”运行。默认并发 1,缓存上限 12 GiB,系统盘预留 10 GiB;文件或整份种子超过安全容量时保留排队,不强行下载。115 授权失败、资源受限或提交结果不确定,都不会自动转成本地下载。两个下载器的管理接口只监听本机地址,并且拿不到 115 Cookie 或 OAuth 令牌。
本地下载、上传和回读校验链路使用 Debian 官方的小型公开文件验收通过。ed2k 链接也已由 115 离线接口实际受理;“受理”仍只是任务开始,资源是否有源和最终是否完成,要继续看队列状态。
遇到问题,按这个顺序查
容器启动后反复重启
cd /srv/openlist
docker compose ps
docker compose logs --tail 50 openlist
ls -ld /srv/openlist/data
先看目录权限和日志,不要直接删除数据目录。OpenList 数据库里保存着账号与授权信息,删了就需要重配。
rclone 提示 401 或 403
401 先检查 OpenList 专用账号和密码;403 再检查 WebDAV 读取、管理及对应文件操作权限。URL 必须带 /dav/。浏览器能打开网页,不代表同一用户就有 WebDAV 写权限。
如果外面套了 Cloudflare Access,命令行或手机 WebDAV 客户端可能拿到 GitHub 登录页,而不是文件。服务器与 Bot 使用本机地址,别让内部传输绕到受浏览器登录保护的公网域名。
写入了 /mnt/115,网盘里却还没出现
先查缓存和挂载服务,等待上传完成:
systemctl status mount115.service --no-pager
journalctl -u mount115.service -n 30 --no-pager
df -h /srv/115-cache
rclone --config /etc/rclone/115.conf lsf vps115:
没有确认云端成品可读之前,不删本地唯一原件。需要明确完成结果时,用 rclone copyto,不要只凭 cp 命令退出判断成功。
115 返回“登录超时”
先分清报错来自 Open 授权还是 Cookie 接口。Cookie 流程应保留完整的 UID、CID、SEID、KID,不要硬编码成只取旧的三项;当前客户端可参考 p115client。还要验证实际目录或分享接口,不是拿到 Cookie 就结束检查。
如果是 TCP 连接或 TLS 握手超时,查服务器到 115 的网络,不要不停重登,也不要关闭证书校验。可以先做不带凭据的连接检查:
curl -I --connect-timeout 10 --max-time 20 https://webapi.115.com/
HTTP 400、404 本身不代表登录失败,这条命令只用于检查是否连到了 HTTPS 服务。提交操作超时且结果不确定时,先查云端目录或任务列表,确认没有执行后再重试,避免重复任务。
日常维护与备份
升级前保留 Compose、OpenList 数据目录、rclone 配置,以及 Bot 的 .env 和 SQLite 数据。OpenList 的一致性备份最好在停掉对应容器后进行;不要直接复制正在写入的 SQLite 文件充当完整快照。备份中含密钥和会话,只放私有位置。
WebDAV 管理权限可能允许删除。需要保护普通传输目录时,可在本机反向代理层额外拒绝 DELETE;Bot 的取消与清理则用另一个受限账号、另一个子目录,别共用一套全盘权限。
本文基础方案已在 CCS 上运行。上传下载的小文件校验一致;115 原生离线完成了 792,723,456 字节的 Debian 官方镜像;Telegram 文件链路测试了 29,628,128 字节的视频并核对内容哈希;分享云端转存已确认接收成功、目标目录和下级目录可列出。目录转存验证不等于逐个视频内容校验,也不保证受平台限制的文件可以播放或下载。
先把一个小文件传完整,再考虑批量和大文件。对这类服务来说,稳定保存下来比瞬时速度更重要。
