@muhammedaksam/easiarr 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/arr-api.ts +53 -0
- package/src/api/bazarr-api.ts +83 -0
- package/src/api/huntarr-api.ts +622 -0
- package/src/api/jellyseerr-api.ts +82 -8
- package/src/api/naming-config.ts +67 -0
- package/src/api/overseerr-api.ts +24 -0
- package/src/api/qbittorrent-api.ts +58 -0
- package/src/api/quality-profile-api.ts +54 -4
- package/src/apps/registry.ts +35 -1
- package/src/config/homepage-config.ts +46 -13
- package/src/config/trash-quality-definitions.ts +187 -0
- package/src/ui/screens/FullAutoSetup.ts +245 -20
- package/src/ui/screens/TRaSHProfileSetup.ts +9 -1
- package/src/utils/url-utils.ts +38 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRaSH Guides Quality Definitions (File Size Limits)
|
|
3
|
+
* Min/Preferred/Max in MB/min
|
|
4
|
+
* Source: ../Guides/docs/json/radarr/quality-size/movie.json
|
|
5
|
+
* Source: ../Guides/docs/json/sonarr/quality-size/series.json
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface TrashQualityDefinition {
|
|
9
|
+
quality: string
|
|
10
|
+
min: number
|
|
11
|
+
preferred: number
|
|
12
|
+
max: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const TRASH_RADARR_QUALITY_DEFINITIONS: TrashQualityDefinition[] = [
|
|
16
|
+
{
|
|
17
|
+
quality: "HDTV-720p",
|
|
18
|
+
min: 17.1,
|
|
19
|
+
preferred: 1999,
|
|
20
|
+
max: 2000,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
quality: "WEBDL-720p",
|
|
24
|
+
min: 12.5,
|
|
25
|
+
preferred: 1999,
|
|
26
|
+
max: 2000,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
quality: "WEBRip-720p",
|
|
30
|
+
min: 12.5,
|
|
31
|
+
preferred: 1999,
|
|
32
|
+
max: 2000,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
quality: "Bluray-720p",
|
|
36
|
+
min: 25.7,
|
|
37
|
+
preferred: 1999,
|
|
38
|
+
max: 2000,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
quality: "HDTV-1080p",
|
|
42
|
+
min: 33.8,
|
|
43
|
+
preferred: 1999,
|
|
44
|
+
max: 2000,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
quality: "WEBDL-1080p",
|
|
48
|
+
min: 12.5,
|
|
49
|
+
preferred: 1999,
|
|
50
|
+
max: 2000,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
quality: "WEBRip-1080p",
|
|
54
|
+
min: 12.5,
|
|
55
|
+
preferred: 1999,
|
|
56
|
+
max: 2000,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
quality: "Bluray-1080p",
|
|
60
|
+
min: 50.8,
|
|
61
|
+
preferred: 1999,
|
|
62
|
+
max: 2000,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
quality: "Remux-1080p",
|
|
66
|
+
min: 102,
|
|
67
|
+
preferred: 1999,
|
|
68
|
+
max: 2000,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
quality: "HDTV-2160p",
|
|
72
|
+
min: 85,
|
|
73
|
+
preferred: 1999,
|
|
74
|
+
max: 2000,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
quality: "WEBDL-2160p",
|
|
78
|
+
min: 34.5,
|
|
79
|
+
preferred: 1999,
|
|
80
|
+
max: 2000,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
quality: "WEBRip-2160p",
|
|
84
|
+
min: 34.5,
|
|
85
|
+
preferred: 1999,
|
|
86
|
+
max: 2000,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
quality: "Bluray-2160p",
|
|
90
|
+
min: 102,
|
|
91
|
+
preferred: 1999,
|
|
92
|
+
max: 2000,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
quality: "Remux-2160p",
|
|
96
|
+
min: 187.4,
|
|
97
|
+
preferred: 1999,
|
|
98
|
+
max: 2000,
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
export const TRASH_SONARR_QUALITY_DEFINITIONS: TrashQualityDefinition[] = [
|
|
103
|
+
{
|
|
104
|
+
quality: "HDTV-720p",
|
|
105
|
+
min: 10,
|
|
106
|
+
preferred: 995,
|
|
107
|
+
max: 1000,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
quality: "HDTV-1080p",
|
|
111
|
+
min: 15,
|
|
112
|
+
preferred: 995,
|
|
113
|
+
max: 1000,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
quality: "WEBRip-720p",
|
|
117
|
+
min: 10,
|
|
118
|
+
preferred: 995,
|
|
119
|
+
max: 1000,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
quality: "WEBDL-720p",
|
|
123
|
+
min: 10,
|
|
124
|
+
preferred: 995,
|
|
125
|
+
max: 1000,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
quality: "Bluray-720p",
|
|
129
|
+
min: 17.1,
|
|
130
|
+
preferred: 995,
|
|
131
|
+
max: 1000,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
quality: "WEBRip-1080p",
|
|
135
|
+
min: 15,
|
|
136
|
+
preferred: 995,
|
|
137
|
+
max: 1000,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
quality: "WEBDL-1080p",
|
|
141
|
+
min: 15,
|
|
142
|
+
preferred: 995,
|
|
143
|
+
max: 1000,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
quality: "Bluray-1080p",
|
|
147
|
+
min: 50.4,
|
|
148
|
+
preferred: 995,
|
|
149
|
+
max: 1000,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
quality: "Bluray-1080p Remux",
|
|
153
|
+
min: 69.1,
|
|
154
|
+
preferred: 995,
|
|
155
|
+
max: 1000,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
quality: "HDTV-2160p",
|
|
159
|
+
min: 25,
|
|
160
|
+
preferred: 995,
|
|
161
|
+
max: 1000,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
quality: "WEBRip-2160p",
|
|
165
|
+
min: 25,
|
|
166
|
+
preferred: 995,
|
|
167
|
+
max: 1000,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
quality: "WEBDL-2160p",
|
|
171
|
+
min: 25,
|
|
172
|
+
preferred: 995,
|
|
173
|
+
max: 1000,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
quality: "Bluray-2160p",
|
|
177
|
+
min: 94.6,
|
|
178
|
+
preferred: 995,
|
|
179
|
+
max: 1000,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
quality: "Bluray-2160p Remux",
|
|
183
|
+
min: 187.4,
|
|
184
|
+
preferred: 995,
|
|
185
|
+
max: 1000,
|
|
186
|
+
},
|
|
187
|
+
]
|
|
@@ -12,6 +12,7 @@ import { ProwlarrClient, type ArrAppType } from "../../api/prowlarr-api"
|
|
|
12
12
|
import { QBittorrentClient, type QBittorrentCategory } from "../../api/qbittorrent-api"
|
|
13
13
|
import { PortainerApiClient } from "../../api/portainer-api"
|
|
14
14
|
import { JellyfinClient } from "../../api/jellyfin-api"
|
|
15
|
+
import { QualityProfileClient } from "../../api/quality-profile-api"
|
|
15
16
|
import { JellyseerrClient } from "../../api/jellyseerr-api"
|
|
16
17
|
import { CloudflareApi, setupCloudflaredTunnel } from "../../api/cloudflare-api"
|
|
17
18
|
import { PlexApiClient } from "../../api/plex-api"
|
|
@@ -21,6 +22,7 @@ import { OverseerrClient } from "../../api/overseerr-api"
|
|
|
21
22
|
import { TautulliClient } from "../../api/tautulli-api"
|
|
22
23
|
import { HomarrClient } from "../../api/homarr-api"
|
|
23
24
|
import { HeimdallClient } from "../../api/heimdall-api"
|
|
25
|
+
import { HuntarrClient } from "../../api/huntarr-api"
|
|
24
26
|
import { saveConfig } from "../../config"
|
|
25
27
|
import { saveCompose } from "../../compose"
|
|
26
28
|
import { getApp } from "../../apps/registry"
|
|
@@ -28,6 +30,7 @@ import { getApp } from "../../apps/registry"
|
|
|
28
30
|
import { getCategoriesForApps } from "../../utils/categories"
|
|
29
31
|
import { readEnvSync, updateEnv } from "../../utils/env"
|
|
30
32
|
import { debugLog } from "../../utils/debug"
|
|
33
|
+
import { getApplicationUrl } from "../../utils/url-utils"
|
|
31
34
|
|
|
32
35
|
interface SetupStep {
|
|
33
36
|
name: string
|
|
@@ -89,7 +92,10 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
89
92
|
private initSteps(): void {
|
|
90
93
|
this.steps = [
|
|
91
94
|
{ name: "Root Folders", status: "pending" },
|
|
95
|
+
{ name: "Naming Scheme", status: "pending" },
|
|
96
|
+
{ name: "Quality Settings", status: "pending" },
|
|
92
97
|
{ name: "Authentication", status: "pending" },
|
|
98
|
+
{ name: "External URLs", status: "pending" },
|
|
93
99
|
{ name: "Prowlarr Apps", status: "pending" },
|
|
94
100
|
{ name: "FlareSolverr", status: "pending" },
|
|
95
101
|
{ name: "qBittorrent", status: "pending" },
|
|
@@ -104,6 +110,7 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
104
110
|
{ name: "Grafana", status: "pending" },
|
|
105
111
|
{ name: "Homarr", status: "pending" },
|
|
106
112
|
{ name: "Heimdall", status: "pending" },
|
|
113
|
+
{ name: "Huntarr", status: "pending" },
|
|
107
114
|
{ name: "Cloudflare Tunnel", status: "pending" },
|
|
108
115
|
]
|
|
109
116
|
}
|
|
@@ -138,52 +145,64 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
138
145
|
// Step 1: Root folders
|
|
139
146
|
await this.setupRootFolders()
|
|
140
147
|
|
|
141
|
-
// Step 2:
|
|
148
|
+
// Step 2: Naming Scheme
|
|
149
|
+
await this.setupNaming()
|
|
150
|
+
|
|
151
|
+
// Step 2b: Quality Settings
|
|
152
|
+
await this.setupQuality()
|
|
153
|
+
|
|
154
|
+
// Step 3: Authentication
|
|
142
155
|
await this.setupAuthentication()
|
|
143
156
|
|
|
144
|
-
// Step 3:
|
|
157
|
+
// Step 3: External URLs
|
|
158
|
+
await this.setupExternalUrls()
|
|
159
|
+
|
|
160
|
+
// Step 4: Prowlarr apps
|
|
145
161
|
await this.setupProwlarrApps()
|
|
146
162
|
|
|
147
|
-
// Step
|
|
163
|
+
// Step 5: FlareSolverr
|
|
148
164
|
await this.setupFlareSolverr()
|
|
149
165
|
|
|
150
|
-
// Step
|
|
166
|
+
// Step 6: qBittorrent
|
|
151
167
|
await this.setupQBittorrent()
|
|
152
168
|
|
|
153
|
-
// Step
|
|
169
|
+
// Step 7: Portainer
|
|
154
170
|
await this.setupPortainer()
|
|
155
171
|
|
|
156
|
-
// Step
|
|
172
|
+
// Step 8: Jellyfin
|
|
157
173
|
await this.setupJellyfin()
|
|
158
174
|
|
|
159
|
-
// Step
|
|
175
|
+
// Step 9: Jellyseerr
|
|
160
176
|
await this.setupJellyseerr()
|
|
161
177
|
|
|
162
|
-
// Step
|
|
178
|
+
// Step 10: Plex
|
|
163
179
|
await this.setupPlex()
|
|
164
180
|
|
|
165
|
-
// Step
|
|
181
|
+
// Step 11: Overseerr (requires Plex)
|
|
166
182
|
await this.setupOverseerr()
|
|
167
183
|
|
|
168
|
-
// Step
|
|
184
|
+
// Step 12: Tautulli (Plex monitoring)
|
|
169
185
|
await this.setupTautulli()
|
|
170
186
|
|
|
171
|
-
// Step
|
|
187
|
+
// Step 13: Bazarr (subtitles)
|
|
172
188
|
await this.setupBazarr()
|
|
173
189
|
|
|
174
|
-
// Step
|
|
190
|
+
// Step 14: Uptime Kuma (monitors)
|
|
175
191
|
await this.setupUptimeKuma()
|
|
176
192
|
|
|
177
|
-
// Step
|
|
193
|
+
// Step 15: Grafana (dashboards)
|
|
178
194
|
await this.setupGrafana()
|
|
179
195
|
|
|
180
|
-
// Step
|
|
196
|
+
// Step 16: Homarr (dashboard)
|
|
181
197
|
await this.setupHomarr()
|
|
182
198
|
|
|
183
|
-
// Step
|
|
199
|
+
// Step 17: Heimdall (dashboard)
|
|
184
200
|
await this.setupHeimdall()
|
|
185
201
|
|
|
186
|
-
// Step
|
|
202
|
+
// Step 18: Huntarr (*arr app manager)
|
|
203
|
+
await this.setupHuntarr()
|
|
204
|
+
|
|
205
|
+
// Step 19: Cloudflare Tunnel
|
|
187
206
|
await this.setupCloudflare()
|
|
188
207
|
|
|
189
208
|
this.isRunning = false
|
|
@@ -230,6 +249,74 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
230
249
|
this.refreshContent()
|
|
231
250
|
}
|
|
232
251
|
|
|
252
|
+
private async setupNaming(): Promise<void> {
|
|
253
|
+
this.updateStep("Naming Scheme", "running")
|
|
254
|
+
this.refreshContent()
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const arrApps = this.config.apps.filter((a) => {
|
|
258
|
+
return a.enabled && (a.id === "radarr" || a.id === "sonarr")
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
for (const app of arrApps) {
|
|
262
|
+
const apiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
263
|
+
if (!apiKey) continue
|
|
264
|
+
|
|
265
|
+
const def = getApp(app.id)
|
|
266
|
+
if (!def) continue
|
|
267
|
+
|
|
268
|
+
const port = app.port || def.defaultPort
|
|
269
|
+
const client = new ArrApiClient("localhost", port, apiKey, def.rootFolder?.apiVersion || "v3")
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
await client.configureTRaSHNaming(app.id as "radarr" | "sonarr")
|
|
273
|
+
debugLog("FullAutoSetup", `Configured naming for ${app.id}`)
|
|
274
|
+
} catch (e) {
|
|
275
|
+
debugLog("FullAutoSetup", `Failed to configure naming for ${app.id}: ${e}`)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
this.updateStep("Naming Scheme", "success")
|
|
280
|
+
} catch (e) {
|
|
281
|
+
this.updateStep("Naming Scheme", "error", `${e}`)
|
|
282
|
+
}
|
|
283
|
+
this.refreshContent()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private async setupQuality(): Promise<void> {
|
|
287
|
+
this.updateStep("Quality Settings", "running")
|
|
288
|
+
this.refreshContent()
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const arrApps = this.config.apps.filter((a) => {
|
|
292
|
+
return a.enabled && (a.id === "radarr" || a.id === "sonarr")
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
for (const app of arrApps) {
|
|
296
|
+
const apiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
297
|
+
if (!apiKey) continue
|
|
298
|
+
|
|
299
|
+
const def = getApp(app.id)
|
|
300
|
+
if (!def) continue
|
|
301
|
+
|
|
302
|
+
const port = app.port || def.defaultPort
|
|
303
|
+
const client = new QualityProfileClient("localhost", port, apiKey)
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
await client.updateTrashQualityDefinitions(app.id as "radarr" | "sonarr")
|
|
307
|
+
debugLog("FullAutoSetup", `Configured quality settings for ${app.id}`)
|
|
308
|
+
} catch (e) {
|
|
309
|
+
debugLog("FullAutoSetup", `Failed to configure quality settings for ${app.id}: ${e}`)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
this.updateStep("Quality Settings", "success")
|
|
314
|
+
} catch (e) {
|
|
315
|
+
this.updateStep("Quality Settings", "error", `${e}`)
|
|
316
|
+
}
|
|
317
|
+
this.refreshContent()
|
|
318
|
+
}
|
|
319
|
+
|
|
233
320
|
private async setupAuthentication(): Promise<void> {
|
|
234
321
|
this.updateStep("Authentication", "running")
|
|
235
322
|
this.refreshContent()
|
|
@@ -312,6 +399,10 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
312
399
|
debugLog("FullAutoSetup", "Failed to configure Bazarr -> Sonarr connection")
|
|
313
400
|
}
|
|
314
401
|
}
|
|
402
|
+
|
|
403
|
+
// TRaSH Recommended Settings
|
|
404
|
+
await bazarrClient.configureGeneralSettings()
|
|
405
|
+
await bazarrClient.configureDefaultLanguageProfile()
|
|
315
406
|
}
|
|
316
407
|
}
|
|
317
408
|
|
|
@@ -322,6 +413,56 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
322
413
|
this.refreshContent()
|
|
323
414
|
}
|
|
324
415
|
|
|
416
|
+
private async setupExternalUrls(): Promise<void> {
|
|
417
|
+
this.updateStep("External URLs", "running")
|
|
418
|
+
this.refreshContent()
|
|
419
|
+
|
|
420
|
+
let configured = 0
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
// Configure *arr apps (Radarr, Sonarr, Lidarr, Readarr, Whisparr, Prowlarr)
|
|
424
|
+
const arrApps = this.config.apps.filter((a) => {
|
|
425
|
+
const def = getApp(a.id)
|
|
426
|
+
return a.enabled && (def?.rootFolder || a.id === "prowlarr")
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
for (const app of arrApps) {
|
|
430
|
+
const def = getApp(app.id)
|
|
431
|
+
if (!def) continue
|
|
432
|
+
|
|
433
|
+
const apiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
434
|
+
if (!apiKey) {
|
|
435
|
+
continue
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const port = app.port || def.defaultPort
|
|
439
|
+
const apiVersion = app.id === "prowlarr" ? "v1" : def.rootFolder?.apiVersion || "v3"
|
|
440
|
+
const client = new ArrApiClient("localhost", port, apiKey, apiVersion)
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
const applicationUrl = getApplicationUrl(app.id, port, this.config)
|
|
444
|
+
await client.setApplicationUrl(applicationUrl)
|
|
445
|
+
debugLog("FullAutoSetup", `Set applicationUrl for ${app.id}: ${applicationUrl}`)
|
|
446
|
+
configured++
|
|
447
|
+
} catch (e) {
|
|
448
|
+
debugLog("FullAutoSetup", `Failed to set applicationUrl for ${app.id}: ${e}`)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Note: Jellyseerr and Overseerr are handled in their own setup steps
|
|
453
|
+
// (setupJellyseerr/setupOverseerr) because they require authentication first
|
|
454
|
+
|
|
455
|
+
if (configured > 0) {
|
|
456
|
+
this.updateStep("External URLs", "success", `${configured} apps configured`)
|
|
457
|
+
} else {
|
|
458
|
+
this.updateStep("External URLs", "skipped", "No apps with API keys")
|
|
459
|
+
}
|
|
460
|
+
} catch (e) {
|
|
461
|
+
this.updateStep("External URLs", "error", `${e}`)
|
|
462
|
+
}
|
|
463
|
+
this.refreshContent()
|
|
464
|
+
}
|
|
465
|
+
|
|
325
466
|
private async setupProwlarrApps(): Promise<void> {
|
|
326
467
|
this.updateStep("Prowlarr Apps", "running")
|
|
327
468
|
this.refreshContent()
|
|
@@ -576,12 +717,16 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
576
717
|
if (radarrConfig && this.env["API_KEY_RADARR"]) {
|
|
577
718
|
try {
|
|
578
719
|
const radarrDef = getApp("radarr")
|
|
720
|
+
const radarrPort = radarrConfig.port || radarrDef?.defaultPort || 7878
|
|
721
|
+
const radarrExternalUrl = getApplicationUrl("radarr", radarrPort, this.config)
|
|
579
722
|
await client.configureRadarr(
|
|
580
723
|
"radarr",
|
|
581
|
-
|
|
724
|
+
radarrPort,
|
|
582
725
|
this.env["API_KEY_RADARR"],
|
|
583
|
-
radarrDef?.rootFolder?.path || "/data/media/movies"
|
|
726
|
+
radarrDef?.rootFolder?.path || "/data/media/movies",
|
|
727
|
+
radarrExternalUrl
|
|
584
728
|
)
|
|
729
|
+
debugLog("FullAutoSetup", `Jellyseerr: Radarr externalUrl set to ${radarrExternalUrl}`)
|
|
585
730
|
} catch {
|
|
586
731
|
/* Radarr config failed */
|
|
587
732
|
}
|
|
@@ -591,17 +736,43 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
591
736
|
if (sonarrConfig && this.env["API_KEY_SONARR"]) {
|
|
592
737
|
try {
|
|
593
738
|
const sonarrDef = getApp("sonarr")
|
|
739
|
+
const sonarrPort = sonarrConfig.port || sonarrDef?.defaultPort || 8989
|
|
740
|
+
const sonarrExternalUrl = getApplicationUrl("sonarr", sonarrPort, this.config)
|
|
594
741
|
await client.configureSonarr(
|
|
595
742
|
"sonarr",
|
|
596
|
-
|
|
743
|
+
sonarrPort,
|
|
597
744
|
this.env["API_KEY_SONARR"],
|
|
598
|
-
sonarrDef?.rootFolder?.path || "/data/media/tv"
|
|
745
|
+
sonarrDef?.rootFolder?.path || "/data/media/tv",
|
|
746
|
+
sonarrExternalUrl
|
|
599
747
|
)
|
|
748
|
+
debugLog("FullAutoSetup", `Jellyseerr: Sonarr externalUrl set to ${sonarrExternalUrl}`)
|
|
600
749
|
} catch {
|
|
601
750
|
/* Sonarr config failed */
|
|
602
751
|
}
|
|
603
752
|
}
|
|
604
753
|
|
|
754
|
+
// Set Jellyfin's externalHostname for navigation links
|
|
755
|
+
const jellyfinConfig = this.config.apps.find((a) => a.id === "jellyfin" && a.enabled)
|
|
756
|
+
if (jellyfinConfig) {
|
|
757
|
+
try {
|
|
758
|
+
const jellyfinPort = jellyfinConfig.port || 8096
|
|
759
|
+
const jellyfinUrl = getApplicationUrl("jellyfin", jellyfinPort, this.config)
|
|
760
|
+
await client.updateJellyfinSettings({ externalHostname: jellyfinUrl })
|
|
761
|
+
debugLog("FullAutoSetup", `Jellyseerr: Jellyfin externalHostname set to ${jellyfinUrl}`)
|
|
762
|
+
} catch {
|
|
763
|
+
debugLog("FullAutoSetup", "Failed to set Jellyfin externalHostname in Jellyseerr")
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Set Jellyseerr's own applicationUrl (we're already authenticated from setup)
|
|
768
|
+
try {
|
|
769
|
+
const jellyseerrUrl = getApplicationUrl("jellyseerr", port, this.config)
|
|
770
|
+
await client.setApplicationUrl(jellyseerrUrl)
|
|
771
|
+
debugLog("FullAutoSetup", `Jellyseerr: applicationUrl set to ${jellyseerrUrl}`)
|
|
772
|
+
} catch {
|
|
773
|
+
debugLog("FullAutoSetup", "Failed to set Jellyseerr applicationUrl")
|
|
774
|
+
}
|
|
775
|
+
|
|
605
776
|
this.updateStep("Jellyseerr", "success", result.message)
|
|
606
777
|
} else {
|
|
607
778
|
this.updateStep("Jellyseerr", "skipped", result.message)
|
|
@@ -869,6 +1040,16 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
869
1040
|
await updateEnv(result.envUpdates)
|
|
870
1041
|
Object.assign(this.env, result.envUpdates)
|
|
871
1042
|
}
|
|
1043
|
+
|
|
1044
|
+
// Set Overseerr's applicationUrl (we're already authenticated from setup)
|
|
1045
|
+
try {
|
|
1046
|
+
const overseerrUrl = getApplicationUrl("overseerr", port, this.config)
|
|
1047
|
+
await client.setApplicationUrl(overseerrUrl)
|
|
1048
|
+
debugLog("FullAutoSetup", `Overseerr: applicationUrl set to ${overseerrUrl}`)
|
|
1049
|
+
} catch {
|
|
1050
|
+
debugLog("FullAutoSetup", "Failed to set Overseerr applicationUrl")
|
|
1051
|
+
}
|
|
1052
|
+
|
|
872
1053
|
this.updateStep("Overseerr", "success", result.message)
|
|
873
1054
|
} else {
|
|
874
1055
|
this.updateStep("Overseerr", "skipped", result.message)
|
|
@@ -1021,6 +1202,50 @@ export class FullAutoSetup extends BoxRenderable {
|
|
|
1021
1202
|
this.refreshContent()
|
|
1022
1203
|
}
|
|
1023
1204
|
|
|
1205
|
+
private async setupHuntarr(): Promise<void> {
|
|
1206
|
+
this.updateStep("Huntarr", "running")
|
|
1207
|
+
this.refreshContent()
|
|
1208
|
+
|
|
1209
|
+
const huntarrConfig = this.config.apps.find((a) => a.id === "huntarr" && a.enabled)
|
|
1210
|
+
if (!huntarrConfig) {
|
|
1211
|
+
this.updateStep("Huntarr", "skipped", "Not enabled")
|
|
1212
|
+
this.refreshContent()
|
|
1213
|
+
return
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
try {
|
|
1217
|
+
const port = huntarrConfig.port || 9705
|
|
1218
|
+
const client = new HuntarrClient("localhost", port)
|
|
1219
|
+
|
|
1220
|
+
// Check if reachable
|
|
1221
|
+
const healthy = await client.isHealthy()
|
|
1222
|
+
if (!healthy) {
|
|
1223
|
+
this.updateStep("Huntarr", "skipped", "Not reachable yet")
|
|
1224
|
+
this.refreshContent()
|
|
1225
|
+
return
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// Authenticate (creates user if needed, otherwise logs in)
|
|
1229
|
+
const authenticated = await client.authenticate(this.globalUsername, this.globalPassword)
|
|
1230
|
+
if (!authenticated) {
|
|
1231
|
+
this.updateStep("Huntarr", "skipped", "Auth failed")
|
|
1232
|
+
this.refreshContent()
|
|
1233
|
+
return
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Add enabled *arr apps to Huntarr
|
|
1237
|
+
try {
|
|
1238
|
+
const result = await client.setupEasiarrApps(this.config.apps, this.env)
|
|
1239
|
+
this.updateStep("Huntarr", "success", `${result.added} *arr apps added`)
|
|
1240
|
+
} catch {
|
|
1241
|
+
this.updateStep("Huntarr", "success", "Ready")
|
|
1242
|
+
}
|
|
1243
|
+
} catch (e) {
|
|
1244
|
+
this.updateStep("Huntarr", "error", `${e}`)
|
|
1245
|
+
}
|
|
1246
|
+
this.refreshContent()
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1024
1249
|
private async setupHeimdall(): Promise<void> {
|
|
1025
1250
|
this.updateStep("Heimdall", "running")
|
|
1026
1251
|
this.refreshContent()
|
|
@@ -7,6 +7,7 @@ import { BoxRenderable, CliRenderer, TextRenderable, KeyEvent } from "@opentui/c
|
|
|
7
7
|
import { createPageLayout } from "../components/PageLayout"
|
|
8
8
|
import { EasiarrConfig, AppId } from "../../config/schema"
|
|
9
9
|
import { getApp } from "../../apps/registry"
|
|
10
|
+
import { ArrApiClient } from "../../api/arr-api"
|
|
10
11
|
import { QualityProfileClient } from "../../api/quality-profile-api"
|
|
11
12
|
import { CustomFormatClient, getCFNamesForCategories } from "../../api/custom-format-api"
|
|
12
13
|
import { getPresetsForApp, TRaSHProfilePreset } from "../../data/trash-profiles"
|
|
@@ -18,6 +19,7 @@ interface SetupResult {
|
|
|
18
19
|
appName: string
|
|
19
20
|
profile: string
|
|
20
21
|
cfCount: number
|
|
22
|
+
namingConfigured: boolean
|
|
21
23
|
status: "pending" | "configuring" | "success" | "error"
|
|
22
24
|
message?: string
|
|
23
25
|
}
|
|
@@ -185,6 +187,7 @@ export class TRaSHProfileSetup extends BoxRenderable {
|
|
|
185
187
|
appName: appDef.name,
|
|
186
188
|
profile: preset.name,
|
|
187
189
|
cfCount: 0,
|
|
190
|
+
namingConfigured: false,
|
|
188
191
|
status: "configuring",
|
|
189
192
|
})
|
|
190
193
|
this.refreshContent()
|
|
@@ -195,6 +198,7 @@ export class TRaSHProfileSetup extends BoxRenderable {
|
|
|
195
198
|
if (result) {
|
|
196
199
|
result.status = "success"
|
|
197
200
|
result.cfCount = Object.keys(preset.cfScores).length
|
|
201
|
+
result.namingConfigured = true
|
|
198
202
|
}
|
|
199
203
|
} catch (error) {
|
|
200
204
|
const result = this.results.find((r) => r.appId === appId)
|
|
@@ -237,6 +241,10 @@ export class TRaSHProfileSetup extends BoxRenderable {
|
|
|
237
241
|
|
|
238
242
|
// Create quality profile
|
|
239
243
|
await qpClient.createTRaSHProfile(preset.name, preset.cutoffQuality, preset.allowedQualities, preset.cfScores)
|
|
244
|
+
|
|
245
|
+
// Configure naming scheme
|
|
246
|
+
const arrClient = new ArrApiClient("localhost", port, apiKey, appDef.rootFolder?.apiVersion || "v3")
|
|
247
|
+
await arrClient.configureTRaSHNaming(appId as "radarr" | "sonarr")
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
private refreshContent(): void {
|
|
@@ -349,7 +357,7 @@ export class TRaSHProfileSetup extends BoxRenderable {
|
|
|
349
357
|
|
|
350
358
|
let content = `${status} ${result.appName}: ${result.profile}`
|
|
351
359
|
if (result.status === "success") {
|
|
352
|
-
content += ` (${result.cfCount} CF scores)`
|
|
360
|
+
content += ` (${result.cfCount} CF scores, naming configured)`
|
|
353
361
|
}
|
|
354
362
|
if (result.message) {
|
|
355
363
|
content += ` - ${result.message}`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL Utilities
|
|
3
|
+
* Functions for generating app URLs based on Traefik configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { EasiarrConfig } from "../config/schema"
|
|
7
|
+
import { getLocalIp } from "./env"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the local URL for an app (http://LOCAL_IP:PORT)
|
|
11
|
+
*/
|
|
12
|
+
export function getLocalAppUrl(port: number): string {
|
|
13
|
+
const localIp = getLocalIp()
|
|
14
|
+
return `http://${localIp}:${port}`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the external URL for an app (https://APP.DOMAIN)
|
|
19
|
+
* Returns null if Traefik is not enabled
|
|
20
|
+
*/
|
|
21
|
+
export function getExternalAppUrl(appId: string, config: EasiarrConfig): string | null {
|
|
22
|
+
if (!config.traefik?.enabled || !config.traefik.domain) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
return `https://${appId}.${config.traefik.domain}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the appropriate applicationUrl based on Traefik status
|
|
30
|
+
* Returns external URL if Traefik enabled, otherwise local URL
|
|
31
|
+
*/
|
|
32
|
+
export function getApplicationUrl(appId: string, port: number, config: EasiarrConfig): string {
|
|
33
|
+
const externalUrl = getExternalAppUrl(appId, config)
|
|
34
|
+
if (externalUrl) {
|
|
35
|
+
return externalUrl
|
|
36
|
+
}
|
|
37
|
+
return getLocalAppUrl(port)
|
|
38
|
+
}
|