@muhammedaksam/easiarr 1.3.5 → 1.4.1
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/README.md +8 -5
- package/dist/index.js +29837 -0
- package/package.json +18 -16
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -508
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/bazarr-api.ts +0 -356
- package/src/api/cloudflare-api.ts +0 -577
- package/src/api/custom-format-api.ts +0 -212
- package/src/api/grafana-api.ts +0 -314
- package/src/api/heimdall-api.ts +0 -209
- package/src/api/homarr-api.ts +0 -296
- package/src/api/huntarr-api.ts +0 -622
- package/src/api/index.ts +0 -1
- package/src/api/jellyfin-api.ts +0 -473
- package/src/api/jellyseerr-api.ts +0 -660
- package/src/api/maintainerr-api.ts +0 -731
- package/src/api/naming-config.ts +0 -93
- package/src/api/overseerr-api.ts +0 -513
- package/src/api/plex-api.ts +0 -358
- package/src/api/portainer-api.ts +0 -467
- package/src/api/profilarr-api.ts +0 -380
- package/src/api/prowlarr-api.ts +0 -859
- package/src/api/qbittorrent-api.ts +0 -339
- package/src/api/quality-profile-api.ts +0 -269
- package/src/api/tautulli-api.ts +0 -277
- package/src/api/uptime-kuma-api.ts +0 -342
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1255
- package/src/compose/caddy-config.ts +0 -129
- package/src/compose/generator.ts +0 -262
- package/src/compose/index.ts +0 -3
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -185
- package/src/config/bookmarks-generator.ts +0 -149
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -376
- package/src/config/index.ts +0 -3
- package/src/config/manager.ts +0 -149
- package/src/config/recyclarr-config.ts +0 -179
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -85
- package/src/config/soularr-config.ts +0 -105
- package/src/config/trash-quality-definitions.ts +0 -213
- package/src/data/lidarr-custom-formats.ts +0 -127
- package/src/data/trash-profiles.ts +0 -326
- package/src/docker/client.ts +0 -291
- package/src/docker/index.ts +0 -1
- package/src/index.ts +0 -77
- package/src/structure/manager.ts +0 -128
- package/src/ui/App.ts +0 -109
- package/src/ui/components/ApplicationSelector.ts +0 -272
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -152
- package/src/ui/components/UpdateNotification.ts +0 -101
- package/src/ui/index.ts +0 -1
- package/src/ui/screens/AdvancedSettings.ts +0 -181
- package/src/ui/screens/ApiKeyViewer.ts +0 -500
- package/src/ui/screens/AppConfigurator.ts +0 -731
- package/src/ui/screens/AppManager.ts +0 -276
- package/src/ui/screens/CloudflaredSetup.ts +0 -974
- package/src/ui/screens/ContainerControl.ts +0 -579
- package/src/ui/screens/FullAutoSetup.ts +0 -1719
- package/src/ui/screens/HomepageSetup.ts +0 -306
- package/src/ui/screens/JellyfinSetup.ts +0 -471
- package/src/ui/screens/JellyseerrSetup.ts +0 -612
- package/src/ui/screens/LogsViewer.ts +0 -468
- package/src/ui/screens/MainMenu.ts +0 -288
- package/src/ui/screens/MonitorDashboard.ts +0 -866
- package/src/ui/screens/ProwlarrSetup.ts +0 -623
- package/src/ui/screens/QBittorrentSetup.ts +0 -268
- package/src/ui/screens/QuickSetup.ts +0 -1161
- package/src/ui/screens/RecyclarrSetup.ts +0 -418
- package/src/ui/screens/SecretsEditor.ts +0 -214
- package/src/ui/screens/SettingsScreen.ts +0 -605
- package/src/ui/screens/TRaSHProfileSetup.ts +0 -395
- package/src/ui/screens/index.ts +0 -4
- package/src/util/arch.ts +0 -78
- package/src/utils/browser.ts +0 -26
- package/src/utils/categories.ts +0 -59
- package/src/utils/debug.ts +0 -91
- package/src/utils/env.ts +0 -137
- package/src/utils/logs.ts +0 -118
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -101
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -90
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -44
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -107
- package/src/utils/migrations.ts +0 -164
- package/src/utils/password.ts +0 -42
- package/src/utils/unraid.ts +0 -101
- package/src/utils/update-checker.ts +0 -210
- package/src/utils/url-utils.ts +0 -38
|
@@ -1,1719 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Full Auto Setup Screen
|
|
3
|
-
* Runs all configuration steps in sequence
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { BoxRenderable, CliRenderer, TextRenderable, KeyEvent } from "@opentui/core"
|
|
7
|
-
import { createPageLayout } from "../components/PageLayout"
|
|
8
|
-
import type { EasiarrConfig } from "../../config/schema"
|
|
9
|
-
import { ArrApiClient, type AddRootFolderOptions } from "../../api/arr-api"
|
|
10
|
-
import { BazarrApiClient } from "../../api/bazarr-api"
|
|
11
|
-
import { ProwlarrClient, type ArrAppType } from "../../api/prowlarr-api"
|
|
12
|
-
import { QBittorrentClient, type QBittorrentCategory } from "../../api/qbittorrent-api"
|
|
13
|
-
import { PortainerApiClient } from "../../api/portainer-api"
|
|
14
|
-
import { JellyfinClient } from "../../api/jellyfin-api"
|
|
15
|
-
import { QualityProfileClient } from "../../api/quality-profile-api"
|
|
16
|
-
import { JellyseerrClient } from "../../api/jellyseerr-api"
|
|
17
|
-
import { CloudflareApi, setupCloudflaredTunnel } from "../../api/cloudflare-api"
|
|
18
|
-
import { PlexApiClient } from "../../api/plex-api"
|
|
19
|
-
import { UptimeKumaClient } from "../../api/uptime-kuma-api"
|
|
20
|
-
import { GrafanaClient } from "../../api/grafana-api"
|
|
21
|
-
import { OverseerrClient } from "../../api/overseerr-api"
|
|
22
|
-
import { TautulliClient } from "../../api/tautulli-api"
|
|
23
|
-
import { MaintainerrClient } from "../../api/maintainerr-api"
|
|
24
|
-
import { HomarrClient } from "../../api/homarr-api"
|
|
25
|
-
import { HeimdallClient } from "../../api/heimdall-api"
|
|
26
|
-
import { HuntarrClient } from "../../api/huntarr-api"
|
|
27
|
-
import { ProfilarrApiClient } from "../../api/profilarr-api"
|
|
28
|
-
import { saveConfig } from "../../config"
|
|
29
|
-
import { saveCompose } from "../../compose"
|
|
30
|
-
import { generateSlskdConfig, getSlskdConfigPath } from "../../config/slskd-config"
|
|
31
|
-
import { generateSoularrConfig, getSoularrConfigPath } from "../../config/soularr-config"
|
|
32
|
-
import { saveRecyclarrConfig } from "../../config/recyclarr-config"
|
|
33
|
-
import { writeFile, mkdir } from "fs/promises"
|
|
34
|
-
import { dirname } from "path"
|
|
35
|
-
import { existsSync } from "fs"
|
|
36
|
-
import { getApp } from "../../apps/registry"
|
|
37
|
-
// import type { AppId } from "../../config/schema"
|
|
38
|
-
import { getCategoriesForApps } from "../../utils/categories"
|
|
39
|
-
import { readEnvSync, updateEnv } from "../../utils/env"
|
|
40
|
-
import { debugLog } from "../../utils/debug"
|
|
41
|
-
import { getApplicationUrl } from "../../utils/url-utils"
|
|
42
|
-
|
|
43
|
-
interface SetupStep {
|
|
44
|
-
name: string
|
|
45
|
-
status: "pending" | "running" | "success" | "error" | "skipped"
|
|
46
|
-
message?: string
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const ARR_APP_TYPES: Record<string, ArrAppType> = {
|
|
50
|
-
radarr: "Radarr",
|
|
51
|
-
sonarr: "Sonarr",
|
|
52
|
-
lidarr: "Lidarr",
|
|
53
|
-
readarr: "Readarr",
|
|
54
|
-
whisparr: "Whisparr",
|
|
55
|
-
mylar3: "Mylar",
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export class FullAutoSetup extends BoxRenderable {
|
|
59
|
-
private config: EasiarrConfig
|
|
60
|
-
private cliRenderer: CliRenderer
|
|
61
|
-
private onBack: () => void
|
|
62
|
-
private keyHandler!: (key: KeyEvent) => void
|
|
63
|
-
private contentBox!: BoxRenderable
|
|
64
|
-
private pageContainer!: BoxRenderable
|
|
65
|
-
|
|
66
|
-
private isRunning = false
|
|
67
|
-
private isDone = false
|
|
68
|
-
private steps: SetupStep[] = []
|
|
69
|
-
private globalUsername = ""
|
|
70
|
-
private globalPassword = ""
|
|
71
|
-
private env: Record<string, string> = {}
|
|
72
|
-
|
|
73
|
-
constructor(cliRenderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
|
|
74
|
-
const { container: pageContainer, content: contentBox } = createPageLayout(cliRenderer, {
|
|
75
|
-
title: "Full Auto Setup",
|
|
76
|
-
stepInfo: "Configure all services automatically",
|
|
77
|
-
footerHint: [
|
|
78
|
-
{ type: "key", key: "Enter", value: "Start/Continue" },
|
|
79
|
-
{ type: "key", key: "Esc", value: "Back" },
|
|
80
|
-
],
|
|
81
|
-
})
|
|
82
|
-
super(cliRenderer, { width: "100%", height: "100%" })
|
|
83
|
-
this.add(pageContainer)
|
|
84
|
-
|
|
85
|
-
this.config = config
|
|
86
|
-
this.cliRenderer = cliRenderer
|
|
87
|
-
this.onBack = onBack
|
|
88
|
-
this.contentBox = contentBox
|
|
89
|
-
this.pageContainer = pageContainer
|
|
90
|
-
|
|
91
|
-
this.env = readEnvSync()
|
|
92
|
-
this.globalUsername = this.env["USERNAME_GLOBAL"] || "admin"
|
|
93
|
-
this.globalPassword = this.env["PASSWORD_GLOBAL"] || "Ch4ng3m3!1234securityReasons"
|
|
94
|
-
|
|
95
|
-
this.initKeyHandler()
|
|
96
|
-
this.initSteps()
|
|
97
|
-
this.refreshContent()
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
private initSteps(): void {
|
|
101
|
-
this.steps = [
|
|
102
|
-
{ name: "Root Folders", status: "pending" },
|
|
103
|
-
{ name: "Naming Scheme", status: "pending" },
|
|
104
|
-
{ name: "Quality Settings", status: "pending" },
|
|
105
|
-
{ name: "Authentication", status: "pending" },
|
|
106
|
-
{ name: "External URLs", status: "pending" },
|
|
107
|
-
{ name: "Prowlarr Apps", status: "pending" },
|
|
108
|
-
{ name: "FlareSolverr", status: "pending" },
|
|
109
|
-
{ name: "qBittorrent", status: "pending" },
|
|
110
|
-
{ name: "Portainer", status: "pending" },
|
|
111
|
-
{ name: "Jellyfin", status: "pending" },
|
|
112
|
-
{ name: "Jellyseerr", status: "pending" },
|
|
113
|
-
{ name: "Plex", status: "pending" },
|
|
114
|
-
{ name: "Overseerr", status: "pending" },
|
|
115
|
-
{ name: "Tautulli", status: "pending" },
|
|
116
|
-
{ name: "Maintainerr", status: "pending" },
|
|
117
|
-
{ name: "Bazarr", status: "pending" },
|
|
118
|
-
{ name: "Uptime Kuma", status: "pending" },
|
|
119
|
-
{ name: "Grafana", status: "pending" },
|
|
120
|
-
{ name: "Homarr", status: "pending" },
|
|
121
|
-
{ name: "Heimdall", status: "pending" },
|
|
122
|
-
{ name: "Huntarr", status: "pending" },
|
|
123
|
-
{ name: "Slskd", status: "pending" },
|
|
124
|
-
{ name: "Soularr", status: "pending" },
|
|
125
|
-
{ name: "Recyclarr", status: "pending" },
|
|
126
|
-
{ name: "Profilarr", status: "pending" },
|
|
127
|
-
{ name: "Cloudflare Tunnel", status: "pending" },
|
|
128
|
-
]
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private initKeyHandler(): void {
|
|
132
|
-
this.keyHandler = (key: KeyEvent) => {
|
|
133
|
-
debugLog("FullAutoSetup", `Key: ${key.name}, running=${this.isRunning}`)
|
|
134
|
-
|
|
135
|
-
if (key.name === "escape" || (key.name === "c" && key.ctrl)) {
|
|
136
|
-
if (!this.isRunning) {
|
|
137
|
-
this.cleanup()
|
|
138
|
-
}
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (key.name === "return") {
|
|
143
|
-
if (this.isDone) {
|
|
144
|
-
this.cleanup()
|
|
145
|
-
} else if (!this.isRunning) {
|
|
146
|
-
this.runSetup()
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
this.cliRenderer.keyInput.on("keypress", this.keyHandler)
|
|
151
|
-
debugLog("FullAutoSetup", "Key handler registered")
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private async runSetup(): Promise<void> {
|
|
155
|
-
this.isRunning = true
|
|
156
|
-
this.refreshContent()
|
|
157
|
-
|
|
158
|
-
// Step 1: Root folders
|
|
159
|
-
await this.setupRootFolders()
|
|
160
|
-
|
|
161
|
-
// Step 2: Naming Scheme
|
|
162
|
-
await this.setupNaming()
|
|
163
|
-
|
|
164
|
-
// Step 2b: Quality Settings
|
|
165
|
-
await this.setupQuality()
|
|
166
|
-
|
|
167
|
-
// Step 3: Authentication
|
|
168
|
-
await this.setupAuthentication()
|
|
169
|
-
|
|
170
|
-
// Step 3: External URLs
|
|
171
|
-
await this.setupExternalUrls()
|
|
172
|
-
|
|
173
|
-
// Step 4: Prowlarr apps
|
|
174
|
-
await this.setupProwlarrApps()
|
|
175
|
-
|
|
176
|
-
// Step 5: FlareSolverr
|
|
177
|
-
await this.setupFlareSolverr()
|
|
178
|
-
|
|
179
|
-
// Step 6: qBittorrent
|
|
180
|
-
await this.setupQBittorrent()
|
|
181
|
-
|
|
182
|
-
// Step 7: Portainer
|
|
183
|
-
await this.setupPortainer()
|
|
184
|
-
|
|
185
|
-
// Step 8: Jellyfin
|
|
186
|
-
await this.setupJellyfin()
|
|
187
|
-
|
|
188
|
-
// Step 9: Jellyseerr
|
|
189
|
-
await this.setupJellyseerr()
|
|
190
|
-
|
|
191
|
-
// Step 10: Plex
|
|
192
|
-
await this.setupPlex()
|
|
193
|
-
|
|
194
|
-
// Step 11: Overseerr (requires Plex)
|
|
195
|
-
await this.setupOverseerr()
|
|
196
|
-
|
|
197
|
-
// Step 12: Tautulli (Plex monitoring)
|
|
198
|
-
await this.setupTautulli()
|
|
199
|
-
|
|
200
|
-
// Step 12b: Maintainerr (Plex media management)
|
|
201
|
-
await this.setupMaintainerr()
|
|
202
|
-
|
|
203
|
-
// Step 13: Bazarr (subtitles)
|
|
204
|
-
await this.setupBazarr()
|
|
205
|
-
|
|
206
|
-
// Step 14: Uptime Kuma (monitors)
|
|
207
|
-
await this.setupUptimeKuma()
|
|
208
|
-
|
|
209
|
-
// Step 15: Grafana (dashboards)
|
|
210
|
-
await this.setupGrafana()
|
|
211
|
-
|
|
212
|
-
// Step 16: Homarr (dashboard)
|
|
213
|
-
await this.setupHomarr()
|
|
214
|
-
|
|
215
|
-
// Step 17: Heimdall (dashboard)
|
|
216
|
-
await this.setupHeimdall()
|
|
217
|
-
|
|
218
|
-
// Step 18: Huntarr (*arr app manager)
|
|
219
|
-
await this.setupHuntarr()
|
|
220
|
-
|
|
221
|
-
// Step 19: Slskd (Soulseek client)
|
|
222
|
-
await this.setupSlskd()
|
|
223
|
-
|
|
224
|
-
// Step 20: Soularr (Lidarr -> Slskd bridge)
|
|
225
|
-
await this.setupSoularr()
|
|
226
|
-
|
|
227
|
-
// Step 21: Recyclarr (TRaSH Guides sync)
|
|
228
|
-
await this.setupRecyclarr()
|
|
229
|
-
|
|
230
|
-
// Step 22: Profilarr (Alternative TRaSH Guides sync)
|
|
231
|
-
await this.setupProfilarr()
|
|
232
|
-
|
|
233
|
-
// Step 22: Cloudflare Tunnel
|
|
234
|
-
await this.setupCloudflare()
|
|
235
|
-
|
|
236
|
-
this.isRunning = false
|
|
237
|
-
this.isDone = true
|
|
238
|
-
this.refreshContent()
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
private async setupRootFolders(): Promise<void> {
|
|
242
|
-
this.updateStep("Root Folders", "running")
|
|
243
|
-
this.refreshContent()
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
const arrApps = this.config.apps.filter((a) => {
|
|
247
|
-
const def = getApp(a.id)
|
|
248
|
-
return a.enabled && def?.rootFolder
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
for (const app of arrApps) {
|
|
252
|
-
const def = getApp(app.id)
|
|
253
|
-
if (!def?.rootFolder) continue
|
|
254
|
-
|
|
255
|
-
const apiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
256
|
-
if (!apiKey) continue
|
|
257
|
-
|
|
258
|
-
const port = app.port || def.defaultPort
|
|
259
|
-
const client = new ArrApiClient("localhost", port, apiKey, def.rootFolder.apiVersion)
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
const existing = await client.getRootFolders()
|
|
263
|
-
if (existing.length === 0) {
|
|
264
|
-
const options: AddRootFolderOptions = { path: def.rootFolder.path }
|
|
265
|
-
if (app.id === "lidarr") options.name = "Music"
|
|
266
|
-
await client.addRootFolder(options)
|
|
267
|
-
}
|
|
268
|
-
} catch {
|
|
269
|
-
// Skip individual failures
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
this.updateStep("Root Folders", "success")
|
|
274
|
-
} catch (e) {
|
|
275
|
-
this.updateStep("Root Folders", "error", `${e}`)
|
|
276
|
-
}
|
|
277
|
-
this.refreshContent()
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
private async setupNaming(): Promise<void> {
|
|
281
|
-
this.updateStep("Naming Scheme", "running")
|
|
282
|
-
this.refreshContent()
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
const arrApps = this.config.apps.filter((a) => {
|
|
286
|
-
return a.enabled && (a.id === "radarr" || a.id === "sonarr" || a.id === "lidarr")
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
for (const app of arrApps) {
|
|
290
|
-
const apiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
291
|
-
if (!apiKey) continue
|
|
292
|
-
|
|
293
|
-
const def = getApp(app.id)
|
|
294
|
-
if (!def) continue
|
|
295
|
-
|
|
296
|
-
const port = app.port || def.defaultPort
|
|
297
|
-
const client = new ArrApiClient("localhost", port, apiKey, def.rootFolder?.apiVersion || "v3")
|
|
298
|
-
|
|
299
|
-
try {
|
|
300
|
-
await client.configureTRaSHNaming(app.id as "radarr" | "sonarr" | "lidarr")
|
|
301
|
-
debugLog("FullAutoSetup", `Configured naming for ${app.id}`)
|
|
302
|
-
} catch (e) {
|
|
303
|
-
debugLog("FullAutoSetup", `Failed to configure naming for ${app.id}: ${e}`)
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
this.updateStep("Naming Scheme", "success")
|
|
308
|
-
} catch (e) {
|
|
309
|
-
this.updateStep("Naming Scheme", "error", `${e}`)
|
|
310
|
-
}
|
|
311
|
-
this.refreshContent()
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private async setupQuality(): Promise<void> {
|
|
315
|
-
this.updateStep("Quality Settings", "running")
|
|
316
|
-
this.refreshContent()
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
const arrApps = this.config.apps.filter((a) => {
|
|
320
|
-
return a.enabled && (a.id === "radarr" || a.id === "sonarr" || a.id === "lidarr")
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
for (const app of arrApps) {
|
|
324
|
-
const apiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
325
|
-
if (!apiKey) continue
|
|
326
|
-
|
|
327
|
-
const def = getApp(app.id)
|
|
328
|
-
if (!def) continue
|
|
329
|
-
|
|
330
|
-
const port = app.port || def.defaultPort
|
|
331
|
-
const apiVersion = def.rootFolder?.apiVersion || "v3"
|
|
332
|
-
const client = new QualityProfileClient("localhost", port, apiKey, apiVersion)
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
await client.updateTrashQualityDefinitions(app.id as "radarr" | "sonarr" | "lidarr")
|
|
336
|
-
debugLog("FullAutoSetup", `Configured quality settings for ${app.id}`)
|
|
337
|
-
} catch (e) {
|
|
338
|
-
debugLog("FullAutoSetup", `Failed to configure quality settings for ${app.id}: ${e}`)
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
this.updateStep("Quality Settings", "success")
|
|
343
|
-
} catch (e) {
|
|
344
|
-
this.updateStep("Quality Settings", "error", `${e}`)
|
|
345
|
-
}
|
|
346
|
-
this.refreshContent()
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
private async setupAuthentication(): Promise<void> {
|
|
350
|
-
this.updateStep("Authentication", "running")
|
|
351
|
-
this.refreshContent()
|
|
352
|
-
|
|
353
|
-
if (!this.globalPassword) {
|
|
354
|
-
this.updateStep("Authentication", "skipped", "No PASSWORD_GLOBAL set")
|
|
355
|
-
this.refreshContent()
|
|
356
|
-
return
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
try {
|
|
360
|
-
// Setup *arr apps (Radarr, Sonarr, Lidarr, etc.) with form auth
|
|
361
|
-
const arrApps = this.config.apps.filter((a) => {
|
|
362
|
-
const def = getApp(a.id)
|
|
363
|
-
return a.enabled && (def?.rootFolder || a.id === "prowlarr")
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
for (const app of arrApps) {
|
|
367
|
-
const def = getApp(app.id)
|
|
368
|
-
if (!def) continue
|
|
369
|
-
|
|
370
|
-
const apiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
371
|
-
if (!apiKey) continue
|
|
372
|
-
|
|
373
|
-
const port = app.port || def.defaultPort
|
|
374
|
-
const apiVersion = app.id === "prowlarr" ? "v1" : def.rootFolder?.apiVersion || "v3"
|
|
375
|
-
const client = new ArrApiClient("localhost", port, apiKey, apiVersion)
|
|
376
|
-
|
|
377
|
-
try {
|
|
378
|
-
await client.updateHostConfig(this.globalUsername, this.globalPassword, false)
|
|
379
|
-
} catch {
|
|
380
|
-
// Skip individual failures
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Setup Bazarr form authentication and Radarr/Sonarr connections
|
|
385
|
-
const bazarrConfig = this.config.apps.find((a) => a.id === "bazarr" && a.enabled)
|
|
386
|
-
if (bazarrConfig) {
|
|
387
|
-
const bazarrApiKey = this.env["API_KEY_BAZARR"]
|
|
388
|
-
if (bazarrApiKey) {
|
|
389
|
-
const bazarrDef = getApp("bazarr")
|
|
390
|
-
const bazarrPort = bazarrConfig.port || bazarrDef?.defaultPort || 6767
|
|
391
|
-
const bazarrClient = new BazarrApiClient("localhost", bazarrPort)
|
|
392
|
-
bazarrClient.setApiKey(bazarrApiKey)
|
|
393
|
-
|
|
394
|
-
try {
|
|
395
|
-
// Enable form auth
|
|
396
|
-
await bazarrClient.enableFormAuth(this.globalUsername, this.globalPassword, false)
|
|
397
|
-
} catch {
|
|
398
|
-
// Skip Bazarr auth failure - non-critical
|
|
399
|
-
debugLog("FullAutoSetup", "Bazarr form auth failed, continuing...")
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Configure Radarr connection if Radarr is enabled
|
|
403
|
-
// Use container name 'radarr' since Bazarr runs in Docker
|
|
404
|
-
const radarrConfig = this.config.apps.find((a) => a.id === "radarr" && a.enabled)
|
|
405
|
-
const radarrApiKey = this.env["API_KEY_RADARR"]
|
|
406
|
-
if (radarrConfig && radarrApiKey) {
|
|
407
|
-
try {
|
|
408
|
-
const radarrDef = getApp("radarr")
|
|
409
|
-
const radarrPort = radarrConfig.port || radarrDef?.defaultPort || 7878
|
|
410
|
-
await bazarrClient.configureRadarr("radarr", radarrPort, radarrApiKey)
|
|
411
|
-
debugLog("FullAutoSetup", "Bazarr -> Radarr connection configured")
|
|
412
|
-
} catch {
|
|
413
|
-
debugLog("FullAutoSetup", "Failed to configure Bazarr -> Radarr connection")
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Configure Sonarr connection if Sonarr is enabled
|
|
418
|
-
// Use container name 'sonarr' since Bazarr runs in Docker
|
|
419
|
-
const sonarrConfig = this.config.apps.find((a) => a.id === "sonarr" && a.enabled)
|
|
420
|
-
const sonarrApiKey = this.env["API_KEY_SONARR"]
|
|
421
|
-
if (sonarrConfig && sonarrApiKey) {
|
|
422
|
-
try {
|
|
423
|
-
const sonarrDef = getApp("sonarr")
|
|
424
|
-
const sonarrPort = sonarrConfig.port || sonarrDef?.defaultPort || 8989
|
|
425
|
-
await bazarrClient.configureSonarr("sonarr", sonarrPort, sonarrApiKey)
|
|
426
|
-
debugLog("FullAutoSetup", "Bazarr -> Sonarr connection configured")
|
|
427
|
-
} catch {
|
|
428
|
-
debugLog("FullAutoSetup", "Failed to configure Bazarr -> Sonarr connection")
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// TRaSH Recommended Settings
|
|
433
|
-
await bazarrClient.configureGeneralSettings()
|
|
434
|
-
await bazarrClient.configureDefaultLanguageProfile()
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
this.updateStep("Authentication", "success")
|
|
439
|
-
} catch (e) {
|
|
440
|
-
this.updateStep("Authentication", "error", `${e}`)
|
|
441
|
-
}
|
|
442
|
-
this.refreshContent()
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private async setupExternalUrls(): Promise<void> {
|
|
446
|
-
this.updateStep("External URLs", "running")
|
|
447
|
-
this.refreshContent()
|
|
448
|
-
|
|
449
|
-
let configured = 0
|
|
450
|
-
|
|
451
|
-
try {
|
|
452
|
-
// Configure *arr apps (Radarr, Sonarr, Lidarr, Readarr, Whisparr, Prowlarr)
|
|
453
|
-
const arrApps = this.config.apps.filter((a) => {
|
|
454
|
-
const def = getApp(a.id)
|
|
455
|
-
return a.enabled && (def?.rootFolder || a.id === "prowlarr")
|
|
456
|
-
})
|
|
457
|
-
|
|
458
|
-
for (const app of arrApps) {
|
|
459
|
-
const def = getApp(app.id)
|
|
460
|
-
if (!def) continue
|
|
461
|
-
|
|
462
|
-
const apiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
463
|
-
if (!apiKey) {
|
|
464
|
-
continue
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const port = app.port || def.defaultPort
|
|
468
|
-
const apiVersion = app.id === "prowlarr" ? "v1" : def.rootFolder?.apiVersion || "v3"
|
|
469
|
-
const client = new ArrApiClient("localhost", port, apiKey, apiVersion)
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
const applicationUrl = getApplicationUrl(app.id, port, this.config)
|
|
473
|
-
await client.setApplicationUrl(applicationUrl)
|
|
474
|
-
debugLog("FullAutoSetup", `Set applicationUrl for ${app.id}: ${applicationUrl}`)
|
|
475
|
-
configured++
|
|
476
|
-
} catch (e) {
|
|
477
|
-
debugLog("FullAutoSetup", `Failed to set applicationUrl for ${app.id}: ${e}`)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Note: Jellyseerr and Overseerr are handled in their own setup steps
|
|
482
|
-
// (setupJellyseerr/setupOverseerr) because they require authentication first
|
|
483
|
-
|
|
484
|
-
if (configured > 0) {
|
|
485
|
-
this.updateStep("External URLs", "success", `${configured} apps configured`)
|
|
486
|
-
} else {
|
|
487
|
-
this.updateStep("External URLs", "skipped", "No apps with API keys")
|
|
488
|
-
}
|
|
489
|
-
} catch (e) {
|
|
490
|
-
this.updateStep("External URLs", "error", `${e}`)
|
|
491
|
-
}
|
|
492
|
-
this.refreshContent()
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
private async setupProwlarrApps(): Promise<void> {
|
|
496
|
-
this.updateStep("Prowlarr Apps", "running")
|
|
497
|
-
this.refreshContent()
|
|
498
|
-
|
|
499
|
-
const apiKey = this.env["API_KEY_PROWLARR"]
|
|
500
|
-
if (!apiKey) {
|
|
501
|
-
this.updateStep("Prowlarr Apps", "skipped", "No Prowlarr API key")
|
|
502
|
-
this.refreshContent()
|
|
503
|
-
return
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
try {
|
|
507
|
-
const prowlarrConfig = this.config.apps.find((a) => a.id === "prowlarr")
|
|
508
|
-
const prowlarrPort = prowlarrConfig?.port || 9696
|
|
509
|
-
const prowlarr = new ProwlarrClient("localhost", prowlarrPort, apiKey)
|
|
510
|
-
|
|
511
|
-
const arrApps = this.config.apps.filter((a) => {
|
|
512
|
-
return a.enabled && ARR_APP_TYPES[a.id]
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
for (const app of arrApps) {
|
|
516
|
-
const appType = ARR_APP_TYPES[app.id]
|
|
517
|
-
if (!appType) continue
|
|
518
|
-
|
|
519
|
-
const appApiKey = this.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
520
|
-
if (!appApiKey) continue
|
|
521
|
-
|
|
522
|
-
const def = getApp(app.id)
|
|
523
|
-
const port = app.port || def?.defaultPort || 7878
|
|
524
|
-
|
|
525
|
-
try {
|
|
526
|
-
await prowlarr.addArrApp(appType, app.id, port, appApiKey, "prowlarr", prowlarrPort, def?.prowlarrCategoryIds)
|
|
527
|
-
} catch {
|
|
528
|
-
// Skip - may already exist
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Trigger sync
|
|
533
|
-
try {
|
|
534
|
-
await prowlarr.syncApplications()
|
|
535
|
-
} catch {
|
|
536
|
-
// May fail if no indexers
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
this.updateStep("Prowlarr Apps", "success")
|
|
540
|
-
} catch (e) {
|
|
541
|
-
this.updateStep("Prowlarr Apps", "error", `${e}`)
|
|
542
|
-
}
|
|
543
|
-
this.refreshContent()
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
private async setupFlareSolverr(): Promise<void> {
|
|
547
|
-
this.updateStep("FlareSolverr", "running")
|
|
548
|
-
this.refreshContent()
|
|
549
|
-
|
|
550
|
-
const apiKey = this.env["API_KEY_PROWLARR"]
|
|
551
|
-
const flaresolverr = this.config.apps.find((a) => a.id === "flaresolverr" && a.enabled)
|
|
552
|
-
|
|
553
|
-
if (!apiKey || !flaresolverr) {
|
|
554
|
-
this.updateStep("FlareSolverr", "skipped", "Not enabled or no Prowlarr")
|
|
555
|
-
this.refreshContent()
|
|
556
|
-
return
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
try {
|
|
560
|
-
const prowlarrConfig = this.config.apps.find((a) => a.id === "prowlarr")
|
|
561
|
-
const prowlarrPort = prowlarrConfig?.port || 9696
|
|
562
|
-
const prowlarr = new ProwlarrClient("localhost", prowlarrPort, apiKey)
|
|
563
|
-
|
|
564
|
-
await prowlarr.configureFlareSolverr("http://flaresolverr:8191")
|
|
565
|
-
this.updateStep("FlareSolverr", "success")
|
|
566
|
-
} catch (e) {
|
|
567
|
-
this.updateStep("FlareSolverr", "error", `${e}`)
|
|
568
|
-
}
|
|
569
|
-
this.refreshContent()
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
private async setupQBittorrent(): Promise<void> {
|
|
573
|
-
this.updateStep("qBittorrent", "running")
|
|
574
|
-
this.refreshContent()
|
|
575
|
-
|
|
576
|
-
const qbConfig = this.config.apps.find((a) => a.id === "qbittorrent" && a.enabled)
|
|
577
|
-
if (!qbConfig) {
|
|
578
|
-
this.updateStep("qBittorrent", "skipped", "Not enabled")
|
|
579
|
-
this.refreshContent()
|
|
580
|
-
return
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
try {
|
|
584
|
-
const host = "localhost"
|
|
585
|
-
const port = qbConfig.port || 8080
|
|
586
|
-
const user = this.env["USERNAME_QBITTORRENT"] || "admin"
|
|
587
|
-
const pass = this.env["PASSWORD_QBITTORRENT"] || this.env["QBITTORRENT_PASS"] || ""
|
|
588
|
-
|
|
589
|
-
if (!pass) {
|
|
590
|
-
this.updateStep("qBittorrent", "skipped", "No PASSWORD_QBITTORRENT in .env")
|
|
591
|
-
this.refreshContent()
|
|
592
|
-
return
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const client = new QBittorrentClient(host, port, user, pass)
|
|
596
|
-
|
|
597
|
-
const result = await client.setup({
|
|
598
|
-
username: user,
|
|
599
|
-
password: pass,
|
|
600
|
-
env: this.env,
|
|
601
|
-
})
|
|
602
|
-
|
|
603
|
-
if (result.success) {
|
|
604
|
-
// Configure categories after basic setup
|
|
605
|
-
const enabledApps = this.config.apps.filter((a) => a.enabled).map((a) => a.id)
|
|
606
|
-
const categories: QBittorrentCategory[] = getCategoriesForApps(enabledApps).map((cat) => ({
|
|
607
|
-
name: cat.name,
|
|
608
|
-
savePath: `/data/torrents/${cat.name}`,
|
|
609
|
-
}))
|
|
610
|
-
|
|
611
|
-
await client.configureTRaSHCompliant(categories, { user, pass })
|
|
612
|
-
this.updateStep("qBittorrent", "success", result.message)
|
|
613
|
-
} else {
|
|
614
|
-
this.updateStep("qBittorrent", "error", result.message)
|
|
615
|
-
}
|
|
616
|
-
} catch (e) {
|
|
617
|
-
this.updateStep("qBittorrent", "error", `${e}`)
|
|
618
|
-
}
|
|
619
|
-
this.refreshContent()
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
private async setupPortainer(): Promise<void> {
|
|
623
|
-
this.updateStep("Portainer", "running")
|
|
624
|
-
this.refreshContent()
|
|
625
|
-
|
|
626
|
-
const portainerConfig = this.config.apps.find((a) => a.id === "portainer" && a.enabled)
|
|
627
|
-
if (!portainerConfig) {
|
|
628
|
-
this.updateStep("Portainer", "skipped", "Not enabled")
|
|
629
|
-
this.refreshContent()
|
|
630
|
-
return
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
if (!this.globalPassword) {
|
|
634
|
-
this.updateStep("Portainer", "skipped", "No PASSWORD_GLOBAL set")
|
|
635
|
-
this.refreshContent()
|
|
636
|
-
return
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
const port = portainerConfig.port || 9000
|
|
641
|
-
const client = new PortainerApiClient("localhost", port)
|
|
642
|
-
|
|
643
|
-
const result = await client.setup({
|
|
644
|
-
username: this.globalUsername,
|
|
645
|
-
password: this.globalPassword,
|
|
646
|
-
env: this.env,
|
|
647
|
-
})
|
|
648
|
-
|
|
649
|
-
if (result.success) {
|
|
650
|
-
if (result.envUpdates) {
|
|
651
|
-
await updateEnv(result.envUpdates)
|
|
652
|
-
Object.assign(this.env, result.envUpdates)
|
|
653
|
-
}
|
|
654
|
-
this.updateStep("Portainer", "success", result.message)
|
|
655
|
-
} else {
|
|
656
|
-
this.updateStep("Portainer", "skipped", result.message)
|
|
657
|
-
}
|
|
658
|
-
} catch (e) {
|
|
659
|
-
this.updateStep("Portainer", "error", `${e}`)
|
|
660
|
-
}
|
|
661
|
-
this.refreshContent()
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
private async setupJellyfin(): Promise<void> {
|
|
665
|
-
this.updateStep("Jellyfin", "running")
|
|
666
|
-
this.refreshContent()
|
|
667
|
-
|
|
668
|
-
const jellyfinConfig = this.config.apps.find((a) => a.id === "jellyfin" && a.enabled)
|
|
669
|
-
if (!jellyfinConfig) {
|
|
670
|
-
this.updateStep("Jellyfin", "skipped", "Not enabled")
|
|
671
|
-
this.refreshContent()
|
|
672
|
-
return
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
try {
|
|
676
|
-
const port = jellyfinConfig.port || 8096
|
|
677
|
-
const client = new JellyfinClient("localhost", port)
|
|
678
|
-
|
|
679
|
-
const result = await client.setup({
|
|
680
|
-
username: this.globalUsername,
|
|
681
|
-
password: this.globalPassword,
|
|
682
|
-
env: this.env,
|
|
683
|
-
})
|
|
684
|
-
|
|
685
|
-
if (result.success) {
|
|
686
|
-
if (result.envUpdates) {
|
|
687
|
-
await updateEnv(result.envUpdates)
|
|
688
|
-
Object.assign(this.env, result.envUpdates)
|
|
689
|
-
}
|
|
690
|
-
this.updateStep("Jellyfin", "success", result.message)
|
|
691
|
-
} else {
|
|
692
|
-
this.updateStep("Jellyfin", "skipped", result.message)
|
|
693
|
-
}
|
|
694
|
-
} catch (e) {
|
|
695
|
-
this.updateStep("Jellyfin", "error", `${e}`)
|
|
696
|
-
}
|
|
697
|
-
this.refreshContent()
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
private async setupJellyseerr(): Promise<void> {
|
|
701
|
-
this.updateStep("Jellyseerr", "running")
|
|
702
|
-
this.refreshContent()
|
|
703
|
-
|
|
704
|
-
const jellyseerrConfig = this.config.apps.find((a) => a.id === "jellyseerr" && a.enabled)
|
|
705
|
-
if (!jellyseerrConfig) {
|
|
706
|
-
this.updateStep("Jellyseerr", "skipped", "Not enabled")
|
|
707
|
-
this.refreshContent()
|
|
708
|
-
return
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// Check if a media server is enabled
|
|
712
|
-
const jellyfinConfig = this.config.apps.find((a) => a.id === "jellyfin" && a.enabled)
|
|
713
|
-
const plexConfig = this.config.apps.find((a) => a.id === "plex" && a.enabled)
|
|
714
|
-
|
|
715
|
-
if (!jellyfinConfig && !plexConfig) {
|
|
716
|
-
this.updateStep("Jellyseerr", "skipped", "No media server enabled")
|
|
717
|
-
this.refreshContent()
|
|
718
|
-
return
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// Jellyseerr only supports Jellyfin automation (Plex requires manual setup)
|
|
722
|
-
if (!jellyfinConfig) {
|
|
723
|
-
this.updateStep("Jellyseerr", "skipped", "Plex requires manual setup")
|
|
724
|
-
this.refreshContent()
|
|
725
|
-
return
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
try {
|
|
729
|
-
const port = jellyseerrConfig.port || 5055
|
|
730
|
-
const client = new JellyseerrClient("localhost", port)
|
|
731
|
-
|
|
732
|
-
const result = await client.setup({
|
|
733
|
-
username: this.globalUsername,
|
|
734
|
-
password: this.globalPassword,
|
|
735
|
-
env: this.env,
|
|
736
|
-
})
|
|
737
|
-
|
|
738
|
-
if (result.success) {
|
|
739
|
-
if (result.envUpdates) {
|
|
740
|
-
await updateEnv(result.envUpdates)
|
|
741
|
-
Object.assign(this.env, result.envUpdates)
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// Configure Radarr/Sonarr connections after base setup
|
|
745
|
-
const radarrConfig = this.config.apps.find((a) => a.id === "radarr" && a.enabled)
|
|
746
|
-
if (radarrConfig && this.env["API_KEY_RADARR"]) {
|
|
747
|
-
try {
|
|
748
|
-
const radarrDef = getApp("radarr")
|
|
749
|
-
const radarrPort = radarrConfig.port || radarrDef?.defaultPort || 7878
|
|
750
|
-
const radarrExternalUrl = getApplicationUrl("radarr", radarrPort, this.config)
|
|
751
|
-
await client.configureRadarr(
|
|
752
|
-
"radarr",
|
|
753
|
-
radarrPort,
|
|
754
|
-
this.env["API_KEY_RADARR"],
|
|
755
|
-
radarrDef?.rootFolder?.path || "/data/media/movies",
|
|
756
|
-
radarrExternalUrl
|
|
757
|
-
)
|
|
758
|
-
debugLog("FullAutoSetup", `Jellyseerr: Radarr externalUrl set to ${radarrExternalUrl}`)
|
|
759
|
-
} catch {
|
|
760
|
-
/* Radarr config failed */
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const sonarrConfig = this.config.apps.find((a) => a.id === "sonarr" && a.enabled)
|
|
765
|
-
if (sonarrConfig && this.env["API_KEY_SONARR"]) {
|
|
766
|
-
try {
|
|
767
|
-
const sonarrDef = getApp("sonarr")
|
|
768
|
-
const sonarrPort = sonarrConfig.port || sonarrDef?.defaultPort || 8989
|
|
769
|
-
const sonarrExternalUrl = getApplicationUrl("sonarr", sonarrPort, this.config)
|
|
770
|
-
await client.configureSonarr(
|
|
771
|
-
"sonarr",
|
|
772
|
-
sonarrPort,
|
|
773
|
-
this.env["API_KEY_SONARR"],
|
|
774
|
-
sonarrDef?.rootFolder?.path || "/data/media/tv",
|
|
775
|
-
sonarrExternalUrl
|
|
776
|
-
)
|
|
777
|
-
debugLog("FullAutoSetup", `Jellyseerr: Sonarr externalUrl set to ${sonarrExternalUrl}`)
|
|
778
|
-
} catch {
|
|
779
|
-
/* Sonarr config failed */
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// Set Jellyfin's externalHostname for navigation links
|
|
784
|
-
const jellyfinConfig = this.config.apps.find((a) => a.id === "jellyfin" && a.enabled)
|
|
785
|
-
if (jellyfinConfig) {
|
|
786
|
-
try {
|
|
787
|
-
const jellyfinPort = jellyfinConfig.port || 8096
|
|
788
|
-
const jellyfinUrl = getApplicationUrl("jellyfin", jellyfinPort, this.config)
|
|
789
|
-
await client.updateJellyfinSettings({ externalHostname: jellyfinUrl })
|
|
790
|
-
debugLog("FullAutoSetup", `Jellyseerr: Jellyfin externalHostname set to ${jellyfinUrl}`)
|
|
791
|
-
} catch {
|
|
792
|
-
debugLog("FullAutoSetup", "Failed to set Jellyfin externalHostname in Jellyseerr")
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Set Jellyseerr's own applicationUrl (we're already authenticated from setup)
|
|
797
|
-
try {
|
|
798
|
-
const jellyseerrUrl = getApplicationUrl("jellyseerr", port, this.config)
|
|
799
|
-
await client.setApplicationUrl(jellyseerrUrl)
|
|
800
|
-
debugLog("FullAutoSetup", `Jellyseerr: applicationUrl set to ${jellyseerrUrl}`)
|
|
801
|
-
} catch {
|
|
802
|
-
debugLog("FullAutoSetup", "Failed to set Jellyseerr applicationUrl")
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
this.updateStep("Jellyseerr", "success", result.message)
|
|
806
|
-
} else {
|
|
807
|
-
this.updateStep("Jellyseerr", "skipped", result.message)
|
|
808
|
-
}
|
|
809
|
-
} catch (e) {
|
|
810
|
-
this.updateStep("Jellyseerr", "error", `${e}`)
|
|
811
|
-
}
|
|
812
|
-
this.refreshContent()
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
private async setupPlex(): Promise<void> {
|
|
816
|
-
this.updateStep("Plex", "running")
|
|
817
|
-
this.refreshContent()
|
|
818
|
-
|
|
819
|
-
const plexConfig = this.config.apps.find((a) => a.id === "plex" && a.enabled)
|
|
820
|
-
if (!plexConfig) {
|
|
821
|
-
this.updateStep("Plex", "skipped", "Not enabled")
|
|
822
|
-
this.refreshContent()
|
|
823
|
-
return
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
try {
|
|
827
|
-
const port = plexConfig.port || 32400
|
|
828
|
-
const client = new PlexApiClient("localhost", port)
|
|
829
|
-
|
|
830
|
-
// Check if reachable
|
|
831
|
-
const healthy = await client.isHealthy()
|
|
832
|
-
if (!healthy) {
|
|
833
|
-
this.updateStep("Plex", "skipped", "Not reachable yet")
|
|
834
|
-
this.refreshContent()
|
|
835
|
-
return
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Run auto-setup - pass enabled apps so Plex knows which libraries to create
|
|
839
|
-
const enabledApps = this.config.apps.filter((a) => a.enabled).map((a) => a.id)
|
|
840
|
-
const result = await client.setup({
|
|
841
|
-
username: this.globalUsername,
|
|
842
|
-
password: this.globalPassword,
|
|
843
|
-
env: this.env,
|
|
844
|
-
enabledApps,
|
|
845
|
-
})
|
|
846
|
-
|
|
847
|
-
if (result.success) {
|
|
848
|
-
this.updateStep("Plex", "success", result.message)
|
|
849
|
-
} else {
|
|
850
|
-
this.updateStep("Plex", "skipped", result.message)
|
|
851
|
-
}
|
|
852
|
-
} catch (e) {
|
|
853
|
-
this.updateStep("Plex", "error", `${e}`)
|
|
854
|
-
}
|
|
855
|
-
this.refreshContent()
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
private async setupUptimeKuma(): Promise<void> {
|
|
859
|
-
this.updateStep("Uptime Kuma", "running")
|
|
860
|
-
this.refreshContent()
|
|
861
|
-
|
|
862
|
-
const uptimeKumaConfig = this.config.apps.find((a) => a.id === "uptime-kuma" && a.enabled)
|
|
863
|
-
if (!uptimeKumaConfig) {
|
|
864
|
-
this.updateStep("Uptime Kuma", "skipped", "Not enabled")
|
|
865
|
-
this.refreshContent()
|
|
866
|
-
return
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
try {
|
|
870
|
-
const port = uptimeKumaConfig.port || 3001
|
|
871
|
-
const client = new UptimeKumaClient("localhost", port)
|
|
872
|
-
|
|
873
|
-
// Check if reachable
|
|
874
|
-
const healthy = await client.isHealthy()
|
|
875
|
-
if (!healthy) {
|
|
876
|
-
this.updateStep("Uptime Kuma", "skipped", "Not reachable yet")
|
|
877
|
-
this.refreshContent()
|
|
878
|
-
return
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// Run auto-setup (creates admin or logs in)
|
|
882
|
-
const result = await client.setup({
|
|
883
|
-
username: this.globalUsername,
|
|
884
|
-
password: this.globalPassword,
|
|
885
|
-
env: this.env,
|
|
886
|
-
})
|
|
887
|
-
|
|
888
|
-
if (result.success) {
|
|
889
|
-
// Now add monitors for enabled apps
|
|
890
|
-
try {
|
|
891
|
-
// Re-login since setup disconnects
|
|
892
|
-
const loggedIn = await client.login(this.globalUsername, this.globalPassword)
|
|
893
|
-
if (loggedIn) {
|
|
894
|
-
const addedCount = await client.setupEasiarrMonitors(this.config.apps)
|
|
895
|
-
client.disconnect()
|
|
896
|
-
this.updateStep("Uptime Kuma", "success", `${result.message}, ${addedCount} monitors added`)
|
|
897
|
-
} else {
|
|
898
|
-
client.disconnect()
|
|
899
|
-
this.updateStep("Uptime Kuma", "success", result.message)
|
|
900
|
-
}
|
|
901
|
-
} catch {
|
|
902
|
-
client.disconnect()
|
|
903
|
-
this.updateStep("Uptime Kuma", "success", result.message)
|
|
904
|
-
}
|
|
905
|
-
} else {
|
|
906
|
-
this.updateStep("Uptime Kuma", "skipped", result.message)
|
|
907
|
-
}
|
|
908
|
-
} catch (e) {
|
|
909
|
-
this.updateStep("Uptime Kuma", "error", `${e}`)
|
|
910
|
-
}
|
|
911
|
-
this.refreshContent()
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
private async setupGrafana(): Promise<void> {
|
|
915
|
-
this.updateStep("Grafana", "running")
|
|
916
|
-
this.refreshContent()
|
|
917
|
-
|
|
918
|
-
const grafanaConfig = this.config.apps.find((a) => a.id === "grafana" && a.enabled)
|
|
919
|
-
if (!grafanaConfig) {
|
|
920
|
-
this.updateStep("Grafana", "skipped", "Not enabled")
|
|
921
|
-
this.refreshContent()
|
|
922
|
-
return
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
try {
|
|
926
|
-
const port = grafanaConfig.port || 3001
|
|
927
|
-
const client = new GrafanaClient("localhost", port)
|
|
928
|
-
|
|
929
|
-
// Check if reachable
|
|
930
|
-
const healthy = await client.isHealthy()
|
|
931
|
-
if (!healthy) {
|
|
932
|
-
this.updateStep("Grafana", "skipped", "Not reachable yet")
|
|
933
|
-
this.refreshContent()
|
|
934
|
-
return
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// Run auto-setup
|
|
938
|
-
const result = await client.setup({
|
|
939
|
-
username: this.globalUsername,
|
|
940
|
-
password: this.globalPassword,
|
|
941
|
-
env: this.env,
|
|
942
|
-
})
|
|
943
|
-
|
|
944
|
-
if (result.success) {
|
|
945
|
-
// Save any env updates (e.g., API key)
|
|
946
|
-
if (result.envUpdates) {
|
|
947
|
-
await updateEnv(result.envUpdates)
|
|
948
|
-
// Update local env cache
|
|
949
|
-
Object.assign(this.env, result.envUpdates)
|
|
950
|
-
}
|
|
951
|
-
this.updateStep("Grafana", "success", result.message)
|
|
952
|
-
} else {
|
|
953
|
-
this.updateStep("Grafana", "skipped", result.message)
|
|
954
|
-
}
|
|
955
|
-
} catch (e) {
|
|
956
|
-
this.updateStep("Grafana", "error", `${e}`)
|
|
957
|
-
}
|
|
958
|
-
this.refreshContent()
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
private async setupCloudflare(): Promise<void> {
|
|
962
|
-
this.updateStep("Cloudflare Tunnel", "running")
|
|
963
|
-
this.refreshContent()
|
|
964
|
-
|
|
965
|
-
const cloudflaredConfig = this.config.apps.find((a) => a.id === "cloudflared" && a.enabled)
|
|
966
|
-
if (!cloudflaredConfig) {
|
|
967
|
-
this.updateStep("Cloudflare Tunnel", "skipped", "Not enabled")
|
|
968
|
-
this.refreshContent()
|
|
969
|
-
return
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
const apiToken = this.env["CLOUDFLARE_API_TOKEN"]
|
|
973
|
-
if (!apiToken) {
|
|
974
|
-
this.updateStep("Cloudflare Tunnel", "skipped", "No CLOUDFLARE_API_TOKEN in .env")
|
|
975
|
-
this.refreshContent()
|
|
976
|
-
return
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
const domain = this.env["CLOUDFLARE_DNS_ZONE"] || this.config.traefik?.domain
|
|
980
|
-
if (!domain) {
|
|
981
|
-
this.updateStep("Cloudflare Tunnel", "skipped", "No domain configured")
|
|
982
|
-
this.refreshContent()
|
|
983
|
-
return
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
try {
|
|
987
|
-
// Create/update tunnel
|
|
988
|
-
const result = await setupCloudflaredTunnel(apiToken, domain, "easiarr")
|
|
989
|
-
|
|
990
|
-
// Save tunnel token and IDs to .env (IDs needed for Homepage widget)
|
|
991
|
-
await updateEnv({
|
|
992
|
-
CLOUDFLARE_TUNNEL_TOKEN: result.tunnelToken,
|
|
993
|
-
CLOUDFLARE_TUNNEL_ID: result.tunnelId,
|
|
994
|
-
CLOUDFLARE_ACCOUNT_ID: result.accountId,
|
|
995
|
-
CLOUDFLARE_DNS_ZONE: domain,
|
|
996
|
-
})
|
|
997
|
-
Object.assign(this.env, {
|
|
998
|
-
CLOUDFLARE_TUNNEL_ID: result.tunnelId,
|
|
999
|
-
CLOUDFLARE_ACCOUNT_ID: result.accountId,
|
|
1000
|
-
})
|
|
1001
|
-
|
|
1002
|
-
// Update config
|
|
1003
|
-
if (this.config.traefik) {
|
|
1004
|
-
this.config.traefik.domain = domain
|
|
1005
|
-
this.config.traefik.entrypoint = "web"
|
|
1006
|
-
}
|
|
1007
|
-
this.config.updatedAt = new Date().toISOString()
|
|
1008
|
-
await saveConfig(this.config)
|
|
1009
|
-
await saveCompose(this.config)
|
|
1010
|
-
|
|
1011
|
-
// Optional: Set up Cloudflare Access if email is available
|
|
1012
|
-
// Check CLOUDFLARE_ACCESS_EMAIL first, then fall back to EMAIL_GLOBAL
|
|
1013
|
-
const accessEmail = this.env["CLOUDFLARE_ACCESS_EMAIL"] || this.env["EMAIL_GLOBAL"]
|
|
1014
|
-
if (accessEmail) {
|
|
1015
|
-
try {
|
|
1016
|
-
const api = new CloudflareApi(apiToken)
|
|
1017
|
-
await api.setupAccessProtection(domain, [accessEmail], "easiarr")
|
|
1018
|
-
this.updateStep("Cloudflare Tunnel", "success", `Tunnel + Access for ${accessEmail}`)
|
|
1019
|
-
} catch {
|
|
1020
|
-
// Access setup failed, but tunnel is still working
|
|
1021
|
-
this.updateStep("Cloudflare Tunnel", "success", "Tunnel created (Access failed)")
|
|
1022
|
-
}
|
|
1023
|
-
} else {
|
|
1024
|
-
this.updateStep("Cloudflare Tunnel", "success", "Tunnel created")
|
|
1025
|
-
}
|
|
1026
|
-
} catch (e) {
|
|
1027
|
-
this.updateStep("Cloudflare Tunnel", "error", `${e}`)
|
|
1028
|
-
}
|
|
1029
|
-
this.refreshContent()
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
private async setupOverseerr(): Promise<void> {
|
|
1033
|
-
this.updateStep("Overseerr", "running")
|
|
1034
|
-
this.refreshContent()
|
|
1035
|
-
|
|
1036
|
-
const overseerrConfig = this.config.apps.find((a) => a.id === "overseerr" && a.enabled)
|
|
1037
|
-
if (!overseerrConfig) {
|
|
1038
|
-
this.updateStep("Overseerr", "skipped", "Not enabled")
|
|
1039
|
-
this.refreshContent()
|
|
1040
|
-
return
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
// Overseerr requires Plex
|
|
1044
|
-
const plexConfig = this.config.apps.find((a) => a.id === "plex" && a.enabled)
|
|
1045
|
-
if (!plexConfig) {
|
|
1046
|
-
this.updateStep("Overseerr", "skipped", "Plex not enabled")
|
|
1047
|
-
this.refreshContent()
|
|
1048
|
-
return
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
const plexToken = this.env["API_KEY_PLEX"]
|
|
1052
|
-
if (!plexToken) {
|
|
1053
|
-
this.updateStep("Overseerr", "skipped", "No API_KEY_PLEX in .env")
|
|
1054
|
-
this.refreshContent()
|
|
1055
|
-
return
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
try {
|
|
1059
|
-
const port = overseerrConfig.port || 5055
|
|
1060
|
-
const client = new OverseerrClient("localhost", port)
|
|
1061
|
-
|
|
1062
|
-
const result = await client.setup({
|
|
1063
|
-
username: this.globalUsername,
|
|
1064
|
-
password: this.globalPassword,
|
|
1065
|
-
env: this.env,
|
|
1066
|
-
plexToken,
|
|
1067
|
-
})
|
|
1068
|
-
|
|
1069
|
-
if (result.success) {
|
|
1070
|
-
if (result.envUpdates) {
|
|
1071
|
-
await updateEnv(result.envUpdates)
|
|
1072
|
-
Object.assign(this.env, result.envUpdates)
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
// Set Overseerr's applicationUrl (we're already authenticated from setup)
|
|
1076
|
-
try {
|
|
1077
|
-
const overseerrUrl = getApplicationUrl("overseerr", port, this.config)
|
|
1078
|
-
await client.setApplicationUrl(overseerrUrl)
|
|
1079
|
-
debugLog("FullAutoSetup", `Overseerr: applicationUrl set to ${overseerrUrl}`)
|
|
1080
|
-
} catch {
|
|
1081
|
-
debugLog("FullAutoSetup", "Failed to set Overseerr applicationUrl")
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
this.updateStep("Overseerr", "success", result.message)
|
|
1085
|
-
} else {
|
|
1086
|
-
this.updateStep("Overseerr", "skipped", result.message)
|
|
1087
|
-
}
|
|
1088
|
-
} catch (e) {
|
|
1089
|
-
this.updateStep("Overseerr", "error", `${e}`)
|
|
1090
|
-
}
|
|
1091
|
-
this.refreshContent()
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
private async setupTautulli(): Promise<void> {
|
|
1095
|
-
this.updateStep("Tautulli", "running")
|
|
1096
|
-
this.refreshContent()
|
|
1097
|
-
|
|
1098
|
-
const tautulliConfig = this.config.apps.find((a) => a.id === "tautulli" && a.enabled)
|
|
1099
|
-
if (!tautulliConfig) {
|
|
1100
|
-
this.updateStep("Tautulli", "skipped", "Not enabled")
|
|
1101
|
-
this.refreshContent()
|
|
1102
|
-
return
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
try {
|
|
1106
|
-
const port = tautulliConfig.port || 8181
|
|
1107
|
-
const client = new TautulliClient("localhost", port)
|
|
1108
|
-
|
|
1109
|
-
const result = await client.setup({
|
|
1110
|
-
username: this.globalUsername,
|
|
1111
|
-
password: this.globalPassword,
|
|
1112
|
-
env: this.env,
|
|
1113
|
-
})
|
|
1114
|
-
|
|
1115
|
-
if (result.success) {
|
|
1116
|
-
if (result.envUpdates) {
|
|
1117
|
-
await updateEnv(result.envUpdates)
|
|
1118
|
-
Object.assign(this.env, result.envUpdates)
|
|
1119
|
-
}
|
|
1120
|
-
// Check if wizard still needed
|
|
1121
|
-
const requiresWizard = result.data?.requiresWizard
|
|
1122
|
-
const msg = requiresWizard ? `${result.message} (manual Plex setup needed)` : result.message
|
|
1123
|
-
this.updateStep("Tautulli", "success", msg)
|
|
1124
|
-
} else {
|
|
1125
|
-
this.updateStep("Tautulli", "skipped", result.message)
|
|
1126
|
-
}
|
|
1127
|
-
} catch (e) {
|
|
1128
|
-
this.updateStep("Tautulli", "error", `${e}`)
|
|
1129
|
-
}
|
|
1130
|
-
this.refreshContent()
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
private async setupMaintainerr(): Promise<void> {
|
|
1134
|
-
this.updateStep("Maintainerr", "running")
|
|
1135
|
-
this.refreshContent()
|
|
1136
|
-
|
|
1137
|
-
const maintainerrConfig = this.config.apps.find((a) => a.id === "maintainerr" && a.enabled)
|
|
1138
|
-
if (!maintainerrConfig) {
|
|
1139
|
-
this.updateStep("Maintainerr", "skipped", "Not enabled")
|
|
1140
|
-
this.refreshContent()
|
|
1141
|
-
return
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
// Requires Plex
|
|
1145
|
-
const plexConfig = this.config.apps.find((a) => a.id === "plex" && a.enabled)
|
|
1146
|
-
if (!plexConfig) {
|
|
1147
|
-
this.updateStep("Maintainerr", "skipped", "Plex not enabled")
|
|
1148
|
-
this.refreshContent()
|
|
1149
|
-
return
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
try {
|
|
1153
|
-
const port = maintainerrConfig.port || 6246
|
|
1154
|
-
const client = new MaintainerrClient("localhost", port)
|
|
1155
|
-
|
|
1156
|
-
const result = await client.setup({
|
|
1157
|
-
username: this.globalUsername,
|
|
1158
|
-
password: this.globalPassword,
|
|
1159
|
-
env: this.env,
|
|
1160
|
-
plexToken: this.env["API_KEY_PLEX"],
|
|
1161
|
-
})
|
|
1162
|
-
|
|
1163
|
-
if (result.success) {
|
|
1164
|
-
if (result.envUpdates) {
|
|
1165
|
-
await updateEnv(result.envUpdates)
|
|
1166
|
-
Object.assign(this.env, result.envUpdates)
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// Configure Radarr connection if enabled
|
|
1170
|
-
// Use container name since Maintainerr runs in Docker
|
|
1171
|
-
const radarrConfig = this.config.apps.find((a) => a.id === "radarr" && a.enabled)
|
|
1172
|
-
if (radarrConfig && this.env["API_KEY_RADARR"]) {
|
|
1173
|
-
try {
|
|
1174
|
-
const radarrDef = getApp("radarr")
|
|
1175
|
-
const radarrPort = radarrConfig.port || radarrDef?.defaultPort || 7878
|
|
1176
|
-
await client.configureRadarr("radarr", radarrPort, this.env["API_KEY_RADARR"])
|
|
1177
|
-
debugLog("FullAutoSetup", "Maintainerr -> Radarr connection configured")
|
|
1178
|
-
} catch {
|
|
1179
|
-
debugLog("FullAutoSetup", "Failed to configure Maintainerr -> Radarr connection")
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
// Configure Sonarr connection if enabled
|
|
1184
|
-
const sonarrConfig = this.config.apps.find((a) => a.id === "sonarr" && a.enabled)
|
|
1185
|
-
if (sonarrConfig && this.env["API_KEY_SONARR"]) {
|
|
1186
|
-
try {
|
|
1187
|
-
const sonarrDef = getApp("sonarr")
|
|
1188
|
-
const sonarrPort = sonarrConfig.port || sonarrDef?.defaultPort || 8989
|
|
1189
|
-
await client.configureSonarr("sonarr", sonarrPort, this.env["API_KEY_SONARR"])
|
|
1190
|
-
debugLog("FullAutoSetup", "Maintainerr -> Sonarr connection configured")
|
|
1191
|
-
} catch {
|
|
1192
|
-
debugLog("FullAutoSetup", "Failed to configure Maintainerr -> Sonarr connection")
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
// Configure Overseerr connection if enabled
|
|
1197
|
-
const overseerrConfig = this.config.apps.find((a) => a.id === "overseerr" && a.enabled)
|
|
1198
|
-
if (overseerrConfig && this.env["API_KEY_OVERSEERR"]) {
|
|
1199
|
-
try {
|
|
1200
|
-
const overseerrDef = getApp("overseerr")
|
|
1201
|
-
const overseerrPort = overseerrConfig.port || overseerrDef?.defaultPort || 5055
|
|
1202
|
-
await client.configureOverseerr("overseerr", overseerrPort, this.env["API_KEY_OVERSEERR"])
|
|
1203
|
-
debugLog("FullAutoSetup", "Maintainerr -> Overseerr connection configured")
|
|
1204
|
-
} catch {
|
|
1205
|
-
debugLog("FullAutoSetup", "Failed to configure Maintainerr -> Overseerr connection")
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
// Configure Tautulli connection if enabled
|
|
1210
|
-
const tautulliConfig = this.config.apps.find((a) => a.id === "tautulli" && a.enabled)
|
|
1211
|
-
if (tautulliConfig && this.env["API_KEY_TAUTULLI"]) {
|
|
1212
|
-
try {
|
|
1213
|
-
const tautulliDef = getApp("tautulli")
|
|
1214
|
-
const tautulliPort = tautulliConfig.port || tautulliDef?.defaultPort || 8181
|
|
1215
|
-
await client.configureTautulli("tautulli", tautulliPort, this.env["API_KEY_TAUTULLI"])
|
|
1216
|
-
debugLog("FullAutoSetup", "Maintainerr -> Tautulli connection configured")
|
|
1217
|
-
} catch {
|
|
1218
|
-
debugLog("FullAutoSetup", "Failed to configure Maintainerr -> Tautulli connection")
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
const requiresWizard = result.data?.requiresWizard
|
|
1223
|
-
const msg = requiresWizard ? `${result.message} (manual Plex setup needed)` : result.message
|
|
1224
|
-
this.updateStep("Maintainerr", "success", msg)
|
|
1225
|
-
} else {
|
|
1226
|
-
this.updateStep("Maintainerr", "skipped", result.message)
|
|
1227
|
-
}
|
|
1228
|
-
} catch (e) {
|
|
1229
|
-
this.updateStep("Maintainerr", "error", `${e}`)
|
|
1230
|
-
}
|
|
1231
|
-
this.refreshContent()
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
private async setupBazarr(): Promise<void> {
|
|
1235
|
-
this.updateStep("Bazarr", "running")
|
|
1236
|
-
this.refreshContent()
|
|
1237
|
-
|
|
1238
|
-
const bazarrConfig = this.config.apps.find((a) => a.id === "bazarr" && a.enabled)
|
|
1239
|
-
if (!bazarrConfig) {
|
|
1240
|
-
this.updateStep("Bazarr", "skipped", "Not enabled")
|
|
1241
|
-
this.refreshContent()
|
|
1242
|
-
return
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
try {
|
|
1246
|
-
const port = bazarrConfig.port || 6767
|
|
1247
|
-
const client = new BazarrApiClient("localhost", port)
|
|
1248
|
-
|
|
1249
|
-
// Get and set API key if available
|
|
1250
|
-
const existingApiKey = this.env["API_KEY_BAZARR"]
|
|
1251
|
-
if (existingApiKey) {
|
|
1252
|
-
client.setApiKey(existingApiKey)
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
const result = await client.setup({
|
|
1256
|
-
username: this.globalUsername,
|
|
1257
|
-
password: this.globalPassword,
|
|
1258
|
-
env: this.env,
|
|
1259
|
-
})
|
|
1260
|
-
|
|
1261
|
-
if (result.success) {
|
|
1262
|
-
if (result.envUpdates) {
|
|
1263
|
-
await updateEnv(result.envUpdates)
|
|
1264
|
-
Object.assign(this.env, result.envUpdates)
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
// Configure Radarr/Sonarr connections
|
|
1268
|
-
let configured = 0
|
|
1269
|
-
const radarrConfig = this.config.apps.find((a) => a.id === "radarr" && a.enabled)
|
|
1270
|
-
if (radarrConfig && this.env["API_KEY_RADARR"]) {
|
|
1271
|
-
try {
|
|
1272
|
-
await client.configureRadarr("radarr", radarrConfig.port || 7878, this.env["API_KEY_RADARR"])
|
|
1273
|
-
configured++
|
|
1274
|
-
} catch {
|
|
1275
|
-
/* connection failed */
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
const sonarrConfig = this.config.apps.find((a) => a.id === "sonarr" && a.enabled)
|
|
1280
|
-
if (sonarrConfig && this.env["API_KEY_SONARR"]) {
|
|
1281
|
-
try {
|
|
1282
|
-
await client.configureSonarr("sonarr", sonarrConfig.port || 8989, this.env["API_KEY_SONARR"])
|
|
1283
|
-
configured++
|
|
1284
|
-
} catch {
|
|
1285
|
-
/* connection failed */
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
this.updateStep("Bazarr", "success", configured > 0 ? `${configured} apps connected` : result.message)
|
|
1290
|
-
} else {
|
|
1291
|
-
this.updateStep("Bazarr", "skipped", result.message)
|
|
1292
|
-
}
|
|
1293
|
-
} catch (e) {
|
|
1294
|
-
this.updateStep("Bazarr", "error", `${e}`)
|
|
1295
|
-
}
|
|
1296
|
-
this.refreshContent()
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
private async setupHomarr(): Promise<void> {
|
|
1300
|
-
this.updateStep("Homarr", "running")
|
|
1301
|
-
this.refreshContent()
|
|
1302
|
-
|
|
1303
|
-
const homarrConfig = this.config.apps.find((a) => a.id === "homarr" && a.enabled)
|
|
1304
|
-
if (!homarrConfig) {
|
|
1305
|
-
this.updateStep("Homarr", "skipped", "Not enabled")
|
|
1306
|
-
this.refreshContent()
|
|
1307
|
-
return
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
try {
|
|
1311
|
-
const port = homarrConfig.port || 7575
|
|
1312
|
-
const client = new HomarrClient("localhost", port)
|
|
1313
|
-
|
|
1314
|
-
const result = await client.setup({
|
|
1315
|
-
username: this.globalUsername,
|
|
1316
|
-
password: this.globalPassword,
|
|
1317
|
-
env: this.env,
|
|
1318
|
-
})
|
|
1319
|
-
|
|
1320
|
-
if (result.success) {
|
|
1321
|
-
// Add enabled apps to Homarr dashboard
|
|
1322
|
-
try {
|
|
1323
|
-
const addedCount = await client.setupEasiarrApps(this.config.apps)
|
|
1324
|
-
this.updateStep("Homarr", "success", `${result.message}, ${addedCount} apps added`)
|
|
1325
|
-
} catch {
|
|
1326
|
-
this.updateStep("Homarr", "success", result.message)
|
|
1327
|
-
}
|
|
1328
|
-
} else {
|
|
1329
|
-
this.updateStep("Homarr", "skipped", result.message)
|
|
1330
|
-
}
|
|
1331
|
-
} catch (e) {
|
|
1332
|
-
this.updateStep("Homarr", "error", `${e}`)
|
|
1333
|
-
}
|
|
1334
|
-
this.refreshContent()
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
private async setupHuntarr(): Promise<void> {
|
|
1338
|
-
this.updateStep("Huntarr", "running")
|
|
1339
|
-
this.refreshContent()
|
|
1340
|
-
|
|
1341
|
-
const huntarrConfig = this.config.apps.find((a) => a.id === "huntarr" && a.enabled)
|
|
1342
|
-
if (!huntarrConfig) {
|
|
1343
|
-
this.updateStep("Huntarr", "skipped", "Not enabled")
|
|
1344
|
-
this.refreshContent()
|
|
1345
|
-
return
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
try {
|
|
1349
|
-
const port = huntarrConfig.port || 9705
|
|
1350
|
-
const client = new HuntarrClient("localhost", port)
|
|
1351
|
-
|
|
1352
|
-
// Check if reachable
|
|
1353
|
-
const healthy = await client.isHealthy()
|
|
1354
|
-
if (!healthy) {
|
|
1355
|
-
this.updateStep("Huntarr", "skipped", "Not reachable yet")
|
|
1356
|
-
this.refreshContent()
|
|
1357
|
-
return
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
// Authenticate (creates user if needed, otherwise logs in)
|
|
1361
|
-
const authenticated = await client.authenticate(this.globalUsername, this.globalPassword)
|
|
1362
|
-
if (!authenticated) {
|
|
1363
|
-
this.updateStep("Huntarr", "skipped", "Auth failed")
|
|
1364
|
-
this.refreshContent()
|
|
1365
|
-
return
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
// Add enabled *arr apps to Huntarr
|
|
1369
|
-
try {
|
|
1370
|
-
const result = await client.setupEasiarrApps(this.config.apps, this.env)
|
|
1371
|
-
this.updateStep("Huntarr", "success", `${result.added} *arr apps added`)
|
|
1372
|
-
} catch {
|
|
1373
|
-
this.updateStep("Huntarr", "success", "Ready")
|
|
1374
|
-
}
|
|
1375
|
-
} catch (e) {
|
|
1376
|
-
this.updateStep("Huntarr", "error", `${e}`)
|
|
1377
|
-
}
|
|
1378
|
-
this.refreshContent()
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
private async setupHeimdall(): Promise<void> {
|
|
1382
|
-
this.updateStep("Heimdall", "running")
|
|
1383
|
-
this.refreshContent()
|
|
1384
|
-
|
|
1385
|
-
const heimdallConfig = this.config.apps.find((a) => a.id === "heimdall" && a.enabled)
|
|
1386
|
-
if (!heimdallConfig) {
|
|
1387
|
-
this.updateStep("Heimdall", "skipped", "Not enabled")
|
|
1388
|
-
this.refreshContent()
|
|
1389
|
-
return
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
try {
|
|
1393
|
-
const port = heimdallConfig.port || 8090
|
|
1394
|
-
const client = new HeimdallClient("localhost", port)
|
|
1395
|
-
|
|
1396
|
-
const result = await client.setup({
|
|
1397
|
-
username: this.globalUsername,
|
|
1398
|
-
password: this.globalPassword,
|
|
1399
|
-
env: this.env,
|
|
1400
|
-
})
|
|
1401
|
-
|
|
1402
|
-
if (result.success) {
|
|
1403
|
-
// Add enabled apps to Heimdall dashboard
|
|
1404
|
-
try {
|
|
1405
|
-
const addedCount = await client.setupEasiarrApps(this.config.apps)
|
|
1406
|
-
this.updateStep("Heimdall", "success", `${result.message}, ${addedCount} apps added`)
|
|
1407
|
-
} catch {
|
|
1408
|
-
this.updateStep("Heimdall", "success", result.message)
|
|
1409
|
-
}
|
|
1410
|
-
} else {
|
|
1411
|
-
this.updateStep("Heimdall", "skipped", result.message)
|
|
1412
|
-
}
|
|
1413
|
-
} catch (e) {
|
|
1414
|
-
this.updateStep("Heimdall", "error", `${e}`)
|
|
1415
|
-
}
|
|
1416
|
-
this.refreshContent()
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
private async setupSlskd(): Promise<void> {
|
|
1420
|
-
this.updateStep("Slskd", "running")
|
|
1421
|
-
this.refreshContent()
|
|
1422
|
-
|
|
1423
|
-
const slskdConfig = this.config.apps.find((a) => a.id === "slskd" && a.enabled)
|
|
1424
|
-
if (!slskdConfig) {
|
|
1425
|
-
this.updateStep("Slskd", "skipped", "Not enabled")
|
|
1426
|
-
this.refreshContent()
|
|
1427
|
-
return
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
try {
|
|
1431
|
-
// Generate slskd.yml config with auto-generated API key
|
|
1432
|
-
const { yaml, apiKey } = generateSlskdConfig(this.config)
|
|
1433
|
-
const configPath = getSlskdConfigPath(this.config.rootDir)
|
|
1434
|
-
|
|
1435
|
-
// Ensure config directory exists
|
|
1436
|
-
const configDir = dirname(configPath)
|
|
1437
|
-
if (!existsSync(configDir)) {
|
|
1438
|
-
await mkdir(configDir, { recursive: true })
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
// Create slskd download directories (like qBittorrent categories)
|
|
1442
|
-
const slskdDownloadsDir = `${this.config.rootDir}/data/slskd_downloads`
|
|
1443
|
-
await mkdir(`${slskdDownloadsDir}/incomplete`, { recursive: true })
|
|
1444
|
-
await mkdir(`${slskdDownloadsDir}/complete`, { recursive: true })
|
|
1445
|
-
debugLog("FullAutoSetup", `Created slskd download directories at ${slskdDownloadsDir}`)
|
|
1446
|
-
|
|
1447
|
-
// Always write slskd.yml - Docker creates a commented-out example that we need to replace
|
|
1448
|
-
await writeFile(configPath, yaml)
|
|
1449
|
-
debugLog("FullAutoSetup", `Generated slskd.yml at ${configPath}`)
|
|
1450
|
-
|
|
1451
|
-
// Save API key to .env for Homepage widget and Soularr
|
|
1452
|
-
await updateEnv({ API_KEY_SLSKD: apiKey })
|
|
1453
|
-
this.env["API_KEY_SLSKD"] = apiKey
|
|
1454
|
-
|
|
1455
|
-
this.updateStep("Slskd", "success", "Config generated, API key saved")
|
|
1456
|
-
} catch (e) {
|
|
1457
|
-
this.updateStep("Slskd", "error", `${e}`)
|
|
1458
|
-
}
|
|
1459
|
-
this.refreshContent()
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
private async setupSoularr(): Promise<void> {
|
|
1463
|
-
this.updateStep("Soularr", "running")
|
|
1464
|
-
this.refreshContent()
|
|
1465
|
-
|
|
1466
|
-
const soularrConfig = this.config.apps.find((a) => a.id === "soularr" && a.enabled)
|
|
1467
|
-
if (!soularrConfig) {
|
|
1468
|
-
this.updateStep("Soularr", "skipped", "Not enabled")
|
|
1469
|
-
this.refreshContent()
|
|
1470
|
-
return
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
// Check dependencies
|
|
1474
|
-
const lidarrConfig = this.config.apps.find((a) => a.id === "lidarr" && a.enabled)
|
|
1475
|
-
const slskdConfig = this.config.apps.find((a) => a.id === "slskd" && a.enabled)
|
|
1476
|
-
|
|
1477
|
-
if (!lidarrConfig || !slskdConfig) {
|
|
1478
|
-
this.updateStep("Soularr", "skipped", "Requires Lidarr & Slskd")
|
|
1479
|
-
this.refreshContent()
|
|
1480
|
-
return
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
try {
|
|
1484
|
-
// Generate soularr config.ini
|
|
1485
|
-
const configContent = generateSoularrConfig(this.config)
|
|
1486
|
-
const configPath = getSoularrConfigPath(this.config.rootDir)
|
|
1487
|
-
|
|
1488
|
-
// Ensure directory exists
|
|
1489
|
-
const configDir = dirname(configPath)
|
|
1490
|
-
if (!existsSync(configDir)) {
|
|
1491
|
-
await mkdir(configDir, { recursive: true })
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
// Write config file
|
|
1495
|
-
await writeFile(configPath, configContent)
|
|
1496
|
-
debugLog("FullAutoSetup", `Generated soularr config at ${configPath}`)
|
|
1497
|
-
|
|
1498
|
-
this.updateStep("Soularr", "success", "Config generated")
|
|
1499
|
-
} catch (e) {
|
|
1500
|
-
this.updateStep("Soularr", "error", `${e}`)
|
|
1501
|
-
}
|
|
1502
|
-
this.refreshContent()
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
private async setupRecyclarr(): Promise<void> {
|
|
1506
|
-
this.updateStep("Recyclarr", "running")
|
|
1507
|
-
this.refreshContent()
|
|
1508
|
-
|
|
1509
|
-
const recyclarrConfig = this.config.apps.find((a) => a.id === "recyclarr" && a.enabled)
|
|
1510
|
-
if (!recyclarrConfig) {
|
|
1511
|
-
this.updateStep("Recyclarr", "skipped", "Not enabled")
|
|
1512
|
-
this.refreshContent()
|
|
1513
|
-
return
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
// Check if we have at least one *arr app with API key
|
|
1517
|
-
const radarrApiKey = this.env["API_KEY_RADARR"]
|
|
1518
|
-
const sonarrApiKey = this.env["API_KEY_SONARR"]
|
|
1519
|
-
|
|
1520
|
-
if (!radarrApiKey && !sonarrApiKey) {
|
|
1521
|
-
this.updateStep("Recyclarr", "skipped", "No Radarr/Sonarr API keys")
|
|
1522
|
-
this.refreshContent()
|
|
1523
|
-
return
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
try {
|
|
1527
|
-
const configPath = await saveRecyclarrConfig(this.config)
|
|
1528
|
-
debugLog("FullAutoSetup", `Generated recyclarr.yml at ${configPath}`)
|
|
1529
|
-
this.updateStep("Recyclarr", "success", "Config generated")
|
|
1530
|
-
} catch (e) {
|
|
1531
|
-
this.updateStep("Recyclarr", "error", `${e}`)
|
|
1532
|
-
}
|
|
1533
|
-
this.refreshContent()
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
private async setupProfilarr(): Promise<void> {
|
|
1537
|
-
this.updateStep("Profilarr", "running")
|
|
1538
|
-
this.refreshContent()
|
|
1539
|
-
|
|
1540
|
-
const profilarrConfig = this.config.apps.find((a) => a.id === "profilarr" && a.enabled)
|
|
1541
|
-
if (!profilarrConfig) {
|
|
1542
|
-
this.updateStep("Profilarr", "skipped", "Not enabled")
|
|
1543
|
-
this.refreshContent()
|
|
1544
|
-
return
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
try {
|
|
1548
|
-
const port = profilarrConfig.port || 6868
|
|
1549
|
-
const url = getApplicationUrl("profilarr", port, this.config)
|
|
1550
|
-
|
|
1551
|
-
this.updateStep("Profilarr", "running", "Waiting for Profilarr...")
|
|
1552
|
-
this.refreshContent()
|
|
1553
|
-
|
|
1554
|
-
const client = new ProfilarrApiClient("localhost", port)
|
|
1555
|
-
|
|
1556
|
-
// Wait for health
|
|
1557
|
-
let healthy = false
|
|
1558
|
-
for (let i = 0; i < 12; i++) {
|
|
1559
|
-
if (await client.isHealthy()) {
|
|
1560
|
-
healthy = true
|
|
1561
|
-
break
|
|
1562
|
-
}
|
|
1563
|
-
await new Promise((resolve) => setTimeout(resolve, 5000))
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
if (!healthy) {
|
|
1567
|
-
throw new Error("Timed out waiting for Profilarr API")
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
this.updateStep("Profilarr", "running", "Configuring...")
|
|
1571
|
-
|
|
1572
|
-
const result = await client.setup({
|
|
1573
|
-
username: this.globalUsername,
|
|
1574
|
-
password: this.globalPassword,
|
|
1575
|
-
env: this.env,
|
|
1576
|
-
})
|
|
1577
|
-
|
|
1578
|
-
if (!result.success) {
|
|
1579
|
-
throw new Error(result.message)
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// Save API key
|
|
1583
|
-
if (result.envUpdates) {
|
|
1584
|
-
await updateEnv(result.envUpdates)
|
|
1585
|
-
Object.assign(this.env, result.envUpdates)
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
// Configure Radarr/Sonarr connections after auth setup
|
|
1589
|
-
const radarrConfig = this.config.apps.find((a) => a.id === "radarr" && a.enabled)
|
|
1590
|
-
if (radarrConfig && this.env["API_KEY_RADARR"]) {
|
|
1591
|
-
try {
|
|
1592
|
-
await client.configureRadarr("radarr", radarrConfig.port || 7878, this.env["API_KEY_RADARR"])
|
|
1593
|
-
debugLog("FullAutoSetup", "Profilarr: Radarr configured")
|
|
1594
|
-
} catch {
|
|
1595
|
-
/* Radarr config failed */
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
const sonarrConfig = this.config.apps.find((a) => a.id === "sonarr" && a.enabled)
|
|
1600
|
-
if (sonarrConfig && this.env["API_KEY_SONARR"]) {
|
|
1601
|
-
try {
|
|
1602
|
-
await client.configureSonarr("sonarr", sonarrConfig.port || 8989, this.env["API_KEY_SONARR"])
|
|
1603
|
-
debugLog("FullAutoSetup", "Profilarr: Sonarr configured")
|
|
1604
|
-
} catch {
|
|
1605
|
-
/* Sonarr config failed */
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
// Sync all configured arr instances with the Dictionarry database
|
|
1610
|
-
try {
|
|
1611
|
-
debugLog("FullAutoSetup", "Profilarr: Syncing profiles and custom formats...")
|
|
1612
|
-
await client.syncAllConfigs()
|
|
1613
|
-
debugLog("FullAutoSetup", "Profilarr: Sync completed")
|
|
1614
|
-
} catch {
|
|
1615
|
-
debugLog("FullAutoSetup", "Profilarr: Sync failed, but setup is complete")
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
this.updateStep("Profilarr", "success", `Configured - ${url}`)
|
|
1619
|
-
} catch (e) {
|
|
1620
|
-
debugLog("FullAutoSetup", `Profilarr setup error: ${e}`)
|
|
1621
|
-
this.updateStep("Profilarr", "error", `${e}`)
|
|
1622
|
-
}
|
|
1623
|
-
this.refreshContent()
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
private updateStep(name: string, status: SetupStep["status"], message?: string): void {
|
|
1627
|
-
const step = this.steps.find((s) => s.name === name)
|
|
1628
|
-
if (step) {
|
|
1629
|
-
step.status = status
|
|
1630
|
-
step.message = message
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
private refreshContent(): void {
|
|
1635
|
-
this.contentBox.getChildren().forEach((child) => child.destroy())
|
|
1636
|
-
|
|
1637
|
-
if (!this.isRunning && !this.isDone) {
|
|
1638
|
-
// Show intro
|
|
1639
|
-
this.contentBox.add(
|
|
1640
|
-
new TextRenderable(this.cliRenderer, {
|
|
1641
|
-
content: "This will automatically configure:\n\n",
|
|
1642
|
-
fg: "#8be9fd",
|
|
1643
|
-
})
|
|
1644
|
-
)
|
|
1645
|
-
this.steps.forEach((step) => {
|
|
1646
|
-
this.contentBox.add(
|
|
1647
|
-
new TextRenderable(this.cliRenderer, {
|
|
1648
|
-
content: ` • ${step.name}\n`,
|
|
1649
|
-
fg: "#aaaaaa",
|
|
1650
|
-
})
|
|
1651
|
-
)
|
|
1652
|
-
})
|
|
1653
|
-
this.contentBox.add(
|
|
1654
|
-
new TextRenderable(this.cliRenderer, {
|
|
1655
|
-
content: "\n\nPress Enter to start, Esc to go back.\n",
|
|
1656
|
-
fg: "#50fa7b",
|
|
1657
|
-
})
|
|
1658
|
-
)
|
|
1659
|
-
} else {
|
|
1660
|
-
// Show progress
|
|
1661
|
-
this.contentBox.add(
|
|
1662
|
-
new TextRenderable(this.cliRenderer, {
|
|
1663
|
-
content: this.isDone ? "Setup Complete!\n\n" : "Setting up...\n\n",
|
|
1664
|
-
fg: this.isDone ? "#50fa7b" : "#f1fa8c",
|
|
1665
|
-
})
|
|
1666
|
-
)
|
|
1667
|
-
|
|
1668
|
-
this.steps.forEach((step) => {
|
|
1669
|
-
let icon = "⏳"
|
|
1670
|
-
let color = "#aaaaaa"
|
|
1671
|
-
if (step.status === "success") {
|
|
1672
|
-
icon = "✅"
|
|
1673
|
-
color = "#50fa7b"
|
|
1674
|
-
} else if (step.status === "error") {
|
|
1675
|
-
icon = "❌"
|
|
1676
|
-
color = "#ff5555"
|
|
1677
|
-
} else if (step.status === "skipped") {
|
|
1678
|
-
icon = "⏭️"
|
|
1679
|
-
color = "#6272a4"
|
|
1680
|
-
} else if (step.status === "running") {
|
|
1681
|
-
icon = "🔄"
|
|
1682
|
-
color = "#f1fa8c"
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
this.contentBox.add(
|
|
1686
|
-
new TextRenderable(this.cliRenderer, {
|
|
1687
|
-
content: `${icon} ${step.name}`,
|
|
1688
|
-
fg: color,
|
|
1689
|
-
})
|
|
1690
|
-
)
|
|
1691
|
-
if (step.message) {
|
|
1692
|
-
this.contentBox.add(
|
|
1693
|
-
new TextRenderable(this.cliRenderer, {
|
|
1694
|
-
content: ` - ${step.message}`,
|
|
1695
|
-
fg: "#6272a4",
|
|
1696
|
-
})
|
|
1697
|
-
)
|
|
1698
|
-
}
|
|
1699
|
-
this.contentBox.add(new TextRenderable(this.cliRenderer, { content: "\n", fg: "#aaaaaa" }))
|
|
1700
|
-
})
|
|
1701
|
-
|
|
1702
|
-
if (this.isDone) {
|
|
1703
|
-
this.contentBox.add(
|
|
1704
|
-
new TextRenderable(this.cliRenderer, {
|
|
1705
|
-
content: "\nPress Enter to go back.\n",
|
|
1706
|
-
fg: "#50fa7b",
|
|
1707
|
-
})
|
|
1708
|
-
)
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
private cleanup(): void {
|
|
1714
|
-
this.cliRenderer.keyInput.off("keypress", this.keyHandler)
|
|
1715
|
-
debugLog("FullAutoSetup", "Key handler removed")
|
|
1716
|
-
this.destroy()
|
|
1717
|
-
this.onBack()
|
|
1718
|
-
}
|
|
1719
|
-
}
|