Debian 11 小白教程:OneDrive 挂载到服务器 + 安装 qBittorrent + 下载后自动搬运到 OneDrive + 清理文件/日志
适用:Debian GNU/Linux 11(bullseye),root 登录,x86_64
目标:
1) OneDrive 用 rclone 挂载到/mnt/onedrive(开机自启)
2) 安装qbittorrent-nox(WebUI 管理)
3) 下载到本地落盘(/data/qbt),完成后自动rclone move到 OneDrive 并自动删除 qB 任务
4) 需要时一键清理日志/空目录/残留文件
0. 准备:更新系统 + 安装常用工具
apt update
apt install -y curl wget unzip fuse3 jq python3说明:
fuse3:挂载必需python3:脚本里用来解析 qB WebAPI JSONjq:可选,但装上不亏
1. 安装 rclone
1.1 一键安装(官方脚本)
curl https://rclone.org/install.sh | bash检查:
rclone version2. 配置 OneDrive(生成 remote:onedrive)
2.1 允许 allow_other(让非 root 也能访问挂载)
sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf
grep -n 'user_allow_other' /etc/fuse.conf2.2 创建 OneDrive remote
rclone config按提示输入(重要点):
- 输入
n新建 remote - name 输入:
onedrive - Storage 选择:
Microsoft OneDrive(通常编号是 38) client_id直接回车(默认)region回车(global)tenant回车- 服务器无浏览器:选
n 在有浏览器的电脑上执行:
rclone authorize "onedrive"把输出的 完整 JSON(包含 access_token/refresh_token 那坨)粘贴回服务器
config_token。- 后面按默认选项即可(onedrive / driveid 等按你的账户选择)
测试是否成功:
rclone listremotes
rclone lsd onedrive:3. 挂载 OneDrive 到服务器(/mnt/onedrive,开机自启)
3.1 创建挂载点和缓存目录
mkdir -p /mnt/onedrive /var/cache/rclone3.2 创建 systemd 服务(推荐)
cat >/etc/systemd/system/rclone-onedrive.service <<'EOF'
[Unit]
Description=Rclone Mount OneDrive
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/rclone mount onedrive: /mnt/onedrive --allow-other --cache-dir /var/cache/rclone --vfs-cache-mode writes --vfs-cache-max-size 15G --vfs-cache-max-age 72h --vfs-write-back 30s --dir-cache-time 72h --poll-interval 1m
ExecStop=/bin/fusermount3 -u /mnt/onedrive
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF启用并启动:
systemctl daemon-reload
systemctl enable --now rclone-onedrive.service
systemctl status rclone-onedrive.service --no-pager验证挂载:
mount | grep /mnt/onedrive
ls /mnt/onedrive | head提醒: fusermount3 -u /mnt/onedrive 只有在已挂载时才会成功。
如果提示 Invalid argument,通常是它本来就没挂载(先 mount | grep 看)。
4. 安装 qBittorrent-nox(WebUI)
4.1 安装
apt install -y qbittorrent-nox4.2 创建专用用户 qb(更安全)
adduser --disabled-password --gecos "" qb4.3 创建下载目录(本地落盘)
mkdir -p /data/qbt/incomplete /data/qbt/complete
chown -R qb:qb /data/qbt5. 让 qBittorrent 作为服务运行(开机自启)
创建 systemd 服务:
cat >/etc/systemd/system/qbittorrent-nox.service <<'EOF'
[Unit]
Description=qBittorrent-nox
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=qb
Group=qb
UMask=002
ExecStart=/usr/bin/qbittorrent-nox --webui-port=18230
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF启动:
systemctl daemon-reload
systemctl enable --now qbittorrent-nox
systemctl status qbittorrent-nox --no-pager确认端口监听:
ss -lntp | grep 182305.1 登录 WebUI
浏览器打开:http://服务器IP:18230
首次登录后在 WebUI 修改密码。
6. qB 下载完成后自动上传 OneDrive + 删除 qB 任务(核心)
6.1 让 qb 用户也能用 rclone(复制 rclone.conf)
mkdir -p /var/lib/qbittorrent/.config/rclone
cp /root/.config/rclone/rclone.conf /var/lib/qbittorrent/.config/rclone/rclone.conf
chown -R qb:qb /var/lib/qbittorrent/.config
chmod 600 /var/lib/qbittorrent/.config/rclone/rclone.conf测试 qb 是否能访问 OneDrive:
sudo -u qb rclone lsd onedrive:6.2 建日志文件
touch /var/log/qbt-rclone-move.log
chown qb:qb /var/log/qbt-rclone-move.log
chmod 664 /var/log/qbt-rclone-move.log6.3 创建 qB WebAPI 凭据文件(用于脚本删任务)
把 QB_USER 改成你的 WebUI 用户名(你的是 brian),把 QB_PASS 改成你的 WebUI 密码:
cat > /etc/qb-webapi.env <<'EOF'
QB_URL=http://127.0.0.1:18230
QB_USER=brian
QB_PASS=把这里改成你WebUI密码
EOF
chown qb:qb /etc/qb-webapi.env
chmod 600 /etc/qb-webapi.env6.4 创建“完成后搬运到 OneDrive 并删除任务”的脚本(稳定版)
cat > /usr/local/bin/qbt-to-onedrive-move.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
# 让中文路径稳定
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
export PYTHONUTF8=1
# WebUI 外部程序建议:/usr/local/bin/qbt-to-onedrive-move.sh "%F" "%N"
ARG_PATH="${1:-}"
ARG_NAME="${2:-}"
# 兼容某些版本会导出的环境变量
ENV_PATH="${QB_CONTENT_PATH:-}"
ENV_NAME="${QB_NAME:-unknown}"
CONTENT_PATH="${ARG_PATH:-$ENV_PATH}"
NAME="${ARG_NAME:-$ENV_NAME}"
LOCAL_COMPLETE_BASE="/data/qbt/complete"
REMOTE_BASE="onedrive:/qbittorrent"
LOG="/var/log/qbt-rclone-move.log"
ENV_FILE="/etc/qb-webapi.env"
ts() { date '+%F %T'; }
log() { echo "[$(ts)] $*" | tee -a "$LOG"; }
trap 'echo "[$(ts)] ERROR: script failed at line $LINENO" >>"$LOG"' ERR
# 没拿到路径就跳过
if [[ -z "${CONTENT_PATH:-}" ]]; then
echo "[$(ts)] SKIP: empty content path. NAME=${NAME:-unknown}" >>"$LOG"
exit 0
fi
# 只处理 complete 目录
if [[ "$CONTENT_PATH" != "$LOCAL_COMPLETE_BASE"* ]]; then
echo "[$(ts)] SKIP: not in complete dir: $CONTENT_PATH NAME=$NAME" >>"$LOG"
exit 0
fi
# 下载什么就搬什么:目录本体搬到 OneDrive/<目录名>;单文件直接放根
BASENAME="$(basename -- "$CONTENT_PATH")"
if [[ -d "$CONTENT_PATH" ]]; then
REMOTE_TARGET="${REMOTE_BASE}/${BASENAME}"
else
REMOTE_TARGET="${REMOTE_BASE}/"
fi
log "START: NAME=$NAME PATH=$CONTENT_PATH -> $REMOTE_TARGET"
# 排队上传(1核1G更稳):不加 -n
flock /tmp/qbt_rclone.lock rclone move "$CONTENT_PATH" "$REMOTE_TARGET" --transfers 2 --checkers 4 --retries 10 --low-level-retries 20 --onedrive-chunk-size 10Mi --log-level INFO --log-file "$LOG"
# 清理空目录
find "$LOCAL_COMPLETE_BASE" -type d -empty -delete 2>/dev/null || true
log "UPLOAD_DONE: NAME=$NAME PATH=$CONTENT_PATH"
# -------- WebAPI 删除 qB 任务(不删文件)--------
if [[ ! -r "$ENV_FILE" ]]; then
log "WARN: $ENV_FILE not readable, skip qb delete"
log "DONE: NAME=$NAME PATH=$CONTENT_PATH"
exit 0
fi
source "$ENV_FILE"
COOKIE="/tmp/qb_cookie_$$.txt"
JSON_FILE="/tmp/qb_torrents_$$.json"
cleanup() { rm -f "$COOKIE" "$JSON_FILE"; }
trap cleanup EXIT
LOGIN_OK="$(curl -sS -c "$COOKIE" --data-urlencode "username=${QB_USER}" --data-urlencode "password=${QB_PASS}" "${QB_URL}/api/v2/auth/login" || true)"
if [[ "$LOGIN_OK" != "Ok." ]]; then
log "WARN: qb login failed (${LOGIN_OK}), skip qb delete"
log "DONE: NAME=$NAME PATH=$CONTENT_PATH"
exit 0
fi
curl -sS -b "$COOKIE" "${QB_URL}/api/v2/torrents/info" -o "$JSON_FILE" || true
if [[ "$(head -c 1 "$JSON_FILE")" != "[" ]]; then
log "WARN: torrents/info not JSON. head=$(head -c 120 "$JSON_FILE")"
log "DONE: NAME=$NAME PATH=$CONTENT_PATH"
exit 0
fi
HASH="$(python3 - "$CONTENT_PATH" "$NAME" "$JSON_FILE" 2>>"$LOG" <<'PY' || true
import json, os, sys
content_path = os.path.realpath(sys.argv[1])
name = sys.argv[2]
json_file = sys.argv[3]
with open(json_file, "r", encoding="utf-8") as f:
data = json.load(f)
def norm(p):
try:
return os.path.realpath(p)
except Exception:
return p or ""
# 1) 名称精确匹配(最稳)
for t in data:
if (t.get("name") or "") == name:
h = t.get("hash") or ""
if h:
print(h)
raise SystemExit
# 2) content_path 精确匹配(兜底)
for t in data:
cp = norm(t.get("content_path",""))
if cp and cp == content_path:
h = t.get("hash") or ""
if h:
print(h)
raise SystemExit
# 3) save_path + name 再兜底
for t in data:
sp = t.get("save_path","") or ""
nm = t.get("name","") or ""
guess = norm(os.path.join(sp, nm))
if guess == content_path:
h = t.get("hash") or ""
if h:
print(h)
raise SystemExit
PY
)"
if [[ -z "${HASH:-}" ]]; then
log "WARN: cannot find torrent hash for NAME=$NAME PATH=$CONTENT_PATH, skip qb delete"
log "DONE: NAME=$NAME PATH=$CONTENT_PATH"
exit 0
fi
DEL_OK="$(curl -sS -b "$COOKIE" --data-urlencode "hashes=${HASH}" --data-urlencode "deleteFiles=false" "${QB_URL}/api/v2/torrents/delete" || true)"
log "QB_DELETE: HASH=$HASH RESULT=${DEL_OK:-ok}"
log "DONE: NAME=$NAME PATH=$CONTENT_PATH"
EOF
chmod +x /usr/local/bin/qbt-to-onedrive-move.sh7. WebUI 设置:下载路径 + 完成后执行脚本(照抄)
打开 qB WebUI → Settings/Options:
7.1 Downloads(下载)
- Default Save Path:
/data/qbt/complete - Keep incomplete torrents in:勾选,路径:
/data/qbt/incomplete
7.2 Downloads → Run external program(外部程序)
- 勾选:Run external program on torrent completion
- 填入(照抄):
/usr/local/bin/qbt-to-onedrive-move.sh "%F" "%N"说明:
%F= 完整内容路径(文件或文件夹)%N= 任务名称- 脚本会把“下载的那个文件夹/文件”整体搬到 OneDrive,并自动删 qB 任务(不删云端文件)。
8. 如何看是否正常工作
8.1 实时看脚本日志
tail -f /var/log/qbt-rclone-move.log你会看到类似:
START ...- rclone 传输进度(Transferred / ETA)
UPLOAD_DONE ...QB_DELETE ... RESULT=okDONE ...
8.2 OneDrive 上查看是否有目录保留
sudo -u qb rclone lsf onedrive:/qbittorrent --max-depth 2 | head9. 清理:日志、空目录、残留文件(重新测试用)
9.1 清空脚本日志(不删除文件,只清空内容)
sudo -u qb truncate -s 0 /var/log/qbt-rclone-move.log9.2 清理本地 complete 下的空目录
find /data/qbt/complete -type d -empty -delete9.3 清理 incomplete(谨慎:会删未完成下载)
rm -rf /data/qbt/incomplete/*10. 常见问题(小白必看)
Q1:为什么 qB 任务下载完还在列表里?
原因:脚本没能通过 WebAPI 删除任务(密码错/接口失败/脚本异常)。
检查日志是否有:
QB_DELETE ... RESULT=ok(有这个就会删)WARN/ERROR(按日志提示修)
Q2:为什么 rclone move 后本地文件没了?
因为你用的是 move:上传成功后删除本地源文件,这是预期行为(你“不需要本地做种”的需求)。
Q3:能不能“下载什么就搬什么(整个文件夹)”?
可以。脚本已经实现:如果 %F 是目录,就搬整个目录到 OneDrive 同名目录里。
Q4:我的服务器 1C1G 会不会崩?
建议:
- rclone 用低并发:
--transfers 2 --checkers 4(脚本已按低配优化) - 加 swap(你已做 1G swap 很合理)
11. 停止/卸载(可选)
11.1 停止 OneDrive 挂载
systemctl disable --now rclone-onedrive.service
mount | grep /mnt/onedrive || true11.2 停止 qB
systemctl disable --now qbittorrent-nox最终效果(你应该看到的)
/mnt/onedrive:像本地目录一样能浏览 OneDrive(挂载)- qB 下载到
/data/qbt/incomplete→ 完成后进/data/qbt/complete - 完成触发脚本:自动把“文件/文件夹”搬到
onedrive:/qbittorrent/ - 搬完自动删除 qB 任务(列表干净)
- 需要时随时
truncate清空日志,重新测试