From 2d90d9cba5c39a3927de35193c60fa3153ea94d4 Mon Sep 17 00:00:00 2001 From: ShukeBta Date: Fri, 29 May 2026 03:35:14 +0800 Subject: [PATCH] fix: improve qbittorrent host connectivity --- README.md | 53 ++++++++++++++++++++++------- README_EN.md | 47 +++++++++++++++++++------ docker-compose.yml | 8 ++++- internal/service/qbittorrent.go | 13 +++++-- internal/service/qbittorrent_adp.go | 10 ++++-- 5 files changed, 102 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index d4b6b6d..a12bbf3 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ mkdir -p data cache media downloads ```bash cat > .env <<'EOF' # 固定版本;需要升级时改成新的 MediaStationGo-vX.Y.Z 后执行 docker compose pull && docker compose up -d -MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.8 +MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.9 MEDIASTATION_HTTP_PORT=18080 # 程序数据和缓存建议放在 MediaStationGo 部署目录下,便于备份和迁移。 @@ -298,7 +298,7 @@ vim docker-compose.yml # # 镜像版本: # 默认拉取 latest;如需固定版本,创建 .env 并写入: -# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.8 +# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.9 # # 路径映射总览: # /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。 @@ -485,7 +485,7 @@ docker compose up -d ```bash cat > .env <<'EOF' -MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.8 +MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.9 MEDIASTATION_HTTP_PORT=18080 MEDIASTATION_DATA_DIR=./data MEDIASTATION_CACHE_DIR=./cache @@ -587,6 +587,35 @@ MEDIASTATION_DOWNLOAD_DIR=/vol1/1000/qBittorrent/downloads 容器内媒体库建议添加 `/media/电影` 和 `/media/电视剧` 两个根目录;整理后会自动进入 `/media/电影/动画电影`、`/media/电视剧/国产剧` 等分类目录。下载器保存根目录填写 `/downloads`,订阅下载会自动落到 `/downloads/动画电影`、`/downloads/国产剧` 等分类目录。 +### qBittorrent 连接怎么填 + +如果 qBittorrent 运行在同一台 NAS/宿主机上,MediaStationGo 容器里不要优先填 `127.0.0.1`;`127.0.0.1` 代表 MediaStationGo 容器自己。推荐在「下载器管理」中填写: + +```text +http://host.docker.internal:8085 +``` + +仓库默认 `docker-compose.yml` 已配置: + +```yaml +extra_hosts: + - "host.docker.internal:host-gateway" +``` + +如果你填写 `http://192.168.1.125:8085` 超时,但 `http://172.17.0.1:8085` 返回 403,通常表示: + +- Docker 容器到局域网 IP 存在防火墙、路由或 hairpin 限制,建议改用 `host.docker.internal`。 +- qBittorrent WebUI 已经能被容器访问,但登录被拒绝。请检查用户名/密码、IP 封禁、CSRF/Host Header 校验。 +- qBittorrent WebUI 设置里建议确认:监听地址为 `0.0.0.0` 或所有地址;端口为 `8085`;解除/关闭连续失败后的 IP 封禁;必要时把 `host.docker.internal`、`172.17.0.1`、NAS 局域网 IP 加入允许域名/白名单,或关闭 Host Header 校验。 + +可在 NAS 上用下面命令快速验证 qBittorrent 登录: + +```bash +docker exec -it mediastation-go sh -lc 'wget -S -O- --post-data="username=你的用户名&password=你的密码" http://host.docker.internal:8085/api/v2/auth/login' +``` + +返回 `Ok.` 才表示账号和 qBittorrent WebUI 配置都正常。返回 `Forbidden` / `403` 时先去 qBittorrent WebUI 解除封禁和检查安全设置。 + ### 下载器路径怎么填 如果 qBittorrent 也运行在 Docker 中,必须让 qBittorrent 与 MediaStationGo 看到同一份下载目录。 @@ -702,26 +731,26 @@ cd MediaStationGo | 平台 | 包名示例 | | --- | --- | -| Linux x86_64 | `MediaStationGo-v0.0.8-linux-amd64.tar.gz` | -| Linux ARM64 | `MediaStationGo-v0.0.8-linux-arm64.tar.gz` | -| Windows x86_64 | `MediaStationGo-v0.0.8-windows-amd64.zip` | -| macOS Intel | `MediaStationGo-v0.0.8-darwin-amd64.tar.gz` | -| macOS Apple Silicon | `MediaStationGo-v0.0.8-darwin-arm64.tar.gz` | +| Linux x86_64 | `MediaStationGo-v0.0.9-linux-amd64.tar.gz` | +| Linux ARM64 | `MediaStationGo-v0.0.9-linux-arm64.tar.gz` | +| Windows x86_64 | `MediaStationGo-v0.0.9-windows-amd64.zip` | +| macOS Intel | `MediaStationGo-v0.0.9-darwin-amd64.tar.gz` | +| macOS Apple Silicon | `MediaStationGo-v0.0.9-darwin-arm64.tar.gz` | 部署步骤: ```bash # Linux 示例 -tar -xzf MediaStationGo-v0.0.8-linux-amd64.tar.gz -cd MediaStationGo-v0.0.8-linux-amd64 +tar -xzf MediaStationGo-v0.0.9-linux-amd64.tar.gz +cd MediaStationGo-v0.0.9-linux-amd64 MEDIASTATION_APP_PORT=18080 ./mediastation-go ``` Windows: ```powershell -Expand-Archive .\MediaStationGo-v0.0.8-windows-amd64.zip -cd .\MediaStationGo-v0.0.8-windows-amd64 +Expand-Archive .\MediaStationGo-v0.0.9-windows-amd64.zip +cd .\MediaStationGo-v0.0.9-windows-amd64 $env:MEDIASTATION_APP_PORT = "18080" .\mediastation-go.exe ``` diff --git a/README_EN.md b/README_EN.md index 4e969ca..43d93bb 100644 --- a/README_EN.md +++ b/README_EN.md @@ -228,7 +228,7 @@ mkdir -p data cache media downloads ```bash cat > .env <<'EOF' -MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.8 +MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.9 MEDIASTATION_HTTP_PORT=18080 MEDIASTATION_DATA_DIR=./data MEDIASTATION_CACHE_DIR=./cache @@ -322,7 +322,7 @@ For production, pin a specific release tag instead of using `latest`. Recommende ```bash cat > .env <<'EOF' -MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.8 +MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.9 MEDIASTATION_HTTP_PORT=18080 MEDIASTATION_DATA_DIR=./data MEDIASTATION_CACHE_DIR=./cache @@ -414,6 +414,31 @@ MEDIASTATION_DOWNLOAD_DIR=/vol1/1000/qBittorrent/downloads Inside MediaStationGo, add `/media/电影` and `/media/电视剧` as media library roots. Organized files will land in category folders such as `/media/电影/动画电影` and `/media/电视剧/国产剧`. Use `/downloads` as the download root; subscriptions will save to folders such as `/downloads/动画电影` and `/downloads/国产剧`. +### qBittorrent Connection + +If qBittorrent runs on the same NAS/host, do not use `127.0.0.1` from MediaStationGo; inside the container it means the MediaStationGo container itself. In Download Clients, use: + +```text +http://host.docker.internal:8085 +``` + +The default `docker-compose.yml` includes: + +```yaml +extra_hosts: + - "host.docker.internal:host-gateway" +``` + +If `http://192.168.1.125:8085` times out but `http://172.17.0.1:8085` returns 403, the container can reach qBittorrent but the WebUI rejects login. Check username/password, IP bans, CSRF/Host Header validation, and allowed subnets/domains. + +Quick test from the MediaStationGo container: + +```bash +docker exec -it mediastation-go sh -lc 'wget -S -O- --post-data="username=YOUR_USER&password=YOUR_PASS" http://host.docker.internal:8085/api/v2/auth/login' +``` + +`Ok.` means the connection is healthy. `Forbidden` / `403` means qBittorrent WebUI security settings or credentials still need adjustment. + ### Download Client Paths If qBittorrent also runs in Docker, make sure qBittorrent and MediaStationGo share the same host directory and use consistent container paths. @@ -529,25 +554,25 @@ Each release provides multi-platform archives: | Platform | Package example | | --- | --- | -| Linux x86_64 | `MediaStationGo-v0.0.8-linux-amd64.tar.gz` | -| Linux ARM64 | `MediaStationGo-v0.0.8-linux-arm64.tar.gz` | -| Windows x86_64 | `MediaStationGo-v0.0.8-windows-amd64.zip` | -| macOS Intel | `MediaStationGo-v0.0.8-darwin-amd64.tar.gz` | -| macOS Apple Silicon | `MediaStationGo-v0.0.8-darwin-arm64.tar.gz` | +| Linux x86_64 | `MediaStationGo-v0.0.9-linux-amd64.tar.gz` | +| Linux ARM64 | `MediaStationGo-v0.0.9-linux-arm64.tar.gz` | +| Windows x86_64 | `MediaStationGo-v0.0.9-windows-amd64.zip` | +| macOS Intel | `MediaStationGo-v0.0.9-darwin-amd64.tar.gz` | +| macOS Apple Silicon | `MediaStationGo-v0.0.9-darwin-arm64.tar.gz` | Linux example: ```bash -tar -xzf MediaStationGo-v0.0.8-linux-amd64.tar.gz -cd MediaStationGo-v0.0.8-linux-amd64 +tar -xzf MediaStationGo-v0.0.9-linux-amd64.tar.gz +cd MediaStationGo-v0.0.9-linux-amd64 MEDIASTATION_APP_PORT=18080 ./mediastation-go ``` Windows example: ```powershell -Expand-Archive .\MediaStationGo-v0.0.8-windows-amd64.zip -cd .\MediaStationGo-v0.0.8-windows-amd64 +Expand-Archive .\MediaStationGo-v0.0.9-windows-amd64.zip +cd .\MediaStationGo-v0.0.9-windows-amd64 $env:MEDIASTATION_APP_PORT = "18080" .\mediastation-go.exe ``` diff --git a/docker-compose.yml b/docker-compose.yml index de584ea..ff27a96 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ # # 镜像版本: # 默认拉取 latest;如需固定版本,创建 .env 并写入: -# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.8 +# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.9 # # 路径映射总览: # /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。 @@ -63,6 +63,12 @@ services: # 宿主机端口:容器端口。默认访问 http://<服务器IP>:18080 - "${MEDIASTATION_HTTP_PORT:-18080}:8080" + extra_hosts: + # Linux / NAS Docker 中访问宿主机服务的固定别名。 + # qBittorrent 如果运行在 NAS 宿主机上,下载器地址建议填: + # http://host.docker.internal:8085 + - "host.docker.internal:host-gateway" + volumes: # 程序持久化数据:数据库、JWT secret、运行时配置。 - ${MEDIASTATION_DATA_DIR:-./data}:/data diff --git a/internal/service/qbittorrent.go b/internal/service/qbittorrent.go index 0903e49..80638cc 100644 --- a/internal/service/qbittorrent.go +++ b/internal/service/qbittorrent.go @@ -107,7 +107,10 @@ func (q *QBitClient) Login(ctx context.Context) error { return err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Referer", q.cfg.BaseURL) + baseURL := strings.TrimRight(q.cfg.BaseURL, "/") + req.Header.Set("Referer", baseURL) + req.Header.Set("Origin", baseURL) + req.Header.Set("User-Agent", "MediaStationGo/0.1") resp, err := q.client.Do(req) if err != nil { @@ -115,8 +118,12 @@ func (q *QBitClient) Login(ctx context.Context) error { } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) - if resp.StatusCode >= 400 || strings.TrimSpace(string(body)) != "Ok." { - return fmt.Errorf("qbittorrent login failed: %s", strings.TrimSpace(string(body))) + text := strings.TrimSpace(string(body)) + if resp.StatusCode == http.StatusForbidden { + return errors.New("qbittorrent login forbidden: check username/password, WebUI IP ban, CSRF/Host header validation, and allowed subnets") + } + if resp.StatusCode >= 400 || text != "Ok." { + return fmt.Errorf("qbittorrent login failed: status=%d body=%s", resp.StatusCode, text) } return nil } diff --git a/internal/service/qbittorrent_adp.go b/internal/service/qbittorrent_adp.go index b7331b0..46822ff 100644 --- a/internal/service/qbittorrent_adp.go +++ b/internal/service/qbittorrent_adp.go @@ -284,6 +284,8 @@ func (a *QBitAdapter) loginLocked(ctx context.Context) error { } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Referer", baseURL) + req.Header.Set("Origin", baseURL) + req.Header.Set("User-Agent", "MediaStationGo/0.1") resp, err := a.client.Do(req) if err != nil { @@ -291,8 +293,12 @@ func (a *QBitAdapter) loginLocked(ctx context.Context) error { } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) - if resp.StatusCode >= 400 || strings.TrimSpace(string(body)) != "Ok." { - return fmt.Errorf("qbittorrent login failed: %s", strings.TrimSpace(string(body))) + text := strings.TrimSpace(string(body)) + if resp.StatusCode == http.StatusForbidden { + return fmt.Errorf("qbittorrent login forbidden: check username/password, WebUI IP ban, CSRF/Host header validation, and allowed subnets") + } + if resp.StatusCode >= 400 || text != "Ok." { + return fmt.Errorf("qbittorrent login failed: status=%d body=%s", resp.StatusCode, text) } a.LoggedIn = true return nil