@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,165 +0,0 @@
1
- /**
2
- * Homepage Setup Screen
3
- * Configure Homepage dashboard with enabled apps
4
- */
5
-
6
- import { CliRenderer, KeyEvent, TextRenderable } from "@opentui/core"
7
-
8
- import type { MenuItem } from "~/ui/screens/setup/BaseAppSetupScreen"
9
- import { getApp } from "~/apps/registry"
10
- import { generateServicesYaml, saveHomepageConfig } from "~/config/homepage-config"
11
- import { EasiarrConfig } from "~/config/schema"
12
- import { BaseAppSetupScreen } from "~/ui/screens/setup/BaseAppSetupScreen"
13
-
14
- export class HomepageSetup extends BaseAppSetupScreen {
15
- private previewContent = ""
16
- private isPreviewMode = false
17
-
18
- constructor(cliRenderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
19
- super(cliRenderer, config, onBack)
20
- }
21
-
22
- getTitle(): string {
23
- return "Homepage Setup"
24
- }
25
-
26
- getStepInfo(): string {
27
- return "Configure Dashboard"
28
- }
29
-
30
- getMenuItems(): MenuItem[] {
31
- return [
32
- {
33
- name: "📊 Generate Services",
34
- description: "Create services.yaml with all enabled apps",
35
- action: () => this.generateServices(),
36
- },
37
- {
38
- name: "👁️ Preview Config",
39
- description: "Preview generated services.yaml",
40
- action: () => this.previewServices(),
41
- },
42
- {
43
- name: "📋 Show Enabled Apps",
44
- description: "List apps that will be added to Homepage",
45
- action: () => this.showEnabledApps(),
46
- },
47
- {
48
- name: "↩️ Back",
49
- description: "Return to main menu",
50
- action: () => this.cleanup(),
51
- },
52
- ]
53
- }
54
-
55
- protected getMenuDescription(): string {
56
- return "Configure Homepage dashboard with your enabled apps:"
57
- }
58
-
59
- // ============================================
60
- // CUSTOM KEY HANDLING - Handle preview step
61
- // ============================================
62
-
63
- protected handleCustomKeys(key: KeyEvent): boolean {
64
- if (this.isPreviewMode) {
65
- if (key.name === "return" || key.name === "escape") {
66
- this.isPreviewMode = false
67
- this.refreshContent()
68
- }
69
- return true // Handled
70
- }
71
- return false
72
- }
73
-
74
- // ============================================
75
- // CUSTOM RENDERING - Preview step
76
- // ============================================
77
-
78
- protected renderCustomContent(): boolean {
79
- if (this.isPreviewMode) {
80
- this.renderPreview()
81
- return true
82
- }
83
- return false // Use default for menu/results
84
- }
85
-
86
- private renderPreview(): void {
87
- this.contentBox.add(
88
- new TextRenderable(this.cliRenderer, {
89
- content: "Preview: services.yaml\n",
90
- fg: "#50fa7b",
91
- })
92
- )
93
- this.contentBox.add(
94
- new TextRenderable(this.cliRenderer, {
95
- content: "─".repeat(40) + "\n",
96
- fg: "#555555",
97
- })
98
- )
99
-
100
- // Show preview (truncated)
101
- const lines = this.previewContent.split("\n").slice(0, 30)
102
- for (const line of lines) {
103
- this.contentBox.add(
104
- new TextRenderable(this.cliRenderer, {
105
- content: line + "\n",
106
- fg: line.startsWith("#") ? "#6272a4" : line.endsWith(":") ? "#8be9fd" : "#f8f8f2",
107
- })
108
- )
109
- }
110
-
111
- if (this.previewContent.split("\n").length > 30) {
112
- this.contentBox.add(
113
- new TextRenderable(this.cliRenderer, {
114
- content: "\n... (truncated)\n",
115
- fg: "#6272a4",
116
- })
117
- )
118
- }
119
-
120
- this.contentBox.add(
121
- new TextRenderable(this.cliRenderer, {
122
- content: "\nPress Enter or Esc to go back",
123
- fg: "#6272a4",
124
- })
125
- )
126
- }
127
-
128
- // ============================================
129
- // MENU ACTIONS
130
- // ============================================
131
-
132
- private async generateServices(): Promise<void> {
133
- await this.runAction("services.yaml", async () => {
134
- const paths = await saveHomepageConfig(this.config)
135
- return { success: true, message: `Saved to ${paths.services}` }
136
- })
137
- }
138
-
139
- private async previewServices(): Promise<void> {
140
- this.previewContent = await generateServicesYaml(this.config)
141
- this.isPreviewMode = true
142
- this.refreshContent()
143
- }
144
-
145
- private showEnabledApps(): void {
146
- const apps = this.config.apps.filter((a) => a.enabled && a.id !== "homepage")
147
-
148
- this.results = apps.map((app) => {
149
- const def = getApp(app.id)
150
- const hasWidget = def?.homepage?.widget ? "📊" : "📌"
151
- return {
152
- name: `${hasWidget} ${def?.name || app.id}`,
153
- status: "success" as const,
154
- message: def?.description,
155
- }
156
- })
157
-
158
- if (this.results.length === 0) {
159
- this.results = [{ name: "No apps enabled", status: "skipped", message: "Enable apps first" }]
160
- }
161
-
162
- this.currentStep = "done"
163
- this.refreshContent()
164
- }
165
- }
@@ -1,328 +0,0 @@
1
- /**
2
- * Jellyfin Setup Screen
3
- * Automates the Jellyfin setup wizard via API
4
- */
5
-
6
- import { CliRenderer, TextRenderable } from "@opentui/core"
7
-
8
- import type { MenuItem, SetupResult } from "~/ui/screens/setup/BaseAppSetupScreen"
9
- import { JellyfinClient } from "~/api/jellyfin-api"
10
- import { EasiarrConfig } from "~/config/schema"
11
- import { BaseAppSetupScreen } from "~/ui/screens/setup/BaseAppSetupScreen"
12
- import { readEnvSync, writeEnvSync } from "~/utils/env"
13
-
14
- export class JellyfinSetup extends BaseAppSetupScreen {
15
- private jellyfinClient: JellyfinClient | null = null
16
- private healthChecked = false
17
- private healthMessage = ""
18
- private healthFg = "#aaaaaa"
19
-
20
- constructor(cliRenderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
21
- super(cliRenderer, config, onBack)
22
- this.initJellyfinClient()
23
- this.checkHealth()
24
- }
25
-
26
- getTitle(): string {
27
- return "Jellyfin Setup"
28
- }
29
-
30
- getStepInfo(): string {
31
- return "Configure Jellyfin via API"
32
- }
33
-
34
- getMenuItems(): MenuItem[] {
35
- return [
36
- {
37
- name: "🚀 Run Setup Wizard",
38
- description: "Create admin user and complete initial setup",
39
- action: () => this.runSetupWizard(),
40
- },
41
- {
42
- name: "📚 Add Default Libraries",
43
- description: "Add Movies, TV Shows, Music libraries",
44
- action: () => this.addDefaultLibraries(),
45
- },
46
- {
47
- name: "🔑 Generate API Key",
48
- description: "Create API key for Homepage widget",
49
- action: () => this.generateApiKey(),
50
- },
51
- {
52
- name: "↩️ Back",
53
- description: "Return to main menu",
54
- action: () => this.cleanup(),
55
- },
56
- ]
57
- }
58
-
59
- // ============================================
60
- // CUSTOM RENDERING - Show health status
61
- // ============================================
62
-
63
- protected renderCustomContent(): boolean {
64
- if (this.currentStep === "menu") {
65
- // Show health status before menu
66
- if (this.healthChecked && this.healthMessage) {
67
- this.contentBox.add(
68
- new TextRenderable(this.cliRenderer, {
69
- content: this.healthMessage + "\n\n",
70
- fg: this.healthFg,
71
- })
72
- )
73
- }
74
-
75
- // Render menu normally
76
- this.contentBox.add(
77
- new TextRenderable(this.cliRenderer, {
78
- content: "Select an action:\n\n",
79
- fg: "#aaaaaa",
80
- })
81
- )
82
-
83
- this.getMenuItems().forEach((item, idx) => {
84
- const pointer = idx === this.menuIndex ? "→ " : " "
85
- const fg = idx === this.menuIndex ? "#50fa7b" : "#8be9fd"
86
-
87
- this.contentBox.add(
88
- new TextRenderable(this.cliRenderer, {
89
- content: `${pointer}${item.name}\n`,
90
- fg,
91
- })
92
- )
93
- this.contentBox.add(
94
- new TextRenderable(this.cliRenderer, {
95
- content: ` ${item.description}\n\n`,
96
- fg: "#6272a4",
97
- })
98
- )
99
- })
100
-
101
- return true
102
- }
103
- return false // Use default for results
104
- }
105
-
106
- // ============================================
107
- // PRIVATE METHODS
108
- // ============================================
109
-
110
- private initJellyfinClient(): void {
111
- const jellyfinConfig = this.config.apps.find((a) => a.id === "jellyfin")
112
- if (jellyfinConfig?.enabled) {
113
- const port = jellyfinConfig.port || 8096
114
- this.jellyfinClient = new JellyfinClient("localhost", port)
115
- }
116
- }
117
-
118
- private async checkHealth(): Promise<void> {
119
- if (!this.jellyfinClient) {
120
- this.healthMessage = "⚠️ Jellyfin not enabled in config!"
121
- this.healthFg = "#ff5555"
122
- this.healthChecked = true
123
- this.refreshContent()
124
- return
125
- }
126
-
127
- try {
128
- const isHealthy = await this.jellyfinClient.isHealthy()
129
- const isComplete = isHealthy ? await this.jellyfinClient.isStartupComplete() : false
130
-
131
- if (!isHealthy) {
132
- this.healthMessage = "⚠️ Jellyfin is not reachable. Make sure the container is running."
133
- this.healthFg = "#ffb86c"
134
- } else if (!isComplete) {
135
- this.healthMessage = "✨ Jellyfin needs initial setup. Run 'Setup Wizard' to configure."
136
- this.healthFg = "#50fa7b"
137
- } else {
138
- this.healthMessage = "✓ Jellyfin is running and configured."
139
- this.healthFg = "#50fa7b"
140
- }
141
- } catch {
142
- // Ignore health check errors
143
- }
144
-
145
- this.healthChecked = true
146
- this.refreshContent()
147
- }
148
-
149
- private updateResult(index: number, status: SetupResult["status"], message?: string): void {
150
- if (this.results[index]) {
151
- this.results[index].status = status
152
- if (message) this.results[index].message = message
153
- }
154
- this.refreshContent()
155
- }
156
-
157
- // ============================================
158
- // MENU ACTIONS
159
- // ============================================
160
-
161
- private async runSetupWizard(): Promise<void> {
162
- if (!this.jellyfinClient) {
163
- this.results = [{ name: "Jellyfin", status: "error", message: "Not enabled in config" }]
164
- this.currentStep = "done"
165
- this.refreshContent()
166
- return
167
- }
168
-
169
- this.currentStep = "running"
170
- this.results = [
171
- { name: "Check status", status: "configuring" },
172
- { name: "Set metadata language", status: "pending" },
173
- { name: "Create admin user", status: "pending" },
174
- { name: "Configure remote access", status: "pending" },
175
- { name: "Complete wizard", status: "pending" },
176
- ]
177
- this.refreshContent()
178
-
179
- try {
180
- // Step 1: Check if already set up
181
- const isComplete = await this.jellyfinClient.isStartupComplete()
182
- if (isComplete) {
183
- this.updateResult(0, "skipped", "Already configured")
184
- this.results
185
- .slice(1)
186
- .forEach((_, i) => this.updateResult(i + 1, "skipped", "Wizard already completed"))
187
- this.currentStep = "done"
188
- this.refreshContent()
189
- return
190
- }
191
- this.updateResult(0, "success", "Wizard needed")
192
-
193
- // Step 2: Set metadata language
194
- this.updateResult(1, "configuring")
195
- await this.jellyfinClient.setStartupConfiguration({
196
- UICulture: "en-US",
197
- MetadataCountryCode: "US",
198
- PreferredMetadataLanguage: "en",
199
- })
200
- this.updateResult(1, "success")
201
-
202
- // Step 3: Create admin user
203
- this.updateResult(2, "configuring")
204
- const env = readEnvSync()
205
- const username = env["USERNAME_GLOBAL"] || "admin"
206
- const password = env["PASSWORD_GLOBAL"] || "Ch4ng3m3!1234securityReasons"
207
- await this.jellyfinClient.createAdminUser(username, password)
208
- this.updateResult(2, "success", `User: ${username}`)
209
-
210
- // Step 4: Configure remote access
211
- this.updateResult(3, "configuring")
212
- await this.jellyfinClient.setRemoteAccess(true, false)
213
- this.updateResult(3, "success")
214
-
215
- // Step 5: Complete wizard
216
- this.updateResult(4, "configuring")
217
- await this.jellyfinClient.completeStartup()
218
- this.updateResult(4, "success")
219
- } catch (error) {
220
- const current = this.results.findIndex((r) => r.status === "configuring")
221
- if (current >= 0) {
222
- this.updateResult(current, "error", error instanceof Error ? error.message : String(error))
223
- }
224
- }
225
-
226
- this.currentStep = "done"
227
- this.refreshContent()
228
- }
229
-
230
- private async addDefaultLibraries(): Promise<void> {
231
- if (!this.jellyfinClient) {
232
- this.results = [{ name: "Jellyfin", status: "error", message: "Not enabled in config" }]
233
- this.currentStep = "done"
234
- this.refreshContent()
235
- return
236
- }
237
-
238
- this.currentStep = "running"
239
- this.results = [
240
- { name: "Authenticate", status: "configuring" },
241
- { name: "Movies", status: "pending" },
242
- { name: "TV Shows", status: "pending" },
243
- { name: "Music", status: "pending" },
244
- ]
245
- this.refreshContent()
246
-
247
- try {
248
- // Authenticate first
249
- const env = readEnvSync()
250
- const username = env["USERNAME_GLOBAL"] || "admin"
251
- const password = env["PASSWORD_GLOBAL"] || "Ch4ng3m3!1234securityReasons"
252
- await this.jellyfinClient.authenticate(username, password)
253
- this.updateResult(0, "success")
254
-
255
- // Add libraries
256
- const libraries = [
257
- { name: "Movies", collectionType: "movies" as const, paths: ["/data/media/movies"] },
258
- { name: "TV Shows", collectionType: "tvshows" as const, paths: ["/data/media/tv"] },
259
- { name: "Music", collectionType: "music" as const, paths: ["/data/media/music"] },
260
- ]
261
-
262
- for (let i = 0; i < libraries.length; i++) {
263
- const lib = libraries[i]
264
- this.updateResult(i + 1, "configuring")
265
-
266
- try {
267
- await this.jellyfinClient.addVirtualFolder(lib)
268
- this.updateResult(i + 1, "success", lib.paths[0])
269
- } catch (error) {
270
- this.updateResult(i + 1, "error", error instanceof Error ? error.message : String(error))
271
- }
272
- }
273
- } catch (error) {
274
- this.updateResult(0, "error", error instanceof Error ? error.message : String(error))
275
- }
276
-
277
- this.currentStep = "done"
278
- this.refreshContent()
279
- }
280
-
281
- private async generateApiKey(): Promise<void> {
282
- if (!this.jellyfinClient) {
283
- this.results = [{ name: "Jellyfin", status: "error", message: "Not enabled in config" }]
284
- this.currentStep = "done"
285
- this.refreshContent()
286
- return
287
- }
288
-
289
- this.currentStep = "running"
290
- this.results = [
291
- { name: "Authenticate", status: "configuring" },
292
- { name: "Generate API Key", status: "pending" },
293
- { name: "Save to .env", status: "pending" },
294
- ]
295
- this.refreshContent()
296
-
297
- try {
298
- // Authenticate first
299
- const env = readEnvSync()
300
- const username = env["USERNAME_GLOBAL"] || "admin"
301
- const password = env["PASSWORD_GLOBAL"] || "Ch4ng3m3!1234securityReasons"
302
- await this.jellyfinClient.authenticate(username, password)
303
- this.updateResult(0, "success")
304
-
305
- // Generate API key
306
- this.updateResult(1, "configuring")
307
- const apiKey = await this.jellyfinClient.createApiKey("easiarr")
308
- if (!apiKey) {
309
- throw new Error("Failed to create API key")
310
- }
311
- this.updateResult(1, "success", `Key: ${apiKey.substring(0, 8)}...`)
312
-
313
- // Save to .env
314
- this.updateResult(2, "configuring")
315
- env["API_KEY_JELLYFIN"] = apiKey
316
- writeEnvSync(env)
317
- this.updateResult(2, "success", "Saved as API_KEY_JELLYFIN")
318
- } catch (error) {
319
- const current = this.results.findIndex((r) => r.status === "configuring")
320
- if (current >= 0) {
321
- this.updateResult(current, "error", error instanceof Error ? error.message : String(error))
322
- }
323
- }
324
-
325
- this.currentStep = "done"
326
- this.refreshContent()
327
- }
328
- }