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 JSON
  • jq:可选,但装上不亏

1. 安装 rclone

1.1 一键安装(官方脚本)

curl https://rclone.org/install.sh | bash

检查:

rclone version

2. 配置 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.conf

2.2 创建 OneDrive remote

rclone config

按提示输入(重要点):

  1. 输入 n 新建 remote
  2. name 输入:onedrive
  3. Storage 选择:Microsoft OneDrive(通常编号是 38)
  4. client_id 直接回车(默认)
  5. region 回车(global)
  6. tenant 回车
  7. 服务器无浏览器:选 n
  8. 在有浏览器的电脑上执行:

    rclone authorize "onedrive"

    把输出的 完整 JSON(包含 access_token/refresh_token 那坨)粘贴回服务器 config_token

  9. 后面按默认选项即可(onedrive / driveid 等按你的账户选择)

测试是否成功:

rclone listremotes
rclone lsd onedrive:

3. 挂载 OneDrive 到服务器(/mnt/onedrive,开机自启)

3.1 创建挂载点和缓存目录

mkdir -p /mnt/onedrive /var/cache/rclone

3.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-nox

4.2 创建专用用户 qb(更安全)

adduser --disabled-password --gecos "" qb

4.3 创建下载目录(本地落盘)

mkdir -p /data/qbt/incomplete /data/qbt/complete
chown -R qb:qb /data/qbt

5. 让 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 18230

5.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.log

6.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.env

6.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.sh

7. 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=ok
  • DONE ...

8.2 OneDrive 上查看是否有目录保留

sudo -u qb rclone lsf onedrive:/qbittorrent --max-depth 2 | head

9. 清理:日志、空目录、残留文件(重新测试用)

9.1 清空脚本日志(不删除文件,只清空内容)

sudo -u qb truncate -s 0 /var/log/qbt-rclone-move.log

9.2 清理本地 complete 下的空目录

find /data/qbt/complete -type d -empty -delete

9.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 || true

11.2 停止 qB

systemctl disable --now qbittorrent-nox

最终效果(你应该看到的)

  • /mnt/onedrive:像本地目录一样能浏览 OneDrive(挂载)
  • qB 下载到 /data/qbt/incomplete → 完成后进 /data/qbt/complete
  • 完成触发脚本:自动把“文件/文件夹”搬到 onedrive:/qbittorrent/
  • 搬完自动删除 qB 任务(列表干净)
  • 需要时随时 truncate 清空日志,重新测试
This is just a placeholder img.