@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,91 +0,0 @@
1
- /**
2
- * Migration: rename easiarr-status to easiarr
3
- *
4
- * OLD: { "id": "easiarr-status", "enabled": true }
5
- * NEW: { "id": "easiarr", "enabled": true }
6
- */
7
-
8
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
9
- import { homedir } from "node:os"
10
- import { join } from "node:path"
11
-
12
- import { debugLog } from "~/utils/debug"
13
-
14
- const CONFIG_FILE = join(homedir(), ".easiarr", "config.json")
15
-
16
- export const name = "rename_easiarr_status"
17
-
18
- export function up(): boolean {
19
- if (!existsSync(CONFIG_FILE)) {
20
- debugLog("Migrations", "No config.json file found, skipping migration")
21
- return false
22
- }
23
-
24
- try {
25
- const content = readFileSync(CONFIG_FILE, "utf-8")
26
- const config = JSON.parse(content)
27
-
28
- if (!config.apps || !Array.isArray(config.apps)) {
29
- debugLog("Migrations", "No apps array in config, skipping migration")
30
- return false
31
- }
32
-
33
- let changed = false
34
-
35
- for (const app of config.apps) {
36
- if (app.id === "easiarr-status") {
37
- app.id = "easiarr"
38
- changed = true
39
- debugLog("Migrations", "Renamed easiarr-status → easiarr")
40
- }
41
- }
42
-
43
- if (changed) {
44
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8")
45
- debugLog("Migrations", "Migration completed: app id renamed")
46
- return true
47
- }
48
-
49
- debugLog("Migrations", "No changes needed")
50
- return false
51
- } catch (e) {
52
- debugLog("Migrations", `Migration error: ${e}`)
53
- return false
54
- }
55
- }
56
-
57
- export function down(): boolean {
58
- if (!existsSync(CONFIG_FILE)) {
59
- return false
60
- }
61
-
62
- try {
63
- const content = readFileSync(CONFIG_FILE, "utf-8")
64
- const config = JSON.parse(content)
65
-
66
- if (!config.apps || !Array.isArray(config.apps)) {
67
- return false
68
- }
69
-
70
- let changed = false
71
-
72
- for (const app of config.apps) {
73
- if (app.id === "easiarr") {
74
- app.id = "easiarr-status"
75
- changed = true
76
- debugLog("Migrations", "Rolled back easiarr → easiarr-status")
77
- }
78
- }
79
-
80
- if (changed) {
81
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8")
82
- debugLog("Migrations", "Rollback completed: app id restored")
83
- return true
84
- }
85
-
86
- return false
87
- } catch (e) {
88
- debugLog("Migrations", `Rollback error: ${e}`)
89
- return false
90
- }
91
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * Migration: remove cloudflare dns api token
3
- * Removes the unused CLOUDFLARE_DNS_API_TOKEN from .env
4
- */
5
-
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
7
-
8
- import { debugLog } from "~/utils/debug"
9
- import { getEnvPath } from "~/utils/env"
10
-
11
- export const name = "remove_cloudflare_dns_api_token"
12
-
13
- export function up(): boolean {
14
- debugLog("Migrations", "Running migration: remove_cloudflare_dns_api_token")
15
-
16
- const envPath = getEnvPath()
17
- if (!existsSync(envPath)) {
18
- debugLog("Migrations", ".env file not found, skipping")
19
- return true
20
- }
21
-
22
- const content = readFileSync(envPath, "utf-8")
23
-
24
- // Check if CLOUDFLARE_DNS_API_TOKEN exists
25
- if (!content.includes("CLOUDFLARE_DNS_API_TOKEN")) {
26
- debugLog("Migrations", "CLOUDFLARE_DNS_API_TOKEN not found, skipping")
27
- return true
28
- }
29
-
30
- // Remove the line containing CLOUDFLARE_DNS_API_TOKEN
31
- const lines = content.split("\n")
32
- const filtered = lines.filter((line) => !line.startsWith("CLOUDFLARE_DNS_API_TOKEN="))
33
- const newContent = filtered.join("\n")
34
-
35
- writeFileSync(envPath, newContent, "utf-8")
36
- debugLog("Migrations", "Removed CLOUDFLARE_DNS_API_TOKEN from .env")
37
-
38
- return true
39
- }
40
-
41
- export function down(): boolean {
42
- debugLog("Migrations", "Reverting migration: remove_cloudflare_dns_api_token")
43
- // No revert needed - the token was unused
44
- return true
45
- }
@@ -1,114 +0,0 @@
1
- /**
2
- * Migration: migrate to xdg
3
- *
4
- * Moves config from ~/.easiarr to $XDG_CONFIG_HOME/easiarr
5
- */
6
-
7
- import {
8
- copyFileSync,
9
- cpSync,
10
- existsSync,
11
- lstatSync,
12
- mkdirSync,
13
- readdirSync,
14
- rmSync,
15
- } from "node:fs"
16
- import { homedir } from "node:os"
17
- import { join } from "node:path"
18
-
19
- import { debugLog } from "~/utils/debug"
20
-
21
- export const name = "migrate_to_xdg"
22
-
23
- const OLD_DIR = join(homedir(), ".easiarr")
24
-
25
- function getNewDir(): string {
26
- const xdgConfigHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
27
- return join(xdgConfigHome, "easiarr")
28
- }
29
-
30
- export function up(): boolean {
31
- const newDir = getNewDir()
32
-
33
- // Skip if old directory doesn't exist
34
- if (!existsSync(OLD_DIR)) {
35
- debugLog("Migrations", "No legacy ~/.easiarr directory found, skipping migration")
36
- return false
37
- }
38
-
39
- // Skip if already at XDG location (same path)
40
- if (OLD_DIR === newDir) {
41
- debugLog("Migrations", "Old and new paths are identical, skipping")
42
- return false
43
- }
44
-
45
- // Skip if new directory already has config
46
- if (existsSync(join(newDir, "config.json"))) {
47
- debugLog("Migrations", "XDG config already exists, skipping migration")
48
- return false
49
- }
50
-
51
- debugLog("Migrations", `Migrating from ${OLD_DIR} to ${newDir}`)
52
-
53
- try {
54
- // Ensure target directory exists
55
- if (!existsSync(newDir)) {
56
- mkdirSync(newDir, { recursive: true })
57
- debugLog("Migrations", `Created directory ${newDir}`)
58
- }
59
-
60
- // Get all entries in old directory
61
- const entries = readdirSync(OLD_DIR)
62
-
63
- for (const entry of entries) {
64
- // Skip migrations file - it will be recreated in new location
65
- if (entry === ".migrations.json") continue
66
-
67
- const oldPath = join(OLD_DIR, entry)
68
- const newPath = join(newDir, entry)
69
-
70
- // Skip if entry already exists in new location
71
- if (existsSync(newPath)) {
72
- debugLog("Migrations", `Skipping ${entry}, already exists in new location`)
73
- continue
74
- }
75
-
76
- try {
77
- const stat = lstatSync(oldPath)
78
-
79
- if (stat.isDirectory()) {
80
- // Copy directory recursively (e.g., backups/)
81
- cpSync(oldPath, newPath, { recursive: true })
82
- debugLog("Migrations", `Copied directory ${entry} to XDG location`)
83
- } else if (stat.isFile()) {
84
- // Copy file
85
- copyFileSync(oldPath, newPath)
86
- debugLog("Migrations", `Copied ${entry} to XDG location`)
87
- }
88
- } catch {
89
- debugLog("Migrations", `Failed to copy ${entry}`)
90
- }
91
- }
92
-
93
- // Remove old directory completely
94
- try {
95
- rmSync(OLD_DIR, { recursive: true, force: true })
96
- debugLog("Migrations", `Removed legacy directory ${OLD_DIR}`)
97
- } catch {
98
- debugLog("Migrations", `Could not remove old directory`)
99
- }
100
-
101
- debugLog("Migrations", "XDG migration completed successfully")
102
- return true
103
- } catch (e) {
104
- debugLog("Migrations", `Migration error: ${e}`)
105
- return false
106
- }
107
- }
108
-
109
- export function down(): boolean {
110
- // This migration is not easily reversible
111
- // The old config location is deprecated
112
- debugLog("Migrations", "XDG migration rollback not supported")
113
- return false
114
- }
@@ -1,165 +0,0 @@
1
- /**
2
- * Migration Runner
3
- * Sequelize-style migrations with Unix timestamp naming
4
- *
5
- * Migration files: src/utils/migrations/{timestamp}_{name}.ts
6
- * Each migration exports: name, up()
7
- */
8
-
9
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
10
- import { homedir } from "node:os"
11
- import { join } from "node:path"
12
-
13
- import { debugLog } from "./debug"
14
-
15
- /**
16
- * Get the XDG config directory for easiarr
17
- */
18
- function getXdgConfigDir(): string {
19
- const xdgConfigHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
20
- return join(xdgConfigHome, "easiarr")
21
- }
22
-
23
- const EASIARR_DIR = getXdgConfigDir()
24
- const MIGRATIONS_FILE = join(EASIARR_DIR, ".migrations.json")
25
-
26
- interface MigrationState {
27
- applied: string[] // List of applied migration timestamps
28
- lastRun: string
29
- }
30
-
31
- interface Migration {
32
- timestamp: string
33
- name: string
34
- up: () => boolean
35
- down: () => boolean
36
- }
37
-
38
- /**
39
- * Ensure the easiarr directory exists
40
- */
41
- function ensureEasiarrDir(): void {
42
- if (!existsSync(EASIARR_DIR)) {
43
- mkdirSync(EASIARR_DIR, { recursive: true })
44
- }
45
- }
46
-
47
- /**
48
- * Get the current migration state
49
- */
50
- function getMigrationState(): MigrationState {
51
- if (!existsSync(MIGRATIONS_FILE)) {
52
- return { applied: [], lastRun: "" }
53
- }
54
-
55
- try {
56
- const content = readFileSync(MIGRATIONS_FILE, "utf-8")
57
- return JSON.parse(content) as MigrationState
58
- } catch {
59
- return { applied: [], lastRun: "" }
60
- }
61
- }
62
-
63
- /**
64
- * Save the migration state
65
- */
66
- function saveMigrationState(state: MigrationState): void {
67
- ensureEasiarrDir()
68
- state.lastRun = new Date().toISOString()
69
- writeFileSync(MIGRATIONS_FILE, JSON.stringify(state, null, 2), "utf-8")
70
- }
71
-
72
- /**
73
- * Load all migration modules
74
- */
75
- async function loadMigrations(): Promise<Migration[]> {
76
- const migrations: Migration[] = []
77
-
78
- // Import migrations directly - bundled at build time
79
- try {
80
- const m1 = await import("./migrations/1765626338_rename_env_variables")
81
- migrations.push({
82
- timestamp: "1765626338",
83
- name: m1.name,
84
- up: m1.up,
85
- down: m1.down,
86
- })
87
- } catch (e) {
88
- debugLog("Migrations", `Failed to load migration: ${e}`)
89
- }
90
-
91
- try {
92
- const m1765707135 = await import("./migrations/1765707135_rename_easiarr_status")
93
- migrations.push({
94
- timestamp: "1765707135",
95
- name: m1765707135.name,
96
- up: m1765707135.up,
97
- down: m1765707135.down,
98
- })
99
- } catch (e) {
100
- debugLog("Migrations", `Failed to load migration: ${e}`)
101
- }
102
-
103
- try {
104
- const m1765732722 = await import("./migrations/1765732722_remove_cloudflare_dns_api_token")
105
- migrations.push({
106
- timestamp: "1765732722",
107
- name: m1765732722.name,
108
- up: m1765732722.up,
109
- down: m1765732722.down,
110
- })
111
- } catch (e) {
112
- debugLog("Migrations", `Failed to load migration: ${e}`)
113
- }
114
-
115
- try {
116
- const m1769515650 = await import("./migrations/1769515650_migrate_to_xdg")
117
- migrations.push({
118
- timestamp: "1769515650",
119
- name: m1769515650.name,
120
- up: m1769515650.up,
121
- down: m1769515650.down,
122
- })
123
- } catch (e) {
124
- debugLog("Migrations", `Failed to load migration: ${e}`)
125
- }
126
-
127
- // Add future migrations here:
128
- // const m2 = await import("./migrations/1734xxxxxx_xxx")
129
- // migrations.push({ timestamp: "...", name: m2.name, up: m2.up })
130
-
131
- return migrations.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
132
- }
133
-
134
- /**
135
- * Run all pending migrations
136
- */
137
- export async function runMigrations(): Promise<void> {
138
- const state = getMigrationState()
139
- const migrations = await loadMigrations()
140
-
141
- let hasChanges = false
142
-
143
- for (const migration of migrations) {
144
- if (state.applied.includes(migration.timestamp)) {
145
- continue
146
- }
147
-
148
- debugLog("Migrations", `Running migration ${migration.timestamp}_${migration.name}`)
149
-
150
- try {
151
- migration.up()
152
- state.applied.push(migration.timestamp)
153
- hasChanges = true
154
- debugLog("Migrations", `Migration ${migration.timestamp} completed`)
155
- } catch (e) {
156
- debugLog("Migrations", `Migration ${migration.timestamp} failed: ${e}`)
157
- }
158
- }
159
-
160
- if (hasChanges) {
161
- saveMigrationState(state)
162
- } else {
163
- debugLog("Migrations", "No pending migrations")
164
- }
165
- }
@@ -1,42 +0,0 @@
1
- /**
2
- * Password Utilities
3
- * Helpers for password validation and transformation
4
- */
5
-
6
- /**
7
- * Ensures a password meets minimum length requirements by repeating it.
8
- * If the password is already long enough, returns it unchanged.
9
- *
10
- * @param password - The original password
11
- * @param minLength - Minimum required length
12
- * @returns Password padded to at least minLength characters
13
- */
14
- export function ensureMinPasswordLength(password: string, minLength: number): string {
15
- if (!password || password.length === 0) {
16
- throw new Error("Password cannot be empty")
17
- }
18
-
19
- if (password.length >= minLength) {
20
- return password
21
- }
22
-
23
- // Repeat the password until it meets minimum length
24
- let extendedPassword = password
25
- while (extendedPassword.length < minLength) {
26
- extendedPassword += password
27
- }
28
-
29
- // Return exactly minLength characters
30
- return extendedPassword.slice(0, minLength)
31
- }
32
-
33
- /**
34
- * Validates if a password meets minimum length requirements.
35
- *
36
- * @param password - The password to validate
37
- * @param minLength - Minimum required length
38
- * @returns true if password meets requirements
39
- */
40
- export function isPasswordValid(password: string, minLength: number): boolean {
41
- return password !== undefined && password !== null && password.length >= minLength
42
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * Path Utilities
3
- * Centralized XDG-compliant path resolution for easiarr config and data directories
4
- */
5
-
6
- import { homedir } from "os"
7
- import { join } from "path"
8
-
9
- export const xdgConfigHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
10
-
11
- /**
12
- * Get the XDG config directory for easiarr
13
- * Uses $XDG_CONFIG_HOME/easiarr or defaults to ~/.config/easiarr
14
- */
15
- export function getConfigDir(): string {
16
- return join(xdgConfigHome, "easiarr")
17
- }
18
-
19
- /**
20
- * Get a path within the config directory
21
- * @param segments - Path segments to join with config dir
22
- */
23
- export function getConfigPath(...segments: string[]): string {
24
- return join(getConfigDir(), ...segments)
25
- }
26
-
27
- /**
28
- * Legacy path for migration purposes only
29
- * @deprecated Use getConfigDir() instead
30
- */
31
- export function getLegacyConfigDir(): string {
32
- return join(homedir(), ".easiarr")
33
- }
@@ -1,103 +0,0 @@
1
- /**
2
- * Unraid Detection and Compatibility Utilities
3
- * Provides detection of Unraid environment and path adjustments
4
- */
5
-
6
- import { existsSync } from "node:fs"
7
- import { homedir } from "node:os"
8
- import { join } from "node:path"
9
-
10
- import { debugLog } from "./debug"
11
- import { getConfigDir as getXdgConfigDir } from "./paths"
12
-
13
- // Unraid-specific paths
14
- const UNRAID_IDENTIFIERS = [
15
- "/boot/config/plugins", // Unraid plugin directory
16
- "/etc/unraid-version", // Unraid version file
17
- "/var/local/emhttp", // Unraid emhttp directory
18
- ]
19
-
20
- const UNRAID_APPDATA_PATH = "/mnt/user/appdata"
21
- const COMPOSE_MANAGER_PLUGIN_PATH = "/boot/config/plugins/compose.manager"
22
-
23
- /**
24
- * Detect if running on Unraid OS
25
- * Checks for Unraid-specific filesystem paths
26
- */
27
- export function isUnraid(): boolean {
28
- for (const path of UNRAID_IDENTIFIERS) {
29
- if (existsSync(path)) {
30
- debugLog("Unraid", `Detected Unraid OS (found ${path})`)
31
- return true
32
- }
33
- }
34
- return false
35
- }
36
-
37
- /**
38
- * Get the default appdata path for Unraid
39
- * Returns /mnt/user/appdata/easiarr on Unraid, XDG config dir otherwise
40
- */
41
- export function getUnraidAppDataPath(): string {
42
- if (isUnraid()) {
43
- return join(UNRAID_APPDATA_PATH, "easiarr")
44
- }
45
- return getXdgConfigDir()
46
- }
47
-
48
- /**
49
- * Check if Docker Compose Manager plugin is installed
50
- * This plugin allows managing compose stacks via Unraid's web UI
51
- */
52
- export function hasComposeManager(): boolean {
53
- return existsSync(COMPOSE_MANAGER_PLUGIN_PATH)
54
- }
55
-
56
- /**
57
- * Get the recommended Compose Manager project directory
58
- * Compose Manager expects projects in /boot/config/plugins/compose.manager/projects/
59
- */
60
- export function getComposeManagerProjectPath(projectName: string = "easiarr"): string {
61
- return join(COMPOSE_MANAGER_PLUGIN_PATH, "projects", projectName)
62
- }
63
-
64
- /**
65
- * Get platform-appropriate default root directory
66
- * Adjusts paths for Unraid vs standard Linux/Mac
67
- */
68
- export function getDefaultRootDir(): string {
69
- if (isUnraid()) {
70
- // On Unraid, use /mnt/user/data for media and /mnt/user/appdata for configs
71
- return "/mnt/user/data"
72
- }
73
- // Standard path
74
- return join(homedir(), "easiarr")
75
- }
76
-
77
- /**
78
- * Get platform-appropriate config directory
79
- */
80
- export function getConfigDir(): string {
81
- if (isUnraid()) {
82
- return UNRAID_APPDATA_PATH
83
- }
84
- return getXdgConfigDir()
85
- }
86
-
87
- /**
88
- * Get Unraid-specific information for display
89
- */
90
- export function getUnraidInfo(): {
91
- isUnraid: boolean
92
- hasComposeManager: boolean
93
- appDataPath: string
94
- composeManagerPath: string | null
95
- } {
96
- const onUnraid = isUnraid()
97
- return {
98
- isUnraid: onUnraid,
99
- hasComposeManager: onUnraid ? hasComposeManager() : false,
100
- appDataPath: onUnraid ? UNRAID_APPDATA_PATH : getXdgConfigDir(),
101
- composeManagerPath: onUnraid && hasComposeManager() ? getComposeManagerProjectPath() : null,
102
- }
103
- }