清理qBittorrent接入残留:删除死schema分组与前端下载类型,下载器保存目录键改为downloader.savepath并兼容旧键

This commit is contained in:
truewhile
2026-09-05 18:08:52 +08:00
parent 030ed5f325
commit 28551c7883
16 changed files with 31 additions and 88 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ services:
bind:
create_host_path: false
# 下载目录。需要和 qBittorrent 保存路径保持一致。
# 下载目录。需要和下载器保存路径保持一致。
# NAS 示例:source: /vol1/1000/Downloads
# Windows Docker Desktop 示例:source: D:/Downloads
- type: bind
+1 -1
View File
@@ -69,7 +69,7 @@ services:
MEBOX_LOGGING_MAX_AGE_DAYS: "30"
extra_hosts:
# 容器访问宿主机服务用,例如 qBittorrent: http://host.docker.internal:8085
# 容器访问宿主机服务(如下载器等)用: http://host.docker.internal:8085
- "host.docker.internal:host-gateway"
healthcheck:
+1 -1
View File
@@ -50,7 +50,7 @@ services:
bind:
create_host_path: false
# 下载目录。需要和 qBittorrent 保存路径保持一致。
# 下载目录。需要和下载器保存路径保持一致。
# NAS 示例:source: /vol1/1000/Downloads
# Windows Docker Desktop 示例:source: D:/Downloads
- type: bind
+1 -1
View File
@@ -29,7 +29,7 @@ services:
ports:
- "18080:8080"
# 让容器可以访问宿主机上的 qBittorrent。
# 让容器可以访问宿主机上的服务(如下载器)。
# qB 地址通常可填:http://host.docker.internal:8085
extra_hosts:
- "host.docker.internal:host-gateway"
-10
View File
@@ -116,16 +116,6 @@ func schemaHandler(_ *service.Container) gin.HandlerFunc {
{"key": "adult.pin", "type": "text"},
},
},
{
"key": "qbittorrent",
"label": "qBittorrent",
"items": []gin.H{
{"key": "qbittorrent.url", "type": "text"},
{"key": "qbittorrent.username", "type": "text"},
{"key": "qbittorrent.password", "type": "text"},
{"key": "qbittorrent.savepath", "type": "text"},
},
},
{
"key": "system-update",
"label": "系统更新",
+1 -1
View File
@@ -1,6 +1,6 @@
// Package service — AES-GCM crypto helper for at-rest secrets.
//
// Sensitive fields (third-party API keys, qBittorrent passwords, …) are
// Sensitive fields (third-party API keys, service passwords, …) are
// stored in SQLite. We encrypt them with AES-256-GCM keyed off the JWT
// secret so a stolen DB file alone is not enough to recover the
// plaintext credentials.
+5 -1
View File
@@ -77,7 +77,11 @@ func (s *FileManagerService) allowedRoots() (map[string]string, error) {
}
addSetting("organize-source", "organize.source_dir")
addSetting("organize-target", "organize.target_dir")
addSetting("qb-savepath", "qbittorrent.savepath")
addSetting("downloader-savepath", "downloader.savepath")
// 兼容历史键名 qbittorrent.savepath:旧版本把下载器保存目录存在该键下
if value, err := s.repo.Setting.Get(context.Background(), "downloader.savepath"); err != nil || strings.TrimSpace(value) == "" {
addSetting("downloader-savepath", "qbittorrent.savepath")
}
}
if s.repo != nil && s.repo.Library != nil {
libs, err := s.repo.Library.List(context.Background())
+4 -3
View File
@@ -196,9 +196,10 @@ func TestFileManagerIncludesConfiguredOrganizeRoots(t *testing.T) {
got[root.Label] = root.Path
}
for label, want := range map[string]string{
"organize-source": filepath.Clean(sourceDir),
"organize-target": filepath.Clean(targetDir),
"qb-savepath": filepath.Clean(qbDir),
"organize-source": filepath.Clean(sourceDir),
"organize-target": filepath.Clean(targetDir),
// 旧键 qbittorrent.savepath 写入应经兼容回退落在 downloader-savepath 下
"downloader-savepath": filepath.Clean(qbDir),
} {
if got[label] != want {
t.Fatalf("root %s = %q, want %q; roots=%#v", label, got[label], want, listing.Roots)
+1 -1
View File
@@ -136,7 +136,7 @@ func TestResolveAccessibleMappedPathMapsEmbeddedHostDownloadMarker(t *testing.T)
}
t.Setenv("MEBOX_DOWNLOAD_CONTAINER_DIR", containerDownloads)
got, _, err := resolveAccessibleMappedPath("/vol1/1000/Docker/qbittorrent/downloads/国产剧")
got, _, err := resolveAccessibleMappedPath("/vol1/1000/nas/downloads/国产剧")
if err != nil {
t.Fatalf("resolveAccessibleMappedPath() error = %v", err)
}
@@ -40,7 +40,7 @@ func (o *OrganizerService) OrganizeSourceCandidates(ctx context.Context) []Organ
out = append(out, OrganizeSourceCandidate{Label: label, Path: clean, Kind: kind})
}
add("默认整理源", o.settingValue(ctx, "organize.source_dir"), "source")
add("下载器保存目录", o.settingValue(ctx, "qbittorrent.savepath"), "download")
add("下载器保存目录", o.downloaderSavepath(ctx), "download")
add("下载目录", envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads"), "download")
add("媒体目录", envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media"), "media")
return out
@@ -56,8 +56,17 @@ func (o *OrganizerService) settingValue(ctx context.Context, key string) string
return ""
}
// downloaderSavepath 返回下载器保存目录,优先新键 downloader.savepath,
// 兼容历史键名 qbittorrent.savepath(旧版本部署已写入的配置不丢失)。
func (o *OrganizerService) downloaderSavepath(ctx context.Context) string {
if v := o.settingValue(ctx, "downloader.savepath"); v != "" {
return v
}
return o.settingValue(ctx, "qbittorrent.savepath")
}
// defaultSourceRoot resolves the source root for a directory organize:
// explicit override → organize.source_dir setting → qB default save path →
// explicit override → organize.source_dir setting → downloader save path →
// download container dir.
func (o *OrganizerService) defaultSourceRoot(ctx context.Context, override string) string {
if r := strings.TrimSpace(override); r != "" {
@@ -66,7 +75,7 @@ func (o *OrganizerService) defaultSourceRoot(ctx context.Context, override strin
if v := o.settingValue(ctx, "organize.source_dir"); v != "" {
return v
}
if v := o.settingValue(ctx, "qbittorrent.savepath"); v != "" {
if v := o.downloaderSavepath(ctx); v != "" {
return v
}
return envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads")
+1 -1
View File
@@ -54,7 +54,7 @@ func (o *OrganizerService) resolveTransferMode(ctx context.Context, override Tra
}
}
if mode == TransferMove && o.keepSeedingEnabled(ctx) {
// 移动会删除源文件导致 qBittorrent 停止做种;保种开启时改用硬链接
// 移动会删除源文件导致下载器停止做种;保种开启时改用硬链接
// 既规范命名又保留源文件继续做种上传。硬链接失败时会报错,避免静默
// 退化复制后占用双份磁盘空间。
return TransferHardlink
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
// translateClientPath 将下载客户端报告的路径转换为容器内可访问的路径。
// 常见场景:qBittorrent在另一个容器,报告的路径是其容器内路径,需要映射到当前容器。
// 常见场景:下载器在另一个容器,报告的路径是其容器内路径,需要映射到当前容器。
func translateClientPath(clientPath string, mappings map[string]string) string {
if clientPath == "" {
return ""
+1 -1
View File
@@ -27,7 +27,7 @@ func NewExternalHTTPClient(timeout time.Duration) *http.Client {
}
// NewInternalHTTPClient builds an HTTP client for LAN / Docker-internal
// services such as qBittorrent, Transmission and Aria2. These endpoints are
// services such as downloaders and other local tools. These endpoints are
// usually 127.0.0.1, host.docker.internal, 172.17.0.1 or a NAS LAN IP; sending
// them through HTTP_PROXY/SOCKS proxies makes local WebUI logins hang or fail.
func NewInternalHTTPClient(timeout time.Duration) *http.Client {
+1 -1
View File
@@ -7,7 +7,7 @@
// hardlink 硬链接(同盘零额外占用,保留源文件;做种不受影响)
// symlink 软链接(保留源文件,指向源)
//
// 除 move 外,其余方式都保留源文件,因此 qBittorrent 等下载器仍能在原
// 除 move 外,其余方式都保留源文件,因此下载器仍能在原
// 路径找到数据继续做种上传。
package service
-60
View File
@@ -1,60 +0,0 @@
export interface DownloadTask {
id: string
source: string
download_client_id?: string
external_id?: string
title: string
poster_url?: string
backdrop_url?: string
overview?: string
save_path: string
status: string
progress: number
state?: string
dlspeed?: number
upspeed?: number
num_seeds?: number
num_leechs?: number
size?: number
downloaded?: number
created_at: string
updated_at: string
}
export interface QBitTorrent {
hash: string
client_id: string
source: 'qbittorrent' | 'transmission' | 'aria2'
name: string
title: string
poster_url?: string
backdrop_url?: string
overview?: string
state: string
progress: number
dlspeed: number
upspeed: number
num_seeds: number
num_leechs: number
size: number
downloaded: number
save_path: string
}
export interface DownloadClient {
id: string
name: string
type: 'qbittorrent' | 'transmission' | 'aria2'
host: string
username: string
is_default: boolean
enabled: boolean
created_at: string
updated_at: string
}
export interface DownloadClientTypeInfo {
type: string
name: string
description: string
}
-1
View File
@@ -2,7 +2,6 @@
export * from './api'
export * from './auth'
export * from './downloads'
export * from './events'
export * from './history'
export * from './library'