Openlist+qBittorrent 部署

确认 nginx 网络存在

docker network ls | grep nginx-net

如果不存在,需要先创建:

docker network create nginx-net

1、创建宿主机普通服务用户
这里以创建 openlist 用户为例:
查看openlist用户是否存在,如果不存在则新建openlist用户

id openlist || useradd -m -s /usr/sbin/nologin openlist

查看 UID/GID:

id openlist

例如输出:

uid=1000(openlist) gid=1000(openlist) groups=1000(openlist)
记录 UID/GID:
APP_UID=$(id -u openlist)
APP_GID=$(id -g openlist)

echo "APP_UID=${APP_UID}"
echo "APP_GID=${APP_GID}"

后续 OpenList 和 qBittorrent 都使用这个 UID/GID。

2、创建目录并设置权限

mkdir -p /opt/openlist/data
mkdir -p /opt/openlist/cert
mkdir -p /opt/qbittorrent/config

APP_UID=$(id -u openlist)
APP_GID=$(id -g openlist)

chown -R ${APP_UID}:${APP_GID} /opt/openlist
chown -R ${APP_UID}:${APP_GID} /opt/qbittorrent
chown -R ${APP_UID}:${APP_GID} "/html/网盘"

chmod -R 755 /opt/openlist
chmod -R 755 /opt/qbittorrent
chmod -R 755 "/html/网盘"

3、准备 OpenList 可读取的证书目录
因为 OpenList 不再用 root 运行,如果直接挂载 /opt/cert,可能会遇到证书私钥权限不足的问题。
因此建议把证书复制到:

/opt/openlist/cert

