@muhammedaksam/easiarr 1.4.0 → 1.6.0
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/dist/index.js +29750 -0
- package/package.json +15 -15
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -521
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/base-api.ts +0 -216
- package/src/api/bazarr-api.ts +0 -297
- package/src/api/cloudflare-api.ts +0 -493
- package/src/api/custom-format-api.ts +0 -224
- package/src/api/grafana-api.ts +0 -230
- package/src/api/heimdall-api.ts +0 -141
- package/src/api/homarr-api.ts +0 -209
- package/src/api/huntarr-api.ts +0 -457
- package/src/api/index.ts +0 -9
- package/src/api/jellyfin-api.ts +0 -389
- package/src/api/jellyseerr-api.ts +0 -559
- package/src/api/maintainerr-api.ts +0 -461
- package/src/api/naming-config.ts +0 -101
- package/src/api/overseerr-api.ts +0 -392
- package/src/api/plex-api.ts +0 -308
- package/src/api/portainer-api.ts +0 -374
- package/src/api/profilarr-api.ts +0 -391
- package/src/api/prowlarr-api.ts +0 -488
- package/src/api/qbittorrent-api.ts +0 -300
- package/src/api/quality-profile-api.ts +0 -272
- package/src/api/tautulli-api.ts +0 -244
- package/src/api/uptime-kuma-api.ts +0 -343
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1275
- package/src/compose/caddy-config.ts +0 -130
- package/src/compose/generator.ts +0 -287
- package/src/compose/index.ts +0 -7
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -187
- package/src/config/bookmarks-generator.ts +0 -160
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -381
- package/src/config/index.ts +0 -6
- package/src/config/manager.ts +0 -150
- package/src/config/recyclarr-config.ts +0 -181
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -86
- 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 -333
- package/src/docker/client.ts +0 -312
- package/src/docker/index.ts +0 -18
- package/src/index.ts +0 -77
- package/src/setup/actions/arr-common.ts +0 -307
- package/src/setup/actions/dashboards.ts +0 -79
- package/src/setup/actions/download-clients.ts +0 -54
- package/src/setup/actions/jellyseerr.ts +0 -331
- package/src/setup/actions/media-servers.ts +0 -68
- package/src/setup/actions/monitoring.ts +0 -120
- package/src/setup/actions/prowlarr.ts +0 -137
- package/src/setup/actions/utilities.ts +0 -490
- package/src/setup/index.ts +0 -78
- package/src/setup/types.ts +0 -144
- package/src/structure/manager.ts +0 -129
- package/src/ui/App.ts +0 -111
- package/src/ui/components/ApplicationSelector.ts +0 -275
- package/src/ui/components/CredentialsForm.ts +0 -180
- package/src/ui/components/DownloadClientForm.ts +0 -114
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -160
- package/src/ui/components/UpdateNotification.ts +0 -102
- package/src/ui/index.ts +0 -7
- package/src/ui/screens/AdvancedSettings.ts +0 -188
- package/src/ui/screens/ApiKeyViewer.ts +0 -511
- package/src/ui/screens/AppConfigurator.ts +0 -543
- package/src/ui/screens/AppManager.ts +0 -284
- package/src/ui/screens/ContainerControl.ts +0 -581
- package/src/ui/screens/LogsViewer.ts +0 -478
- package/src/ui/screens/MainMenu.ts +0 -299
- package/src/ui/screens/MonitorDashboard.ts +0 -881
- package/src/ui/screens/SecretsEditor.ts +0 -216
- package/src/ui/screens/SettingsScreen.ts +0 -633
- package/src/ui/screens/index.ts +0 -22
- package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
- package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
- package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
- package/src/ui/screens/setup/HomepageSetup.ts +0 -165
- package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
- package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
- package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
- package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
- package/src/ui/screens/setup/QuickSetup.ts +0 -1169
- package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
- package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
- package/src/utils/arch.ts +0 -78
- package/src/utils/browser.ts +0 -27
- package/src/utils/categories.ts +0 -61
- package/src/utils/debug.ts +0 -84
- package/src/utils/env.ts +0 -138
- package/src/utils/index.ts +0 -8
- package/src/utils/logs.ts +0 -119
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
- package/src/utils/migrations.ts +0 -165
- package/src/utils/password.ts +0 -42
- package/src/utils/paths.ts +0 -33
- package/src/utils/unraid.ts +0 -103
- package/src/utils/update-checker.ts +0 -210
- package/src/utils/url-utils.ts +0 -38
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Jellyseerr Setup Actions
|
|
3
|
-
*
|
|
4
|
-
* Reusable functions for configuring Jellyseerr.
|
|
5
|
-
* Can be called from FullAutoSetup or JellyseerrSetup screen.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { JellyseerrClient } from "~/api/jellyseerr-api"
|
|
9
|
-
import { getApp } from "~/apps/registry"
|
|
10
|
-
import { getEnabledAppConfig, reportStep, SetupContext, SetupResult } from "~/setup/types"
|
|
11
|
-
import { debugLog } from "~/utils/debug"
|
|
12
|
-
import { getApplicationUrl } from "~/utils/url-utils"
|
|
13
|
-
|
|
14
|
-
// ============================================
|
|
15
|
-
// LOW-LEVEL ACTIONS (individual operations)
|
|
16
|
-
// ============================================
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Check if Jellyseerr is enabled and a media server is available.
|
|
20
|
-
*/
|
|
21
|
-
export async function checkJellyseerrPrerequisites(ctx: SetupContext): Promise<SetupResult> {
|
|
22
|
-
const jellyseerrConfig = getEnabledAppConfig(ctx, "jellyseerr")
|
|
23
|
-
if (!jellyseerrConfig) {
|
|
24
|
-
return { success: false, message: "Not enabled" }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const hasJellyfin = getEnabledAppConfig(ctx, "jellyfin")
|
|
28
|
-
const hasPlex = getEnabledAppConfig(ctx, "plex")
|
|
29
|
-
|
|
30
|
-
if (!hasJellyfin && !hasPlex) {
|
|
31
|
-
return { success: false, message: "No media server enabled" }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Jellyseerr automation only works with Jellyfin
|
|
35
|
-
if (!hasJellyfin) {
|
|
36
|
-
return { success: false, message: "Plex requires manual setup" }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return { success: true }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Create a JellyseerrClient for the configured Jellyseerr instance.
|
|
44
|
-
*/
|
|
45
|
-
export function createJellyseerrClient(ctx: SetupContext): JellyseerrClient | null {
|
|
46
|
-
const config = getEnabledAppConfig(ctx, "jellyseerr")
|
|
47
|
-
if (!config) return null
|
|
48
|
-
|
|
49
|
-
const port = config.port || 5055
|
|
50
|
-
return new JellyseerrClient("localhost", port)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Run the base Jellyseerr setup wizard.
|
|
55
|
-
* This calls the API client's setup method which handles:
|
|
56
|
-
* - Health check
|
|
57
|
-
* - Authentication with Jellyfin
|
|
58
|
-
* - Library sync
|
|
59
|
-
* - API key retrieval
|
|
60
|
-
* - Initialization
|
|
61
|
-
*/
|
|
62
|
-
export async function runJellyseerrWizard(ctx: SetupContext): Promise<SetupResult> {
|
|
63
|
-
const client = createJellyseerrClient(ctx)
|
|
64
|
-
if (!client) {
|
|
65
|
-
return { success: false, message: "Jellyseerr not enabled" }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const result = await client.setup({
|
|
69
|
-
username: ctx.globalUsername,
|
|
70
|
-
password: ctx.globalPassword,
|
|
71
|
-
env: ctx.env,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
return result
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Configure Radarr connection in Jellyseerr.
|
|
79
|
-
*/
|
|
80
|
-
export async function configureJellyseerrRadarr(ctx: SetupContext): Promise<SetupResult> {
|
|
81
|
-
const client = createJellyseerrClient(ctx)
|
|
82
|
-
if (!client) {
|
|
83
|
-
return { success: false, message: "Jellyseerr not enabled" }
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const radarrConfig = getEnabledAppConfig(ctx, "radarr")
|
|
87
|
-
if (!radarrConfig) {
|
|
88
|
-
return { success: false, message: "Radarr not enabled" }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const radarrApiKey = ctx.env["API_KEY_RADARR"]
|
|
92
|
-
if (!radarrApiKey) {
|
|
93
|
-
return { success: false, message: "No Radarr API key" }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const radarrDef = getApp("radarr")
|
|
97
|
-
const radarrPort = radarrConfig.port || radarrDef?.defaultPort || 7878
|
|
98
|
-
const radarrExternalUrl = getApplicationUrl("radarr", radarrPort, ctx.config)
|
|
99
|
-
const rootFolder = radarrDef?.rootFolder?.path || "/data/media/movies"
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const result = await client.configureRadarr(
|
|
103
|
-
"radarr",
|
|
104
|
-
radarrPort,
|
|
105
|
-
radarrApiKey,
|
|
106
|
-
rootFolder,
|
|
107
|
-
radarrExternalUrl
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
if (result) {
|
|
111
|
-
debugLog("JellyseerrSetup", `Radarr configured: ${result.activeProfileName}`)
|
|
112
|
-
return { success: true, message: `Profile: ${result.activeProfileName}` }
|
|
113
|
-
} else {
|
|
114
|
-
return { success: false, message: "Configuration failed" }
|
|
115
|
-
}
|
|
116
|
-
} catch (e) {
|
|
117
|
-
return { success: false, message: String(e) }
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Configure Sonarr connection in Jellyseerr.
|
|
123
|
-
*/
|
|
124
|
-
export async function configureJellyseerrSonarr(ctx: SetupContext): Promise<SetupResult> {
|
|
125
|
-
const client = createJellyseerrClient(ctx)
|
|
126
|
-
if (!client) {
|
|
127
|
-
return { success: false, message: "Jellyseerr not enabled" }
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const sonarrConfig = getEnabledAppConfig(ctx, "sonarr")
|
|
131
|
-
if (!sonarrConfig) {
|
|
132
|
-
return { success: false, message: "Sonarr not enabled" }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const sonarrApiKey = ctx.env["API_KEY_SONARR"]
|
|
136
|
-
if (!sonarrApiKey) {
|
|
137
|
-
return { success: false, message: "No Sonarr API key" }
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const sonarrDef = getApp("sonarr")
|
|
141
|
-
const sonarrPort = sonarrConfig.port || sonarrDef?.defaultPort || 8989
|
|
142
|
-
const sonarrExternalUrl = getApplicationUrl("sonarr", sonarrPort, ctx.config)
|
|
143
|
-
const rootFolder = sonarrDef?.rootFolder?.path || "/data/media/tv"
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const result = await client.configureSonarr(
|
|
147
|
-
"sonarr",
|
|
148
|
-
sonarrPort,
|
|
149
|
-
sonarrApiKey,
|
|
150
|
-
rootFolder,
|
|
151
|
-
sonarrExternalUrl
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
if (result) {
|
|
155
|
-
debugLog("JellyseerrSetup", `Sonarr configured: ${result.activeProfileName}`)
|
|
156
|
-
return { success: true, message: `Profile: ${result.activeProfileName}` }
|
|
157
|
-
} else {
|
|
158
|
-
return { success: false, message: "Configuration failed" }
|
|
159
|
-
}
|
|
160
|
-
} catch (e) {
|
|
161
|
-
return { success: false, message: String(e) }
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Set Jellyseerr's application URL for external access.
|
|
167
|
-
*/
|
|
168
|
-
export async function setJellyseerrExternalUrl(ctx: SetupContext): Promise<SetupResult> {
|
|
169
|
-
const client = createJellyseerrClient(ctx)
|
|
170
|
-
if (!client) {
|
|
171
|
-
return { success: false, message: "Jellyseerr not enabled" }
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const config = getEnabledAppConfig(ctx, "jellyseerr")!
|
|
175
|
-
const port = config.port || 5055
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const url = getApplicationUrl("jellyseerr", port, ctx.config)
|
|
179
|
-
await client.setApplicationUrl(url)
|
|
180
|
-
debugLog("JellyseerrSetup", `applicationUrl set to ${url}`)
|
|
181
|
-
return { success: true, message: url }
|
|
182
|
-
} catch (e) {
|
|
183
|
-
return { success: false, message: String(e) }
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Set Jellyfin's external hostname in Jellyseerr settings.
|
|
189
|
-
* This is used for navigation links from Jellyseerr to Jellyfin.
|
|
190
|
-
*/
|
|
191
|
-
export async function setJellyseerrJellyfinUrl(ctx: SetupContext): Promise<SetupResult> {
|
|
192
|
-
const client = createJellyseerrClient(ctx)
|
|
193
|
-
if (!client) {
|
|
194
|
-
return { success: false, message: "Jellyseerr not enabled" }
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const jellyfinConfig = getEnabledAppConfig(ctx, "jellyfin")
|
|
198
|
-
if (!jellyfinConfig) {
|
|
199
|
-
return { success: false, message: "Jellyfin not enabled" }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const jellyfinPort = jellyfinConfig.port || 8096
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
const jellyfinUrl = getApplicationUrl("jellyfin", jellyfinPort, ctx.config)
|
|
206
|
-
await client.updateJellyfinSettings({ externalHostname: jellyfinUrl })
|
|
207
|
-
debugLog("JellyseerrSetup", `Jellyfin externalHostname set to ${jellyfinUrl}`)
|
|
208
|
-
return { success: true, message: jellyfinUrl }
|
|
209
|
-
} catch (e) {
|
|
210
|
-
return { success: false, message: String(e) }
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Sync and enable all Jellyfin libraries.
|
|
216
|
-
*/
|
|
217
|
-
export async function syncJellyseerrLibraries(ctx: SetupContext): Promise<SetupResult> {
|
|
218
|
-
const client = createJellyseerrClient(ctx)
|
|
219
|
-
if (!client) {
|
|
220
|
-
return { success: false, message: "Jellyseerr not enabled" }
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
const libraries = await client.syncJellyfinLibraries()
|
|
225
|
-
const libraryIds = libraries.map((lib) => lib.id)
|
|
226
|
-
if (libraryIds.length > 0) {
|
|
227
|
-
await client.enableLibraries(libraryIds)
|
|
228
|
-
}
|
|
229
|
-
return { success: true, message: `${libraries.length} libraries synced` }
|
|
230
|
-
} catch (e) {
|
|
231
|
-
return { success: false, message: String(e) }
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// ============================================
|
|
236
|
-
// HIGH-LEVEL ACTIONS (orchestrated workflows)
|
|
237
|
-
// ============================================
|
|
238
|
-
|
|
239
|
-
export interface JellyseerrSetupOptions {
|
|
240
|
-
/** Skip Radarr/Sonarr configuration */
|
|
241
|
-
skipArrApps?: boolean
|
|
242
|
-
/** Skip library sync (already done in wizard) */
|
|
243
|
-
skipLibraries?: boolean
|
|
244
|
-
/** Skip external URL configuration */
|
|
245
|
-
skipExternalUrls?: boolean
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Full Jellyseerr setup - runs all steps in sequence.
|
|
250
|
-
*
|
|
251
|
-
* This is the main entry point called by both:
|
|
252
|
-
* - FullAutoSetup.setupJellyseerr()
|
|
253
|
-
* - JellyseerrSetup.runSetupWizard()
|
|
254
|
-
*/
|
|
255
|
-
export async function runJellyseerrFullSetup(
|
|
256
|
-
ctx: SetupContext,
|
|
257
|
-
options: JellyseerrSetupOptions = {}
|
|
258
|
-
): Promise<SetupResult> {
|
|
259
|
-
// 1. Check prerequisites
|
|
260
|
-
reportStep(ctx, "Check prerequisites", "running")
|
|
261
|
-
const prereq = await checkJellyseerrPrerequisites(ctx)
|
|
262
|
-
if (!prereq.success) {
|
|
263
|
-
reportStep(ctx, "Check prerequisites", "skipped", prereq.message)
|
|
264
|
-
return prereq
|
|
265
|
-
}
|
|
266
|
-
reportStep(ctx, "Check prerequisites", "success")
|
|
267
|
-
|
|
268
|
-
// 2. Run wizard (creates admin, connects to Jellyfin, syncs libraries)
|
|
269
|
-
reportStep(ctx, "Setup wizard", "running")
|
|
270
|
-
const wizard = await runJellyseerrWizard(ctx)
|
|
271
|
-
if (!wizard.success) {
|
|
272
|
-
reportStep(
|
|
273
|
-
ctx,
|
|
274
|
-
"Setup wizard",
|
|
275
|
-
wizard.message === "Already configured" ? "success" : "error",
|
|
276
|
-
wizard.message
|
|
277
|
-
)
|
|
278
|
-
// If already configured, we can still continue with arr apps
|
|
279
|
-
if (wizard.message !== "Already configured") {
|
|
280
|
-
return wizard
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
reportStep(ctx, "Setup wizard", "success", wizard.message)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// 3. Configure Radarr (optional)
|
|
287
|
-
if (!options.skipArrApps) {
|
|
288
|
-
const radarrConfig = getEnabledAppConfig(ctx, "radarr")
|
|
289
|
-
if (radarrConfig && ctx.env["API_KEY_RADARR"]) {
|
|
290
|
-
reportStep(ctx, "Configure Radarr", "running")
|
|
291
|
-
const radarr = await configureJellyseerrRadarr(ctx)
|
|
292
|
-
reportStep(ctx, "Configure Radarr", radarr.success ? "success" : "skipped", radarr.message)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const sonarrConfig = getEnabledAppConfig(ctx, "sonarr")
|
|
296
|
-
if (sonarrConfig && ctx.env["API_KEY_SONARR"]) {
|
|
297
|
-
reportStep(ctx, "Configure Sonarr", "running")
|
|
298
|
-
const sonarr = await configureJellyseerrSonarr(ctx)
|
|
299
|
-
reportStep(ctx, "Configure Sonarr", sonarr.success ? "success" : "skipped", sonarr.message)
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// 4. Set external URLs (optional)
|
|
304
|
-
if (!options.skipExternalUrls) {
|
|
305
|
-
// Set Jellyfin's external hostname for navigation
|
|
306
|
-
reportStep(ctx, "Jellyfin URL", "running")
|
|
307
|
-
const jellyfinUrl = await setJellyseerrJellyfinUrl(ctx)
|
|
308
|
-
reportStep(
|
|
309
|
-
ctx,
|
|
310
|
-
"Jellyfin URL",
|
|
311
|
-
jellyfinUrl.success ? "success" : "skipped",
|
|
312
|
-
jellyfinUrl.message
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
// Set Jellyseerr's own applicationUrl
|
|
316
|
-
reportStep(ctx, "Jellyseerr URL", "running")
|
|
317
|
-
const jellyseerrUrl = await setJellyseerrExternalUrl(ctx)
|
|
318
|
-
reportStep(
|
|
319
|
-
ctx,
|
|
320
|
-
"Jellyseerr URL",
|
|
321
|
-
jellyseerrUrl.success ? "success" : "skipped",
|
|
322
|
-
jellyseerrUrl.message
|
|
323
|
-
)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
success: true,
|
|
328
|
-
message: "Jellyseerr configured",
|
|
329
|
-
envUpdates: wizard.envUpdates,
|
|
330
|
-
}
|
|
331
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Server Setup Actions
|
|
3
|
-
*
|
|
4
|
-
* Setup functions for Jellyfin, Plex, and related apps.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { JellyfinClient } from "~/api/jellyfin-api"
|
|
8
|
-
import { PlexApiClient } from "~/api/plex-api"
|
|
9
|
-
import { getEnabledAppConfig, SetupContext, SetupResult } from "~/setup/types"
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Setup Jellyfin media server
|
|
13
|
-
*/
|
|
14
|
-
export async function setupJellyfin(ctx: SetupContext): Promise<SetupResult> {
|
|
15
|
-
const jellyfinConfig = getEnabledAppConfig(ctx, "jellyfin")
|
|
16
|
-
if (!jellyfinConfig) {
|
|
17
|
-
return { success: false, message: "Not enabled" }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const port = jellyfinConfig.port || 8096
|
|
21
|
-
const client = new JellyfinClient("localhost", port)
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const result = await client.setup({
|
|
25
|
-
username: ctx.globalUsername,
|
|
26
|
-
password: ctx.globalPassword,
|
|
27
|
-
env: ctx.env,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
return result
|
|
31
|
-
} catch (e) {
|
|
32
|
-
return { success: false, message: String(e) }
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Setup Plex media server
|
|
38
|
-
*/
|
|
39
|
-
export async function setupPlex(ctx: SetupContext): Promise<SetupResult> {
|
|
40
|
-
const plexConfig = getEnabledAppConfig(ctx, "plex")
|
|
41
|
-
if (!plexConfig) {
|
|
42
|
-
return { success: false, message: "Not enabled" }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const port = plexConfig.port || 32400
|
|
46
|
-
const client = new PlexApiClient("localhost", port)
|
|
47
|
-
|
|
48
|
-
// Check if reachable
|
|
49
|
-
const healthy = await client.isHealthy()
|
|
50
|
-
if (!healthy) {
|
|
51
|
-
return { success: false, message: "Not reachable yet" }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
// Run auto-setup - pass enabled apps so Plex knows which libraries to create
|
|
56
|
-
const enabledApps = ctx.config.apps.filter((a) => a.enabled).map((a) => a.id)
|
|
57
|
-
const result = await client.setup({
|
|
58
|
-
username: ctx.globalUsername,
|
|
59
|
-
password: ctx.globalPassword,
|
|
60
|
-
env: ctx.env,
|
|
61
|
-
enabledApps,
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
return result
|
|
65
|
-
} catch (e) {
|
|
66
|
-
return { success: false, message: String(e) }
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Monitoring App Setup Actions
|
|
3
|
-
*
|
|
4
|
-
* Setup functions for Uptime Kuma, Grafana, Tautulli, and other monitoring apps.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { GrafanaClient } from "~/api/grafana-api"
|
|
8
|
-
import { TautulliClient } from "~/api/tautulli-api"
|
|
9
|
-
import { UptimeKumaClient } from "~/api/uptime-kuma-api"
|
|
10
|
-
import { getEnabledAppConfig, SetupContext, SetupResult } from "~/setup/types"
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Setup Uptime Kuma monitoring
|
|
14
|
-
*/
|
|
15
|
-
export async function setupUptimeKuma(ctx: SetupContext): Promise<SetupResult> {
|
|
16
|
-
const uptimeKumaConfig = getEnabledAppConfig(ctx, "uptime-kuma")
|
|
17
|
-
if (!uptimeKumaConfig) {
|
|
18
|
-
return { success: false, message: "Not enabled" }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const port = uptimeKumaConfig.port || 3001
|
|
22
|
-
const client = new UptimeKumaClient("localhost", port)
|
|
23
|
-
|
|
24
|
-
// Check if reachable
|
|
25
|
-
const healthy = await client.isHealthy()
|
|
26
|
-
if (!healthy) {
|
|
27
|
-
return { success: false, message: "Not reachable yet" }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
// Run auto-setup (creates admin or logs in)
|
|
32
|
-
const result = await client.setup({
|
|
33
|
-
username: ctx.globalUsername,
|
|
34
|
-
password: ctx.globalPassword,
|
|
35
|
-
env: ctx.env,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
if (result.success) {
|
|
39
|
-
// Now add monitors for enabled apps
|
|
40
|
-
try {
|
|
41
|
-
const loggedIn = await client.login(ctx.globalUsername, ctx.globalPassword)
|
|
42
|
-
if (loggedIn) {
|
|
43
|
-
const addedCount = await client.setupEasiarrMonitors(ctx.config.apps)
|
|
44
|
-
client.disconnect()
|
|
45
|
-
return { success: true, message: `${result.message}, ${addedCount} monitors added` }
|
|
46
|
-
} else {
|
|
47
|
-
client.disconnect()
|
|
48
|
-
return result
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
client.disconnect()
|
|
52
|
-
return result
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
return result
|
|
56
|
-
}
|
|
57
|
-
} catch (e) {
|
|
58
|
-
return { success: false, message: String(e) }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Setup Grafana monitoring dashboards
|
|
64
|
-
*/
|
|
65
|
-
export async function setupGrafana(ctx: SetupContext): Promise<SetupResult> {
|
|
66
|
-
const grafanaConfig = getEnabledAppConfig(ctx, "grafana")
|
|
67
|
-
if (!grafanaConfig) {
|
|
68
|
-
return { success: false, message: "Not enabled" }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const port = grafanaConfig.port || 3001
|
|
72
|
-
const client = new GrafanaClient("localhost", port)
|
|
73
|
-
|
|
74
|
-
// Check if reachable
|
|
75
|
-
const healthy = await client.isHealthy()
|
|
76
|
-
if (!healthy) {
|
|
77
|
-
return { success: false, message: "Not reachable yet" }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
const result = await client.setup({
|
|
82
|
-
username: ctx.globalUsername,
|
|
83
|
-
password: ctx.globalPassword,
|
|
84
|
-
env: ctx.env,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
return result
|
|
88
|
-
} catch (e) {
|
|
89
|
-
return { success: false, message: String(e) }
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Setup Tautulli for Plex statistics
|
|
95
|
-
*/
|
|
96
|
-
export async function setupTautulli(ctx: SetupContext): Promise<SetupResult> {
|
|
97
|
-
const tautulliConfig = getEnabledAppConfig(ctx, "tautulli")
|
|
98
|
-
if (!tautulliConfig) {
|
|
99
|
-
return { success: false, message: "Not enabled" }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const port = tautulliConfig.port || 8181
|
|
103
|
-
const client = new TautulliClient("localhost", port)
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
const result = await client.setup({
|
|
107
|
-
username: ctx.globalUsername,
|
|
108
|
-
password: ctx.globalPassword,
|
|
109
|
-
env: ctx.env,
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
if (result.success && result.data?.requiresWizard) {
|
|
113
|
-
return { ...result, message: `${result.message} (manual Plex setup needed)` }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return result
|
|
117
|
-
} catch (e) {
|
|
118
|
-
return { success: false, message: String(e) }
|
|
119
|
-
}
|
|
120
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prowlarr Setup Actions
|
|
3
|
-
*
|
|
4
|
-
* Setup functions for Prowlarr indexer management.
|
|
5
|
-
* Includes: connecting *arr apps, FlareSolverr configuration.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { ArrAppType, ProwlarrClient } from "~/api/prowlarr-api"
|
|
9
|
-
import { getApp } from "~/apps/registry"
|
|
10
|
-
import { getEnabledAppConfig, SetupContext, SetupResult } from "~/setup/types"
|
|
11
|
-
|
|
12
|
-
const ARR_APP_TYPES: Record<string, ArrAppType> = {
|
|
13
|
-
radarr: "Radarr",
|
|
14
|
-
sonarr: "Sonarr",
|
|
15
|
-
lidarr: "Lidarr",
|
|
16
|
-
readarr: "Readarr",
|
|
17
|
-
whisparr: "Whisparr",
|
|
18
|
-
mylar3: "Mylar",
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Create a ProwlarrClient if enabled
|
|
23
|
-
*/
|
|
24
|
-
export function createProwlarrClient(ctx: SetupContext): ProwlarrClient | null {
|
|
25
|
-
const apiKey = ctx.env["API_KEY_PROWLARR"]
|
|
26
|
-
if (!apiKey) return null
|
|
27
|
-
|
|
28
|
-
const config = getEnabledAppConfig(ctx, "prowlarr")
|
|
29
|
-
const port = config?.port || 9696
|
|
30
|
-
return new ProwlarrClient("localhost", port, apiKey)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Connect all enabled *arr apps to Prowlarr
|
|
35
|
-
*/
|
|
36
|
-
export async function setupProwlarrApps(ctx: SetupContext): Promise<SetupResult> {
|
|
37
|
-
const apiKey = ctx.env["API_KEY_PROWLARR"]
|
|
38
|
-
if (!apiKey) {
|
|
39
|
-
return { success: false, message: "No Prowlarr API key" }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const prowlarrConfig = getEnabledAppConfig(ctx, "prowlarr")
|
|
43
|
-
if (!prowlarrConfig) {
|
|
44
|
-
return { success: false, message: "Prowlarr not enabled" }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const prowlarrPort = prowlarrConfig.port || 9696
|
|
48
|
-
const prowlarr = new ProwlarrClient("localhost", prowlarrPort, apiKey)
|
|
49
|
-
|
|
50
|
-
const arrApps = ctx.config.apps.filter((a) => {
|
|
51
|
-
return a.enabled && ARR_APP_TYPES[a.id]
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
let configured = 0
|
|
55
|
-
|
|
56
|
-
for (const app of arrApps) {
|
|
57
|
-
const appType = ARR_APP_TYPES[app.id]
|
|
58
|
-
if (!appType) continue
|
|
59
|
-
|
|
60
|
-
const appApiKey = ctx.env[`API_KEY_${app.id.toUpperCase()}`]
|
|
61
|
-
if (!appApiKey) continue
|
|
62
|
-
|
|
63
|
-
const def = getApp(app.id)
|
|
64
|
-
const port = app.port || def?.defaultPort || 7878
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
await prowlarr.addArrApp(
|
|
68
|
-
appType,
|
|
69
|
-
app.id,
|
|
70
|
-
port,
|
|
71
|
-
appApiKey,
|
|
72
|
-
"prowlarr",
|
|
73
|
-
prowlarrPort,
|
|
74
|
-
def?.prowlarrCategoryIds
|
|
75
|
-
)
|
|
76
|
-
configured++
|
|
77
|
-
} catch {
|
|
78
|
-
// Skip - may already exist
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Trigger sync
|
|
83
|
-
try {
|
|
84
|
-
await prowlarr.syncApplications()
|
|
85
|
-
} catch {
|
|
86
|
-
// May fail if no indexers
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { success: true, message: `${configured} apps connected` }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Configure FlareSolverr in Prowlarr
|
|
94
|
-
*/
|
|
95
|
-
export async function setupFlareSolverr(ctx: SetupContext): Promise<SetupResult> {
|
|
96
|
-
const apiKey = ctx.env["API_KEY_PROWLARR"]
|
|
97
|
-
const flaresolverr = getEnabledAppConfig(ctx, "flaresolverr")
|
|
98
|
-
|
|
99
|
-
if (!apiKey) {
|
|
100
|
-
return { success: false, message: "No Prowlarr API key" }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!flaresolverr) {
|
|
104
|
-
return { success: false, message: "FlareSolverr not enabled" }
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const prowlarrConfig = getEnabledAppConfig(ctx, "prowlarr")
|
|
108
|
-
const prowlarrPort = prowlarrConfig?.port || 9696
|
|
109
|
-
const prowlarr = new ProwlarrClient("localhost", prowlarrPort, apiKey)
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
await prowlarr.configureFlareSolverr("http://flaresolverr:8191")
|
|
113
|
-
return { success: true, message: "FlareSolverr configured" }
|
|
114
|
-
} catch (e) {
|
|
115
|
-
return { success: false, message: String(e) }
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Full Prowlarr setup combining apps and FlareSolverr
|
|
121
|
-
*/
|
|
122
|
-
export async function runProwlarrFullSetup(ctx: SetupContext): Promise<SetupResult> {
|
|
123
|
-
const appsResult = await setupProwlarrApps(ctx)
|
|
124
|
-
|
|
125
|
-
// FlareSolverr is optional, so we don't fail if it doesn't work
|
|
126
|
-
const flaresolverrResult = await setupFlareSolverr(ctx)
|
|
127
|
-
|
|
128
|
-
if (!appsResult.success) {
|
|
129
|
-
return appsResult
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const message = flaresolverrResult.success
|
|
133
|
-
? `${appsResult.message}, FlareSolverr enabled`
|
|
134
|
-
: appsResult.message
|
|
135
|
-
|
|
136
|
-
return { success: true, message }
|
|
137
|
-
}
|