mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-12 00:06:37 +08:00
清理qBittorrent接入残留:删除死schema分组与前端下载类型,下载器保存目录键改为downloader.savepath并兼容旧键
This commit is contained in:
@@ -55,7 +55,7 @@ services:
|
|||||||
bind:
|
bind:
|
||||||
create_host_path: false
|
create_host_path: false
|
||||||
|
|
||||||
# 下载目录。需要和 qBittorrent 保存路径保持一致。
|
# 下载目录。需要和下载器保存路径保持一致。
|
||||||
# NAS 示例:source: /vol1/1000/Downloads
|
# NAS 示例:source: /vol1/1000/Downloads
|
||||||
# Windows Docker Desktop 示例:source: D:/Downloads
|
# Windows Docker Desktop 示例:source: D:/Downloads
|
||||||
- type: bind
|
- type: bind
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ services:
|
|||||||
MEBOX_LOGGING_MAX_AGE_DAYS: "30"
|
MEBOX_LOGGING_MAX_AGE_DAYS: "30"
|
||||||
|
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
# 容器访问宿主机服务用,例如 qBittorrent: http://host.docker.internal:8085
|
# 容器访问宿主机服务(如下载器等)用: http://host.docker.internal:8085
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ services:
|
|||||||
bind:
|
bind:
|
||||||
create_host_path: false
|
create_host_path: false
|
||||||
|
|
||||||
# 下载目录。需要和 qBittorrent 保存路径保持一致。
|
# 下载目录。需要和下载器保存路径保持一致。
|
||||||
# NAS 示例:source: /vol1/1000/Downloads
|
# NAS 示例:source: /vol1/1000/Downloads
|
||||||
# Windows Docker Desktop 示例:source: D:/Downloads
|
# Windows Docker Desktop 示例:source: D:/Downloads
|
||||||
- type: bind
|
- type: bind
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "18080:8080"
|
- "18080:8080"
|
||||||
|
|
||||||
# 让容器可以访问宿主机上的 qBittorrent。
|
# 让容器可以访问宿主机上的服务(如下载器)。
|
||||||
# qB 地址通常可填:http://host.docker.internal:8085
|
# qB 地址通常可填:http://host.docker.internal:8085
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
|
|||||||
@@ -116,16 +116,6 @@ func schemaHandler(_ *service.Container) gin.HandlerFunc {
|
|||||||
{"key": "adult.pin", "type": "text"},
|
{"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",
|
"key": "system-update",
|
||||||
"label": "系统更新",
|
"label": "系统更新",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package service — AES-GCM crypto helper for at-rest secrets.
|
// 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
|
// 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
|
// secret so a stolen DB file alone is not enough to recover the
|
||||||
// plaintext credentials.
|
// plaintext credentials.
|
||||||
|
|||||||
@@ -77,7 +77,11 @@ func (s *FileManagerService) allowedRoots() (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
addSetting("organize-source", "organize.source_dir")
|
addSetting("organize-source", "organize.source_dir")
|
||||||
addSetting("organize-target", "organize.target_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 {
|
if s.repo != nil && s.repo.Library != nil {
|
||||||
libs, err := s.repo.Library.List(context.Background())
|
libs, err := s.repo.Library.List(context.Background())
|
||||||
|
|||||||
@@ -196,9 +196,10 @@ func TestFileManagerIncludesConfiguredOrganizeRoots(t *testing.T) {
|
|||||||
got[root.Label] = root.Path
|
got[root.Label] = root.Path
|
||||||
}
|
}
|
||||||
for label, want := range map[string]string{
|
for label, want := range map[string]string{
|
||||||
"organize-source": filepath.Clean(sourceDir),
|
"organize-source": filepath.Clean(sourceDir),
|
||||||
"organize-target": filepath.Clean(targetDir),
|
"organize-target": filepath.Clean(targetDir),
|
||||||
"qb-savepath": filepath.Clean(qbDir),
|
// 旧键 qbittorrent.savepath 写入应经兼容回退落在 downloader-savepath 下
|
||||||
|
"downloader-savepath": filepath.Clean(qbDir),
|
||||||
} {
|
} {
|
||||||
if got[label] != want {
|
if got[label] != want {
|
||||||
t.Fatalf("root %s = %q, want %q; roots=%#v", label, got[label], want, listing.Roots)
|
t.Fatalf("root %s = %q, want %q; roots=%#v", label, got[label], want, listing.Roots)
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func TestResolveAccessibleMappedPathMapsEmbeddedHostDownloadMarker(t *testing.T)
|
|||||||
}
|
}
|
||||||
t.Setenv("MEBOX_DOWNLOAD_CONTAINER_DIR", containerDownloads)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("resolveAccessibleMappedPath() error = %v", err)
|
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})
|
out = append(out, OrganizeSourceCandidate{Label: label, Path: clean, Kind: kind})
|
||||||
}
|
}
|
||||||
add("默认整理源", o.settingValue(ctx, "organize.source_dir"), "source")
|
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_DOWNLOAD_CONTAINER_DIR", "/downloads"), "download")
|
||||||
add("媒体目录", envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media"), "media")
|
add("媒体目录", envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media"), "media")
|
||||||
return out
|
return out
|
||||||
@@ -56,8 +56,17 @@ func (o *OrganizerService) settingValue(ctx context.Context, key string) string
|
|||||||
return ""
|
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:
|
// 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.
|
// download container dir.
|
||||||
func (o *OrganizerService) defaultSourceRoot(ctx context.Context, override string) string {
|
func (o *OrganizerService) defaultSourceRoot(ctx context.Context, override string) string {
|
||||||
if r := strings.TrimSpace(override); r != "" {
|
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 != "" {
|
if v := o.settingValue(ctx, "organize.source_dir"); v != "" {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
if v := o.settingValue(ctx, "qbittorrent.savepath"); v != "" {
|
if v := o.downloaderSavepath(ctx); v != "" {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
return envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads")
|
return envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads")
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (o *OrganizerService) resolveTransferMode(ctx context.Context, override Tra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if mode == TransferMove && o.keepSeedingEnabled(ctx) {
|
if mode == TransferMove && o.keepSeedingEnabled(ctx) {
|
||||||
// 移动会删除源文件导致 qBittorrent 停止做种;保种开启时改用硬链接
|
// 移动会删除源文件导致下载器停止做种;保种开启时改用硬链接
|
||||||
// 既规范命名又保留源文件继续做种上传。硬链接失败时会报错,避免静默
|
// 既规范命名又保留源文件继续做种上传。硬链接失败时会报错,避免静默
|
||||||
// 退化复制后占用双份磁盘空间。
|
// 退化复制后占用双份磁盘空间。
|
||||||
return TransferHardlink
|
return TransferHardlink
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// translateClientPath 将下载客户端报告的路径转换为容器内可访问的路径。
|
// translateClientPath 将下载客户端报告的路径转换为容器内可访问的路径。
|
||||||
// 常见场景:qBittorrent在另一个容器,报告的路径是其容器内路径,需要映射到当前容器。
|
// 常见场景:下载器在另一个容器,报告的路径是其容器内路径,需要映射到当前容器。
|
||||||
func translateClientPath(clientPath string, mappings map[string]string) string {
|
func translateClientPath(clientPath string, mappings map[string]string) string {
|
||||||
if clientPath == "" {
|
if clientPath == "" {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func NewExternalHTTPClient(timeout time.Duration) *http.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewInternalHTTPClient builds an HTTP client for LAN / Docker-internal
|
// 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
|
// 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.
|
// them through HTTP_PROXY/SOCKS proxies makes local WebUI logins hang or fail.
|
||||||
func NewInternalHTTPClient(timeout time.Duration) *http.Client {
|
func NewInternalHTTPClient(timeout time.Duration) *http.Client {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
// hardlink 硬链接(同盘零额外占用,保留源文件;做种不受影响)
|
// hardlink 硬链接(同盘零额外占用,保留源文件;做种不受影响)
|
||||||
// symlink 软链接(保留源文件,指向源)
|
// symlink 软链接(保留源文件,指向源)
|
||||||
//
|
//
|
||||||
// 除 move 外,其余方式都保留源文件,因此 qBittorrent 等下载器仍能在原
|
// 除 move 外,其余方式都保留源文件,因此下载器仍能在原
|
||||||
// 路径找到数据继续做种上传。
|
// 路径找到数据继续做种上传。
|
||||||
package service
|
package service
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
export * from './api'
|
export * from './api'
|
||||||
export * from './auth'
|
export * from './auth'
|
||||||
export * from './downloads'
|
|
||||||
export * from './events'
|
export * from './events'
|
||||||
export * from './history'
|
export * from './history'
|
||||||
export * from './library'
|
export * from './library'
|
||||||
|
|||||||
Reference in New Issue
Block a user