@muhammedaksam/easiarr 1.3.5 → 1.4.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.
Files changed (111) hide show
  1. package/README.md +8 -5
  2. package/package.json +8 -6
  3. package/src/api/arr-api.ts +22 -9
  4. package/src/api/auto-setup-types.ts +1 -1
  5. package/src/api/base-api.ts +216 -0
  6. package/src/api/bazarr-api.ts +53 -112
  7. package/src/api/cloudflare-api.ts +92 -176
  8. package/src/api/custom-format-api.ts +15 -3
  9. package/src/api/grafana-api.ts +32 -116
  10. package/src/api/heimdall-api.ts +15 -83
  11. package/src/api/homarr-api.ts +33 -120
  12. package/src/api/huntarr-api.ts +47 -212
  13. package/src/api/index.ts +8 -0
  14. package/src/api/jellyfin-api.ts +49 -133
  15. package/src/api/jellyseerr-api.ts +45 -146
  16. package/src/api/maintainerr-api.ts +80 -350
  17. package/src/api/naming-config.ts +10 -2
  18. package/src/api/overseerr-api.ts +28 -149
  19. package/src/api/plex-api.ts +23 -73
  20. package/src/api/portainer-api.ts +41 -134
  21. package/src/api/profilarr-api.ts +16 -5
  22. package/src/api/prowlarr-api.ts +55 -426
  23. package/src/api/qbittorrent-api.ts +34 -73
  24. package/src/api/quality-profile-api.ts +7 -4
  25. package/src/api/tautulli-api.ts +13 -46
  26. package/src/api/uptime-kuma-api.ts +5 -4
  27. package/src/apps/categories.ts +1 -1
  28. package/src/apps/index.ts +2 -2
  29. package/src/apps/registry.ts +33 -13
  30. package/src/compose/caddy-config.ts +5 -4
  31. package/src/compose/generator.ts +38 -13
  32. package/src/compose/index.ts +7 -3
  33. package/src/compose/traefik-config.ts +5 -3
  34. package/src/config/bookmarks-generator.ts +21 -10
  35. package/src/config/defaults.ts +1 -1
  36. package/src/config/homepage-config.ts +13 -8
  37. package/src/config/index.ts +4 -1
  38. package/src/config/manager.ts +6 -5
  39. package/src/config/recyclarr-config.ts +5 -3
  40. package/src/config/schema.ts +1 -1
  41. package/src/config/slskd-config.ts +3 -2
  42. package/src/config/soularr-config.ts +1 -1
  43. package/src/data/lidarr-custom-formats.ts +1 -1
  44. package/src/data/trash-profiles.ts +8 -1
  45. package/src/docker/client.ts +34 -13
  46. package/src/docker/index.ts +18 -1
  47. package/src/index.ts +1 -1
  48. package/src/setup/actions/arr-common.ts +307 -0
  49. package/src/setup/actions/dashboards.ts +79 -0
  50. package/src/setup/actions/download-clients.ts +54 -0
  51. package/src/setup/actions/jellyseerr.ts +331 -0
  52. package/src/setup/actions/media-servers.ts +68 -0
  53. package/src/setup/actions/monitoring.ts +120 -0
  54. package/src/setup/actions/prowlarr.ts +137 -0
  55. package/src/setup/actions/utilities.ts +490 -0
  56. package/src/setup/index.ts +78 -0
  57. package/src/setup/types.ts +144 -0
  58. package/src/structure/manager.ts +3 -2
  59. package/src/ui/App.ts +12 -10
  60. package/src/ui/components/ApplicationSelector.ts +11 -8
  61. package/src/ui/components/CredentialsForm.ts +180 -0
  62. package/src/ui/components/DownloadClientForm.ts +114 -0
  63. package/src/ui/components/FileEditor.ts +4 -4
  64. package/src/ui/components/PageLayout.ts +13 -5
  65. package/src/ui/components/UpdateNotification.ts +3 -2
  66. package/src/ui/index.ts +7 -1
  67. package/src/ui/screens/AdvancedSettings.ts +19 -12
  68. package/src/ui/screens/ApiKeyViewer.ts +21 -10
  69. package/src/ui/screens/AppConfigurator.ts +156 -344
  70. package/src/ui/screens/AppManager.ts +18 -10
  71. package/src/ui/screens/ContainerControl.ts +14 -12
  72. package/src/ui/screens/LogsViewer.ts +17 -7
  73. package/src/ui/screens/MainMenu.ts +34 -23
  74. package/src/ui/screens/MonitorDashboard.ts +29 -14
  75. package/src/ui/screens/SecretsEditor.ts +11 -9
  76. package/src/ui/screens/SettingsScreen.ts +42 -14
  77. package/src/ui/screens/index.ts +19 -1
  78. package/src/ui/screens/setup/BaseAppSetupScreen.ts +375 -0
  79. package/src/ui/screens/setup/CloudflaredSetup.ts +460 -0
  80. package/src/ui/screens/setup/FullAutoSetup.ts +766 -0
  81. package/src/ui/screens/setup/HomepageSetup.ts +165 -0
  82. package/src/ui/screens/setup/JellyfinSetup.ts +328 -0
  83. package/src/ui/screens/setup/JellyseerrSetup.ts +242 -0
  84. package/src/ui/screens/{ProwlarrSetup.ts → setup/ProwlarrSetup.ts} +278 -401
  85. package/src/ui/screens/setup/QBittorrentSetup.ts +221 -0
  86. package/src/ui/screens/{QuickSetup.ts → setup/QuickSetup.ts} +22 -14
  87. package/src/ui/screens/setup/RecyclarrSetup.ts +270 -0
  88. package/src/ui/screens/{TRaSHProfileSetup.ts → setup/TRaSHProfileSetup.ts} +204 -218
  89. package/src/{util → utils}/arch.ts +1 -1
  90. package/src/utils/browser.ts +2 -1
  91. package/src/utils/categories.ts +4 -2
  92. package/src/utils/debug.ts +3 -10
  93. package/src/utils/env.ts +3 -2
  94. package/src/utils/index.ts +8 -0
  95. package/src/utils/logs.ts +5 -4
  96. package/src/utils/migrations/1765626338_rename_env_variables.ts +4 -3
  97. package/src/utils/migrations/1765707135_rename_easiarr_status.ts +3 -2
  98. package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +3 -2
  99. package/src/utils/migrations/1769515650_migrate_to_xdg.ts +10 -3
  100. package/src/utils/migrations.ts +2 -1
  101. package/src/utils/paths.ts +33 -0
  102. package/src/utils/unraid.ts +6 -4
  103. package/src/utils/update-checker.ts +4 -4
  104. package/src/utils/url-utils.ts +1 -1
  105. package/src/ui/screens/CloudflaredSetup.ts +0 -974
  106. package/src/ui/screens/FullAutoSetup.ts +0 -1719
  107. package/src/ui/screens/HomepageSetup.ts +0 -306
  108. package/src/ui/screens/JellyfinSetup.ts +0 -471
  109. package/src/ui/screens/JellyseerrSetup.ts +0 -612
  110. package/src/ui/screens/QBittorrentSetup.ts +0 -268
  111. package/src/ui/screens/RecyclarrSetup.ts +0 -418
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  > ⚠️ **Work In Progress** - This project is in early experimental development. Features may be incomplete, unstable, or change without notice.
12
12
 