4、创建 OpenList 证书自动同步脚本
由于 OpenList 使用普通用户运行,不建议直接读取 /opt/cert 下的私钥。用脚本把证书自动同步到**/opt/openlist/cert/**
创建脚本:

cat > /opt/openlist/cert/sync-openlist-cert.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

OPENLIST_DOMAIN="网址.com"
APP_USER="openlist"

APP_UID="$(id -u "${APP_USER}")"
APP_GID="$(id -g "${APP_USER}")"

CERT_SRC="/opt/cert/${OPENLIST_DOMAIN}.fullchain.crt"
KEY_SRC="/opt/cert/${OPENLIST_DOMAIN}.key"

CERT_DST_DIR="/opt/openlist/cert"
CERT_DST="${CERT_DST_DIR}/${OPENLIST_DOMAIN}.fullchain.crt"
KEY_DST="${CERT_DST_DIR}/${OPENLIST_DOMAIN}.key"

STATE_FILE="${CERT_DST_DIR}/.cert.sha256"

if [ ! -f "${CERT_SRC}" ]; then
  echo "证书文件不存在:${CERT_SRC}"
  exit 1
fi

if [ ! -f "${KEY_SRC}" ]; then
  echo "私钥文件不存在:${KEY_SRC}"
  exit 1
fi

NEW_HASH="$(sha256sum "${CERT_SRC}" "${KEY_SRC}" | sha256sum | awk '{print $1}')"
OLD_HASH="$(cat "${STATE_FILE}" 2>/dev/null || true)"

if [ "${NEW_HASH}" = "${OLD_HASH}" ]; then
  echo "证书未变化,无需处理。"
  exit 0
fi

install -d -o "${APP_UID}" -g "${APP_GID}" -m 750 "${CERT_DST_DIR}"

install -o "${APP_UID}" -g "${APP_GID}" -m 644 \
  "${CERT_SRC}" \
  "${CERT_DST}"

install -o "${APP_UID}" -g "${APP_GID}" -m 600 \
  "${KEY_SRC}" \
  "${KEY_DST}"

echo "${NEW_HASH}" > "${STATE_FILE}"
chown "${APP_UID}:${APP_GID}" "${STATE_FILE}"
chmod 600 "${STATE_FILE}"

if docker ps -a --format '{{.Names}}' | grep -qx nginx; then
  docker exec nginx nginx -t
  docker restart nginx
else
  echo "nginx 容器不存在,跳过 nginx reload。"
fi

if docker ps -a --format '{{.Names}}' | grep -qx openlist; then
  docker restart openlist
else
  echo "openlist 容器不存在,跳过 OpenList 重启。"
fi

echo "OpenList 证书已同步,nginx 已 reload,OpenList 已重启。"
EOF

设置权限

chmod +x /opt/openlist/cert/sync-openlist-cert.sh

手动执行一次初始化同步,第一次需要手动执行一次:

/opt/openlist/cert/sync-openlist-cert.sh

查看同步结果:

ls -l /opt/openlist/cert

正常应该看到类似:

-rw------- 1 openlist openlist 网址.com.key

5、添加 cron 自动检测证书变化
编辑 crontab:

crontab -e

加入:

17 * * * * /opt/openlist/cert/sync-openlist-cert.sh >> /opt/openlist/cert/sync-openlist-cert.log 2>&1

说明:

每小时检查一次证书是否变化。

如果证书没有变化,不会 reload nginx,也不会 restart OpenList。

如果 acme 自动续期了证书,脚本会自动同步新证书并重载相关服务。
查看日志:

tail -f /opt/openlist/cert/sync-openlist-cert.log

6、安装 OpenList
创建环境变量文件:

cd /opt/openlist

APP_UID=$(id -u openlist)
APP_GID=$(id -g openlist)

cat > .env <<EOF
APP_UID=${APP_UID}
APP_GID=${APP_GID}
EOF

创建 /opt/openlist/docker-compose.yml

nano /opt/openlist/docker-compose.yml
services:
  openlist:
    image: openlistteam/openlist:v4.2.1
    container_name: openlist
    restart: always
    user: "${APP_UID}:${APP_GID}"
    environment:
      - TZ=Asia/Shanghai
      - UMASK=022
    volumes:
      - /opt/openlist/data:/opt/openlist/data
      - /opt/openlist/cert:/cert:ro
      - "/html/网盘:/html/网盘"
    ports:
      - "55246:55246"
    networks:
      - nginx-net

networks:
  nginx-net:
    external: true

启动:

cd /opt/openlist
docker compose up -d

查看初始密码:

docker logs openlist | grep -i password

如果没有看到密码,可以手动设置:

docker exec -it openlist ./openlist admin set '你的强密码'

7、配置 OpenList 的 HTTPS 直连端口 55246
编辑/opt/openlist/data/config.json

nano /opt/openlist/data/config.json

找到或修改这些字段:

{
  "site_url": "https://网址.com",
  "scheme": {
    "address": "0.0.0.0",
    "http_port": 5244,
    "https_port": 55246,
    "force_https": false,
    "cert_file": "/cert/网址.com.fullchain.crt",
    "key_file": "/cert/网址.com.key"
  }
}

测试:

docker restart openlist
curl -I https://网址.com:55246

8、安装 qBittorrent PT 稳定版
创建环境变量文件:

cd /opt/qbittorrent

APP_UID=$(id -u openlist)
APP_GID=$(id -g openlist)

cat > .env <<EOF
APP_UID=${APP_UID}
APP_GID=${APP_GID}
EOF

创建 /opt/qbittorrent/docker-compose.yml

nano /opt/qbittorrent/docker-compose.yml
services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:4.6.7-libtorrentv1
    container_name: qbittorrent
    restart: always
    environment:
      - PUID=${APP_UID}
      - PGID=${APP_GID}
      - TZ=Asia/Shanghai
      - WEBUI_PORT=8080
      - TORRENTING_PORT=45000
      - UMASK=022
    volumes:
      - /opt/qbittorrent/config:/config
      - "/html/网盘/qBittorrent:/downloads"
    ports:
      - "45000:45000/tcp"
      - "45000:45000/udp"
    networks:
      - nginx-net
    stop_grace_period: 2m
    mem_limit: 16g
    mem_reservation: 2g
    sysctls:
      - net.core.somaxconn=1024
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

networks:
  nginx-net:
    external: true

启动:

cd /opt/qbittorrent
docker compose up -d

查看 WebUI 临时密码:

docker logs qbittorrent | grep -i password

首次登录:

用户名:admin

密码:docker logs qbittorrent 中显示的临时密码


9、配置 Nginx 反代 OpenList
创建 /opt/nginx/conf.d/openlist.conf

nano /opt/nginx/conf.d/openlist.conf
#server {
#    listen 80;
#    server_name 网址.com;
#
#    return 301 https://$host$request_uri;
#}

server {
    listen 443 ssl;
    http2 on;
    server_name 网址.com;

    ssl_certificate     /etc/nginx/cert/网址.com.fullchain.crt;
    ssl_certificate_key /etc/nginx/cert/网址.com.key;

    client_max_body_size 0;

    location / {
        proxy_pass http://openlist:5244;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

说明:

OpenList 的 5244 端口不映射到宿主机。

Nginx 和 OpenList 在同一个 Docker 网络 nginx-net 内。

Nginx 可以直接通过 http://openlist:5244 访问 OpenList。


10、配置 Nginx 反代 qBittorrent
创建 /opt/nginx/conf.d/qbittorrent.conf

nano /opt/nginx/conf.d/qbittorrent.conf
server {
    listen 80;
    server_name qb.网址.com;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name qb.网址.com;

    ssl_certificate     /etc/nginx/cert/qb.网址.com.fullchain.crt;
    ssl_certificate_key /etc/nginx/cert/qb.网址.com.key;

    client_max_body_size 0;

    location / {
        proxy_pass http://qbittorrent:8080;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;

        proxy_http_version 1.1;
    }
}

11、测试并重载 Nginx

docker exec nginx nginx -t
docker exec nginx nginx -s reload

访问测试:

https://网址.com
https://网址.com:55246
https://qb.网址.com

12、显示磁盘容量
在openlist根目录中新建本地磁盘空间.txt文件 在'设置'-'全局'-'自定义内容'内输入以下内容,将 xhttp.open后面修改为TXT的下载地址

<!-- 自定义样式 -->
<style>
  /* 隐藏原底部版权和管理 */
  .footer span,
  .footer a:nth-of-type(1),
  .footer a:nth-of-type(2) {
    display: none !important;
  }

  /* 移除搜索栏中的键盘图标:这个选择器可能会随版本变化失效 */
  .hope-stack.hope-c-dhzjXW.hope-c-PJLV.hope-c-PJLV-ihYBJPK-css {
    display: none !important;
  }

  /* 自定义底部区域 */
  #custom-footer {
    display: none;
    text-align: center;
    padding: 8px 0;
    font-size: 14px;
  }

  #disk-space-content {
    text-align: center;
    white-space: pre-wrap;
    margin-bottom: 4px;
  }

  #custom-footer p {
    margin: 0;
  }

  #custom-footer a {
    text-decoration: none;
  }

  #custom-footer span {
    margin: 0 6px;
  }
