@muhammedaksam/easiarr 1.4.0 → 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.
Files changed (109) hide show
  1. package/dist/index.js +29837 -0
  2. package/package.json +15 -15
  3. package/src/VersionInfo.ts +0 -12
  4. package/src/api/arr-api.ts +0 -521
  5. package/src/api/auto-setup-types.ts +0 -64
  6. package/src/api/base-api.ts +0 -216
  7. package/src/api/bazarr-api.ts +0 -297
  8. package/src/api/cloudflare-api.ts +0 -493
  9. package/src/api/custom-format-api.ts +0 -224
  10. package/src/api/grafana-api.ts +0 -230
  11. package/src/api/heimdall-api.ts +0 -141
  12. package/src/api/homarr-api.ts +0 -209
  13. package/src/api/huntarr-api.ts +0 -457
  14. package/src/api/index.ts +0 -9
  15. package/src/api/jellyfin-api.ts +0 -389
  16. package/src/api/jellyseerr-api.ts +0 -559
  17. package/src/api/maintainerr-api.ts +0 -461
  18. package/src/api/naming-config.ts +0 -101
  19. package/src/api/overseerr-api.ts +0 -392
  20. package/src/api/plex-api.ts +0 -308
  21. package/src/api/portainer-api.ts +0 -374
  22. package/src/api/profilarr-api.ts +0 -391
  23. package/src/api/prowlarr-api.ts +0 -488
  24. package/src/api/qbittorrent-api.ts +0 -300
  25. package/src/api/quality-profile-api.ts +0 -272
  26. package/src/api/tautulli-api.ts +0 -244
  27. package/src/api/uptime-kuma-api.ts +0 -343
  28. package/src/apps/categories.ts +0 -14
  29. package/src/apps/index.ts +0 -2
  30. package/src/apps/registry.ts +0 -1275
  31. package/src/compose/caddy-config.ts +0 -130
  32. package/src/compose/generator.ts +0 -287
  33. package/src/compose/index.ts +0 -7
  34. package/src/compose/templates.ts +0 -78
  35. package/src/compose/traefik-config.ts +0 -187
  36. package/src/config/bookmarks-generator.ts +0 -160
  37. package/src/config/defaults.ts +0 -37
  38. package/src/config/homepage-config.ts +0 -381
  39. package/src/config/index.ts +0 -6
  40. package/src/config/manager.ts +0 -150
  41. package/src/config/recyclarr-config.ts +0 -181
  42. package/src/config/schema.ts +0 -310
  43. package/src/config/slskd-config.ts +0 -86
  44. package/src/config/soularr-config.ts +0 -105
  45. package/src/config/trash-quality-definitions.ts +0 -213
  46. package/src/data/lidarr-custom-formats.ts +0 -127
  47. package/src/data/trash-profiles.ts +0 -333
  48. package/src/docker/client.ts +0 -312
  49. package/src/docker/index.ts +0 -18
  50. package/src/index.ts +0 -77
  51. package/src/setup/actions/arr-common.ts +0 -307
  52. package/src/setup/actions/dashboards.ts +0 -79
  53. package/src/setup/actions/download-clients.ts +0 -54
  54. package/src/setup/actions/jellyseerr.ts +0 -331
  55. package/src/setup/actions/media-servers.ts +0 -68
  56. package/src/setup/actions/monitoring.ts +0 -120
  57. package/src/setup/actions/prowlarr.ts +0 -137
  58. package/src/setup/actions/utilities.ts +0 -490
  59. package/src/setup/index.ts +0 -78
  60. package/src/setup/types.ts +0 -144
  61. package/src/structure/manager.ts +0 -129
  62. package/src/ui/App.ts +0 -111
  63. package/src/ui/components/ApplicationSelector.ts +0 -275
  64. package/src/ui/components/CredentialsForm.ts +0 -180
  65. package/src/ui/components/DownloadClientForm.ts +0 -114
  66. package/src/ui/components/FileEditor.ts +0 -91
  67. package/src/ui/components/FooterHint.ts +0 -119
  68. package/src/ui/components/PageLayout.ts +0 -160
  69. package/src/ui/components/UpdateNotification.ts +0 -102
  70. package/src/ui/index.ts +0 -7
  71. package/src/ui/screens/AdvancedSettings.ts +0 -188
  72. package/src/ui/screens/ApiKeyViewer.ts +0 -511
  73. package/src/ui/screens/AppConfigurator.ts +0 -543
  74. package/src/ui/screens/AppManager.ts +0 -284
  75. package/src/ui/screens/ContainerControl.ts +0 -581
  76. package/src/ui/screens/LogsViewer.ts +0 -478
  77. package/src/ui/screens/MainMenu.ts +0 -299
  78. package/src/ui/screens/MonitorDashboard.ts +0 -881
  79. package/src/ui/screens/SecretsEditor.ts +0 -216
  80. package/src/ui/screens/SettingsScreen.ts +0 -633
  81. package/src/ui/screens/index.ts +0 -22
  82. package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
  83. package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
  84. package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
  85. package/src/ui/screens/setup/HomepageSetup.ts +0 -165
  86. package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
  87. package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
  88. package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
  89. package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
  90. package/src/ui/screens/setup/QuickSetup.ts +0 -1169
  91. package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
  92. package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
  93. package/src/utils/arch.ts +0 -78
  94. package/src/utils/browser.ts +0 -27
  95. package/src/utils/categories.ts +0 -61
  96. package/src/utils/debug.ts +0 -84
  97. package/src/utils/env.ts +0 -138
  98. package/src/utils/index.ts +0 -8
  99. package/src/utils/logs.ts +0 -119
  100. package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
  101. package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
  102. package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
  103. package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
  104. package/src/utils/migrations.ts +0 -165
  105. package/src/utils/password.ts +0 -42
  106. package/src/utils/paths.ts +0 -33
  107. package/src/utils/unraid.ts +0 -103
  108. package/src/utils/update-checker.ts +0 -210
  109. 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
- }