13
- TUI tool for generating docker-compose files for the \*arr media ecosystem with 49 apps, TRaSH Guides best practices, VPN routing, and Traefik/Caddy reverse proxy support.
13
+ TUI tool for generating docker-compose files for the \*arr media ecosystem with 52 apps, TRaSH Guides best practices, VPN routing, and Traefik/Caddy reverse proxy support.
14
14
 
15
15
  A terminal-based wizard that helps you set up Radarr, Sonarr, Prowlarr, and other \*arr applications with Docker Compose, following best practices from [TRaSH Guides](https://trash-guides.info/).
16
16
 
@@ -23,7 +23,7 @@ A terminal-based wizard that helps you set up Radarr, Sonarr, Prowlarr, and othe
23
23
  - 🎮 **Container Control** - Start, stop, and restart containers directly from the TUI
24
24
  - 📋 **Container Logs Viewer** - View and save Docker container logs from the TUI
25
25
  - ⚙️ **App Management** - Add or remove apps from your stack with ease
26
- - 💾 **Persistent Configuration** - Settings saved to `~/.easiarr/config.json`
26
+ - 💾 **Persistent Configuration** - Settings saved to `$XDG_CONFIG_HOME/easiarr/config.json`
27
27
  - 🔀 **Reverse Proxy** - Traefik or Caddy support with automatic SSL
28
28
  - 🖥️ **Unraid Support** - Automatic OS detection and compatibility
29
29
 
@@ -56,7 +56,7 @@ bun run start
56
56
  - [Bun](https://bun.sh/) >= 1.0
57
57
  - [Docker](https://www.docker.com/) with Docker Compose v2
58
58
 
59
- ## Supported Applications (49 apps across 10 categories)
59
+ ## Supported Applications (52 apps across 10 categories)
60
60
 
61
61
  ### Media Management (Servarr)
62
62
 
@@ -79,6 +79,8 @@ bun run start
79
79
 
80
80
  - **qBittorrent** - BitTorrent client
81
81
  - **SABnzbd** - Usenet downloader
82
+ - **Slskd** - Soulseek client for music sharing
83
+ - **Soularr** - Lidarr integration for Soulseek
82
84
 
83
85
  ### Media Servers
84
86
 
@@ -103,6 +105,7 @@ bun run start
103
105
  - **Portainer** - Docker container management UI
104
106
  - **Huntarr** - Missing content manager for \*arr apps
105
107
  - **Unpackerr** - Archive extraction for \*arr apps
108
+ - **Maintainerr** - Automated media cleanup manager
106
109
  - **Recyclarr** - TRaSH Guides profile sync (CLI-based)
107
110
  - **Profilarr** - TRaSH Guides profile sync (Web UI)
108
111
  - **FileBot** - Media file renaming and automator
@@ -160,10 +163,10 @@ The wizard will automatically:
160
163
 
161
164
  ## Configuration
162
165
 
163
- easiarr stores its configuration in `~/.easiarr/`:
166
+ easiarr stores its configuration in `$XDG_CONFIG_HOME/easiarr/`:
164
167
 
165
168
  ```bash
166
- ~/.easiarr/
169
+ $XDG_CONFIG_HOME/easiarr/
167
170
  ├── config.json # Your easiarr configuration
168
171
  ├── docker-compose.yml # Generated Docker Compose file
169
172
  └── backups/ # Configuration backups
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muhammedaksam/easiarr",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "description": "TUI tool for generating docker-compose files for the *arr media ecosystem with 41 apps, TRaSH Guides best practices, VPN routing, and Traefik reverse proxy support",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -51,23 +51,25 @@
51
51
  "start": "bun run src/index.ts"
52
52
  },
53
53
  "devDependencies": {
54
- "@eslint/js": "^9.39.2",
54
+ "@eslint/js": "^10.0.1",
55
+ "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
55
56
  "@types/bcrypt": "^6.0.0",
56
57
  "@types/bun": "latest",
57
58
  "@types/jest": "^30.0.0",
58
- "@types/node": "^25.0.10",
59
- "eslint": "^9.39.2",
59
+ "@types/node": "^25.2.3",
60
+ "eslint": "^10.0.0",
61
+ "eslint-plugin-import": "^2.32.0",
60
62
  "jest": "^30.2.0",
61
63
  "jiti": "^2.6.1",
62
64
  "prettier": "^3.8.1",
63
65
  "ts-jest": "^29.4.6",
64
- "typescript-eslint": "^8.54.0"
66
+ "typescript-eslint": "^8.55.0"
65
67
  },
66
68
  "peerDependencies": {
67
69
  "typescript": "^5.9.3"
68
70
  },
69
71
  "dependencies": {
70
- "@opentui/core": "^0.1.75",
72
+ "@opentui/core": "^0.1.79",
71
73
  "bcrypt": "^6.0.0",
72
74
  "socket.io-client": "^4.8.3",
73
75
  "yaml": "^2.8.2"
@@ -3,7 +3,11 @@
3
3
  * Interacts with Radarr, Sonarr, Lidarr, Readarr, Whisparr APIs
4
4
  */
5
5
 
6
- import { debugLog } from "../utils/debug"
6
+ import type { NamingConfig } from "./naming-config"
7
+ import type { AppId } from "~/config/schema"
8
+ import { getCategoryFieldName, getCategoryForApp } from "~/utils/categories"
9
+ import { debugLog } from "~/utils/debug"
10
+ import { TRASH_NAMING_CONFIG } from "./naming-config"
7
11
 
8
12
  // Types for Root Folder API
9
13
  export interface RootFolder {
@@ -44,10 +48,6 @@ export interface RemotePathMapping {
44
48
  localPath: string
45
49
  }
46
50
 
47
- import type { AppId } from "../config/schema"
48
- import { getCategoryForApp, getCategoryFieldName } from "../utils/categories"
49
- import { TRASH_NAMING_CONFIG, type NamingConfig } from "./naming-config"
50
-
51
51
  // qBittorrent download client config
52
52
  export function createQBittorrentConfig(
53
53
  host: string,
@@ -82,7 +82,12 @@ export function createQBittorrentConfig(
82
82
  }
83
83
 
84
84
  // SABnzbd download client config
85
- export function createSABnzbdConfig(host: string, port: number, apiKey: string, appId?: AppId): DownloadClientConfig {
85
+ export function createSABnzbdConfig(
86
+ host: string,
87
+ port: number,
88
+ apiKey: string,
89
+ appId?: AppId
90
+ ): DownloadClientConfig {
86
91
  const category = appId ? getCategoryForApp(appId) : "default"
87
92
  const categoryField = appId ? getCategoryFieldName(appId) : "category"
88
93
 
@@ -214,7 +219,11 @@ export class ArrApiClient {
214
219
  return this.request<HostConfig>("/config/host")
215
220
  }
216
221
 
217
- async updateHostConfig(username: string, password: string, override = false): Promise<HostConfig | null> {
222
+ async updateHostConfig(
223
+ username: string,
224
+ password: string,
225
+ override = false
226
+ ): Promise<HostConfig | null> {
218
227
  // First get current config to preserve all other settings
219
228
  const currentConfig = await this.getHostConfig()
220
229
 
@@ -293,11 +302,15 @@ export class ArrApiClient {
293
302
  // 3. Update configuration
294
303
  await this.updateNamingConfig(newConfig)
295
304
  } catch (e) {
296
- throw new Error(`Failed to configure naming: ${e}`)
305
+ throw new Error(`Failed to configure naming: ${e}`, { cause: e })
297
306
  }
298
307
  }
299
308
 
300
- async addRemotePathMapping(host: string, remotePath: string, localPath: string): Promise<RemotePathMapping> {
309
+ async addRemotePathMapping(
310
+ host: string,
311
+ remotePath: string,
312
+ localPath: string
313
+ ): Promise<RemotePathMapping> {
301
314
  return this.request<RemotePathMapping>("/remotepathmapping", {
302
315
  method: "POST",
303
316
  body: JSON.stringify({ host, remotePath, localPath }),
@@ -3,7 +3,7 @@
3
3
  * Interfaces for auto-setup capability metadata and clients
4
4
  */
5
5
 
6
- import type { AppId } from "../config/schema"
6
+ import type { AppId } from "~/config/schema"
7
7
 
8
8
  /**
9
9
  * Describes an app's auto-setup capability
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Base API Client
3
+ * Abstract class providing shared fetch, logging, and error handling for all API clients.
4
+ */
5
+
6
+ import { debugLog } from "~/utils/debug"
7
+
8
+ export interface ApiResponse<T> {
9
+ success: boolean
10
+ data?: T
11
+ error?: string
12
+ status?: number
13
+ }
14
+
15
+ export abstract class BaseApiClient {
16
+ protected host: string
17
+ protected port: number
18
+ protected abstract readonly logPrefix: string
19
+
20
+ constructor(host: string, port: number) {
21
+ this.host = host
22
+ this.port = port
23
+ }
24
+
25
+ /**
26
+ * Base URL for the API (override in subclass if protocol differs)
27
+ */
28
+ protected get baseUrl(): string {
29
+ return `http://${this.host}:${this.port}`
30
+ }
31
+
32
+ /**
33
+ * Build API URL for a given endpoint
34
+ */
35
+ protected buildUrl(endpoint: string, basePath: string = ""): string {
36
+ return `${this.baseUrl}${basePath}${endpoint}`
37
+ }
38
+
39
+ /**
40
+ * Generic GET request with logging and error handling
41
+ */
42
+ protected async get<T>(
43
+ endpoint: string,
44
+ options: { basePath?: string; headers?: Record<string, string> } = {}
45
+ ): Promise<ApiResponse<T>> {
46
+ const url = this.buildUrl(endpoint, options.basePath)
47
+ try {
48
+ debugLog(this.logPrefix, `GET ${endpoint}`)
49
+ const response = await fetch(url, {
50
+ method: "GET",
51
+ headers: options.headers,
52
+ })
53
+ if (!response.ok) {
54
+ debugLog(this.logPrefix, `GET ${endpoint} failed: ${response.status}`)
55
+ return { success: false, status: response.status }
56
+ }
57
+ const data = (await response.json()) as T
58
+ return { success: true, data }
59
+ } catch (error) {
60
+ debugLog(this.logPrefix, `GET ${endpoint} error: ${error}`)
61
+ return { success: false, error: String(error) }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Generic GET request returning plain text
67
+ */
68
+ protected async getText(
69
+ endpoint: string,
70
+ options: { basePath?: string; headers?: Record<string, string> } = {}
71
+ ): Promise<ApiResponse<string>> {
72
+ const url = this.buildUrl(endpoint, options.basePath)
73
+ try {
74
+ debugLog(this.logPrefix, `GET (text) ${endpoint}`)
75
+ const response = await fetch(url, {
76
+ method: "GET",
77
+ headers: options.headers,
78
+ })
79
+ if (!response.ok) {
80
+ debugLog(this.logPrefix, `GET ${endpoint} failed: ${response.status}`)
81
+ return { success: false, status: response.status }
82
+ }
83
+ const data = await response.text()
84
+ return { success: true, data }
85
+ } catch (error) {
86
+ debugLog(this.logPrefix, `GET ${endpoint} error: ${error}`)
87
+ return { success: false, error: String(error) }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Generic POST request with logging and error handling
93
+ */
94
+ protected async post<T, B = unknown>(
95
+ endpoint: string,
96
+ body?: B,
97
+ options: { basePath?: string; headers?: Record<string, string> } = {}
98
+ ): Promise<ApiResponse<T>> {
99
+ const url = this.buildUrl(endpoint, options.basePath)
100
+ try {
101
+ debugLog(this.logPrefix, `POST ${endpoint}`)
102
+ const response = await fetch(url, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ ...options.headers,
107
+ },
108
+ body: body ? JSON.stringify(body) : undefined,
109
+ })
110
+ if (!response.ok) {
111
+ debugLog(this.logPrefix, `POST ${endpoint} failed: ${response.status}`)
112
+ return { success: false, status: response.status }
113
+ }
114
+ // Handle empty responses
115
+ const text = await response.text()
116
+ if (!text) {
117
+ return { success: true }
118
+ }
119
+ try {
120
+ const data = JSON.parse(text) as T
121
+ return { success: true, data }
122
+ } catch {
123
+ // Response was not JSON
124
+ return { success: true }
125
+ }
126
+ } catch (error) {
127
+ debugLog(this.logPrefix, `POST ${endpoint} error: ${error}`)
128
+ return { success: false, error: String(error) }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Generic PUT request with logging and error handling
134
+ */
135
+ protected async put<T, B = unknown>(
136
+ endpoint: string,
137
+ body?: B,
138
+ options: { basePath?: string; headers?: Record<string, string> } = {}
139
+ ): Promise<ApiResponse<T>> {
140
+ const url = this.buildUrl(endpoint, options.basePath)
141
+ try {
142
+ debugLog(this.logPrefix, `PUT ${endpoint}`)
143
+ const response = await fetch(url, {
144
+ method: "PUT",
145
+ headers: {
146
+ "Content-Type": "application/json",
147
+ ...options.headers,
148
+ },
149
+ body: body ? JSON.stringify(body) : undefined,
150
+ })
151
+ if (!response.ok) {
152
+ debugLog(this.logPrefix, `PUT ${endpoint} failed: ${response.status}`)
153
+ return { success: false, status: response.status }
154
+ }
155
+ const text = await response.text()
156
+ if (!text) {
157
+ return { success: true }
158
+ }
159
+ try {
160
+ const data = JSON.parse(text) as T
161
+ return { success: true, data }
162
+ } catch {
163
+ return { success: true }
164
+ }
165
+ } catch (error) {
166
+ debugLog(this.logPrefix, `PUT ${endpoint} error: ${error}`)
167
+ return { success: false, error: String(error) }
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Generic DELETE request with logging and error handling
173
+ */
174
+ protected async delete<T>(
175
+ endpoint: string,
176
+ options: { basePath?: string; headers?: Record<string, string> } = {}
177
+ ): Promise<ApiResponse<T>> {
178
+ const url = this.buildUrl(endpoint, options.basePath)
179
+ try {
180
+ debugLog(this.logPrefix, `DELETE ${endpoint}`)
181
+ const response = await fetch(url, {
182
+ method: "DELETE",
183
+ headers: options.headers,
184
+ })
185
+ if (!response.ok) {
186
+ debugLog(this.logPrefix, `DELETE ${endpoint} failed: ${response.status}`)
187
+ return { success: false, status: response.status }
188
+ }
189
+ const text = await response.text()
190
+ if (!text) {
191
+ return { success: true }
192
+ }
193
+ try {
194
+ const data = JSON.parse(text) as T
195
+ return { success: true, data }
196
+ } catch {
197
+ return { success: true }
198
+ }
199
+ } catch (error) {
200
+ debugLog(this.logPrefix, `DELETE ${endpoint} error: ${error}`)
201
+ return { success: false, error: String(error) }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Simple health check - override in subclass for custom logic
207
+ */
208
+ async isHealthy(): Promise<boolean> {
209
+ try {
210
+ const response = await fetch(this.baseUrl, { method: "HEAD" })
211
+ return response.ok
212
+ } catch {
213
+ return false
214
+ }
215
+ }
216
+ }