</style>

<script>
(function () {
  const TXT_URL = "https://www.网址.com/xx.txt";

  function createCustomFooter() {
    const footer = document.querySelector(".footer");

    if (!footer || document.querySelector("#custom-footer")) {
      return;
    }

    const customFooter = document.createElement("div");
    customFooter.id = "custom-footer";

    customFooter.innerHTML = `
      <div id="disk-space-content">正在读取磁盘容量...</div>
      <p>
        <a href="https://openlist.nn.ci/zh/" target="_blank" rel="noopener noreferrer">© Powered by openlist</a>
        <span>|</span>
        <a href="/@manage">管理</a>
      </p>
    `;

    footer.insertAdjacentElement("afterend", customFooter);
    customFooter.style.display = "block";

    loadDiskText();
  }

  async function loadDiskText() {
    const content = document.querySelector("#disk-space-content");

    if (!content) {
      return;
    }

    try {
      const separator = TXT_URL.includes("?") ? "&" : "?";
      const url = TXT_URL + separator + "_t=" + Date.now();

      const response = await fetch(url, {
        cache: "no-store"
      });

      if (!response.ok) {
        throw new Error("HTTP " + response.status);
      }

      const text = await response.text();

      // 用 textContent 更安全,避免 TXT 内容被当成 HTML 执行
      content.textContent = text.trim();
    } catch (error) {
      content.textContent = "磁盘容量信息暂时无法读取";
      console.warn("读取磁盘容量失败:", error);
    }
  }

  function init() {
    createCustomFooter();

    const observer = new MutationObserver(function () {
      createCustomFooter();
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true
    });

    // 防止一直监听,15 秒后停止
    setTimeout(function () {
      observer.disconnect();
    }, 15000);
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();
</script>

创建sh文件

nano /opt/openlist/显示磁盘剩余空间.sh

输入以下内容

#!/bin/bash
all=$(df -Bg | grep -w /dev/vda1 | awk '{ print $2 }')     #注意将/dev/vda1修改为当前磁盘的路径 -w为完全匹配,避免返回多个结果
free=$(df -Bg | grep -w /dev/vda1 | awk '{ print $4 }')    #注意将/dev/vda1修改为当前磁盘的路径 -w为完全匹配,避免返回多个结果
if [ ${free}x != $(awk '{print $2}' /html/网盘/other/本地磁盘空间.txt)x ]
then
    sed -i "1c 本地磁盘可用空间: ${free} / ${all}" /html/网盘/other/本地磁盘空间.txt            #此处是前面建立的存储磁盘容量信息的TXT文件的路径
fi

给与权限

chmod +x /opt/openlist/显示磁盘剩余空间.sh

执行

/opt/openlist/显示磁盘剩余空间.sh

设置定时器

crontab -e

每分钟执行一次

* * * * * /opt/openlist/显示磁盘剩余空间.sh >/dev/null 2>&1

可以在设置-全局- 隐藏文件中加入/\/本地磁盘空间.txt/i来隐藏TXT文件
可以在设置-元信息- 隐藏中加入^文件夹名称$来隐藏文件夹,在用户中关闭 可以看到隐藏选项。如果是将^文件夹名称$直接复制过去的话,需要回车,否则可能不生效。如果文件加名称是中文,比如 ^中文目录$,不能放到第一个。
13、qBittorrent PT 推荐设置
登录 WebUI 后,立即修改用户名和密码:

工具 → 选项 → Web UI

用户名:改成你自己的

密码:改成强密码
下载目录:
工具 → 选项 → 下载

默认保存路径:/downloads
监听端口:
工具 → 选项 → 连接

监听端口:45000
PT 推荐关闭:
工具 → 选项 → BitTorrent

关闭 DHT

关闭 PeX

关闭本地用户发现 / Local Peer Discovery

不要启用匿名模式

不要设置全局分享率达到后自动停止


14、防火墙和安全组设置
放行 HTTP/HTTPS:

ufw allow 80/tcp
ufw allow 443/tcp

放行 qBittorrent BT 端口:

ufw allow 45000/tcp
ufw allow 45000/udp

55246 是给其他 VPS 中转 OpenList 用的,不建议全网开放。
只允许中转机 IP 访问:

ufw allow from 中转机IP to any port 55246 proto tcp
ufw deny 55246/tcp
ufw reload

如果不用 ufw,请在云厂商安全组中放行:

80/tcp
443/tcp
45000/tcp
45000/udp
55246/tcp 仅允许中转机 IP

15、IYUU 安装
创建 IYUUPlus 目录

mkdir -p /opt/iyuu/iyuu
mkdir -p /opt/iyuu/data

chmod -R 755 /opt/iyuu

如果你前面使用的是 openlist 用户统一管理 OpenList 和 qBittorrent 权限,也给 IYUU 目录设置同样权限:

APP_UID=$(id -u openlist)
APP_GID=$(id -g openlist)

chown -R ${APP_UID}:${APP_GID} /opt/iyuu

创建 IYUUPlus docker-compose.yml

nano /opt/iyuu/docker-compose.yml

写入:

services:
  iyuuplus:
    image: iyuucn/iyuuplus-dev:latest
    container_name: iyuuplus
    restart: always
    stdin_open: true
    tty: true
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - /opt/iyuu/iyuu:/iyuu
      - /opt/iyuu/data:/data
      - /opt/qbittorrent/config/qBittorrent/BT_backup:/BT_backup:ro
    networks:
      - nginx-net

networks:
  nginx-net:
    external: true

这里没有把 IYUUPlus 的端口暴露到公网,因为后面会通过 Nginx 容器内网反代访问。
启动 IYUUPlus

cd /opt/iyuu
docker compose up -d

查看日志:

docker logs -f iyuuplus
#等日志中出现 “Press Ctrl+C to stop. Start success.” 才算安装完成 

确认容器是否运行:

docker ps | grep iyuuplus

确认 IYUUPlus 容器内监听端口

docker exec -it iyuuplus sh -c "ss -lntp 2>/dev/null || netstat -lntp 2>/dev/null"

重点看类似:

tcp        0      0 0.0.0.0:8780            0.0.0.0:*               LISTEN      204/nginx: master
测试 nginx 容器能否访问 IYUUPlus
执行:
docker exec nginx sh -c "curl -I http://iyuuplus:8780"

能看到 HTTP 返回头,说明 nginx 容器可以通过 Docker 网络访问 IYUUPlus。

16、配置 Nginx 反代 IYUUPlus
创建:

nano /opt/nginx/conf.d/iyuu.conf

写入:

server {
    listen 80;
    server_name iyuu.网址.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    http2 on;
    server_name iyuu.网址.com;

    ssl_certificate     /etc/nginx/cert/iyuu.网址.com.fullchain.crt;
    ssl_certificate_key /etc/nginx/cert/iyuu.网址.com.key;

    client_max_body_size 0;

    location / {
        proxy_pass http://iyuuplus:8780;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;

        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

测试并重载 Nginx:

docker exec nginx nginx -t

重载 Nginx:

docker exec nginx nginx -s reload

17、访问 IYUUPlus
浏览器访问:

https://iyuu.网址.com

IYUUPlus 中添加 qBittorrent 下载器
下载器类型:

qBittorrent
地址填写:
http://qbittorrent:8080
用户名:
你的 qBittorrent WebUI 用户名
密码:
你的 qBittorrent WebUI 密码
下载目录映射:
qBittorrent 容器内路径:/downloads

IYUUPlus 容器内路径:/downloads

宿主机实际路径:/html/网盘/qBittorrent
因为 qBittorrent 和 IYUUPlus 都在同一个 Docker 网络 nginx-net,所以 IYUUPlus 连接 qBittorrent 不需要走公网域名:
不要填:https://qb.mydisk.cc

推荐填:http://qbittorrent:8080

无标签
评论区
头像