@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
package/src/utils/arch.ts DELETED
@@ -1,78 +0,0 @@
1
- /**
2
- * Architecture Detection Utility
3
- * Detects system architecture and checks app compatibility
4
- */
5
-
6
- import type { AppDefinition, Architecture } from "~/config/schema"
7
-
8
- /**
9
- * Get the current system architecture
10
- */
11
- export function getSystemArch(): Architecture {
12
- const arch = process.arch
13
- switch (arch) {
14
- case "x64":
15
- case "ia32":
16
- return "x64"
17
- case "arm64":
18
- return "arm64"
19
- case "arm":
20
- return "arm32"
21
- default:
22
- return "x64" // Default to x64 for unknown
23
- }
24
- }
25
-
26
- /**
27
- * Check if an app is compatible with the given architecture
28
- * Returns true if compatible (no issues), false if deprecated/broken
29
- */
30
- export function isAppCompatible(app: AppDefinition, arch?: Architecture): boolean {
31
- const systemArch = arch ?? getSystemArch()
32
-
33
- if (!app.arch) {
34
- return true // No arch restrictions = supports all
35
- }
36
-
37
- // Check if explicitly deprecated
38
- if (app.arch.deprecated?.includes(systemArch)) {
39
- return false
40
- }
41
-
42
- // If supported list exists, check if current arch is in it
43
- if (app.arch.supported && !app.arch.supported.includes(systemArch)) {
44
- return false
45
- }
46
-
47
- return true
48
- }
49
-
50
- /**
51
- * Get warning message for an app on the current architecture
52
- * Returns null if no warning
53
- */
54
- export function getArchWarning(app: AppDefinition, arch?: Architecture): string | null {
55
- const systemArch = arch ?? getSystemArch()
56
-
57
- if (!app.arch) {
58
- return null
59
- }
60
-
61
- if (app.arch.deprecated?.includes(systemArch)) {
62
- return app.arch.warning || `${app.name} has deprecated support for ${systemArch}`
63
- }
64
-
65
- if (app.arch.supported && !app.arch.supported.includes(systemArch)) {
66
- return `${app.name} does not support ${systemArch} architecture`
67
- }
68
-
69
- return null
70
- }
71
-
72
- /**
73
- * Check if app is deprecated (but might still work)
74
- */
75
- export function isAppDeprecated(app: AppDefinition, arch?: Architecture): boolean {
76
- const systemArch = arch ?? getSystemArch()
77
- return app.arch?.deprecated?.includes(systemArch) ?? false
78
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Browser Utilities
3
- * Open URLs in the default browser
4
- */
5
-
6
- import { platform } from "node:os"
7
-
8
- import { $ } from "bun"
9
-
10
- /**
11
- * Open a URL in the default browser
12
- */
13
- export async function openUrl(url: string): Promise<void> {
14
- const os = platform()
15
-
16
- try {
17
- if (os === "linux") {
18
- await $`xdg-open ${url}`.quiet()
19
- } else if (os === "darwin") {
20
- await $`open ${url}`.quiet()
21
- } else if (os === "win32") {
22
- await $`cmd /c start ${url}`.quiet()
23
- }
24
- } catch {
25
- // Silently fail if browser can't be opened
26
- }
27
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * Download Category Utilities
3
- * Shared category mappings for *arr apps and download clients
4
- */
5
-
6
- import type { AppId } from "~/config/schema"
7
-
8
- // Category info for each *arr app
9
- export interface CategoryInfo {
10
- name: string
11
- savePath: string
12
- fieldName: string // Field name used in *arr download client config
13
- }
14
-
15
- // Master mapping of app IDs to their download categories
16
- const CATEGORY_MAP: Partial<Record<AppId, CategoryInfo>> = {
17
- radarr: { name: "movies", savePath: "/data/torrents/movies", fieldName: "movieCategory" },
18
- sonarr: { name: "tv", savePath: "/data/torrents/tv", fieldName: "tvCategory" },
19
- lidarr: { name: "music", savePath: "/data/torrents/music", fieldName: "musicCategory" },
20
- readarr: { name: "books", savePath: "/data/torrents/books", fieldName: "bookCategory" },
21
- whisparr: { name: "adult", savePath: "/data/torrents/adult", fieldName: "tvCategory" },
22
- mylar3: { name: "comics", savePath: "/data/torrents/comics", fieldName: "category" },
23
- }
24
-
25
- /**
26
- * Get category name for an app (e.g., "movies" for radarr)
27
- */
28
- export function getCategoryForApp(appId: AppId): string {
29
- return CATEGORY_MAP[appId]?.name ?? "default"
30
- }
31
-
32
- /**
33
- * Get the field name used in *arr download client config for category
34
- * (e.g., "movieCategory" for radarr, "tvCategory" for sonarr)
35
- */
36
- export function getCategoryFieldName(appId: AppId): string {
37
- return CATEGORY_MAP[appId]?.fieldName ?? "category"
38
- }
39
-
40
- /**
41
- * Get the save path for an app's downloads (e.g., "/data/torrents/movies" for radarr)
42
- */
43
- export function getCategorySavePath(appId: AppId): string {
44
- return CATEGORY_MAP[appId]?.savePath ?? "/data/torrents"
45
- }
46
-
47
- /**
48
- * Get full category info for an app
49
- */
50
- export function getCategoryInfo(appId: AppId): CategoryInfo | undefined {
51
- return CATEGORY_MAP[appId]
52
- }
53
-
54
- /**
55
- * Get all category infos for a list of enabled app IDs
56
- */
57
- export function getCategoriesForApps(appIds: AppId[]): CategoryInfo[] {
58
- return appIds
59
- .map((id) => CATEGORY_MAP[id])
60
- .filter((info): info is CategoryInfo => info !== undefined)
61
- }
@@ -1,84 +0,0 @@
1
- /**
2
- * Debug logging utility for easiarr
3
- *
4
- * Enable debug logging via:
5
- * - CLI flag: easiarr --debug or -d
6
- * - Environment variable: EASIARR_DEBUG=1 bun dev
7
- */
8
-
9
- import { appendFileSync, writeFileSync } from "node:fs"
10
-
11
- import { getConfigPath } from "./paths"
12
-
13
- // Check CLI args for --debug flag
14
- const hasDebugFlag = process.argv.includes("--debug") || process.argv.includes("-d")
15
- const hasEnvDebug = process.env.EASIARR_DEBUG === "1" || process.env.EASIARR_DEBUG === "true"
16
-
17
- export const DEBUG_ENABLED = hasDebugFlag || hasEnvDebug
18
-
19
- // Save debug log to XDG config dir like other config files
20
- const logFile = getConfigPath("debug.log")
21
-
22
- /**
23
- * Initialize debug mode - clears old log file
24
- */
25
- export function initDebug(): void {
26
- if (!DEBUG_ENABLED) return
27
- try {
28
- writeFileSync(logFile, `=== easiarr Debug Log - ${new Date().toISOString()} ===\n`)
29
- } catch {
30
- // Ignore
31
- }
32
- }
33
-
34
- /**
35
- * Sanitize sensitive fields from log messages
36
- * Redacts passwords, tokens, API keys, secrets, and credentials
37
- */
38
- export function sanitizeMessage(message: string): string {
39
- // Match common sensitive field names in JSON format
40
- // Covers: passwords, tokens, API keys, secrets, credentials, auth data
41
- return message.replace(
42
- /"(password|passwordConfirmation|Password|Pw|passwd|pass|apiKey|api_key|ApiKey|API_KEY|token|accessToken|access_token|refreshToken|refresh_token|bearerToken|jwtToken|jwt|secret|secretKey|secret_key|privateKey|private_key|credential|auth|authorization|authToken|client_secret|clientSecret|WIREGUARD_PRIVATE_KEY|TUNNEL_TOKEN|USERNAME_VPN|PASSWORD_VPN)":\s*"[^"]*"/gi,
43
- '"$1":"[REDACTED]"'
44
- )
45
- }
46
-
47
- /**
48
- * Log a debug message to debug.log file if debug mode is enabled
49
- * Automatically sanitizes sensitive data (passwords, tokens, etc.)
50
- */
51
- export function debugLog(category: string, message: string): void {
52
- if (!DEBUG_ENABLED) return
53
-
54
- const timestamp = new Date().toISOString()
55
- const sanitizedMessage = sanitizeMessage(message)
56
- const line = `[${timestamp}] [${category}] ${sanitizedMessage}\n`
57
- try {
58
- appendFileSync(logFile, line)
59
- } catch {
60
- // Ignore logging errors
61
- }
62
- }
63
-
64
- /**
65
- * Log API request details for debugging
66
- */
67
- export function debugRequest(method: string, url: string, body?: unknown): void {
68
- if (!DEBUG_ENABLED) return
69
- debugLog("API", `${method} ${url}`)
70
- if (body) {
71
- debugLog("API", `Body: ${JSON.stringify(body, null, 2)}`)
72
- }
73
- }
74
-
75
- /**
76
- * Log API response details for debugging
77
- */
78
- export function debugResponse(status: number, url: string, body?: string): void {
79
- if (!DEBUG_ENABLED) return
80
- debugLog("API", `Response ${status} from ${url}`)
81
- if (body && body.length < 2000) {
82
- debugLog("API", `Response Body: ${body}`)
83
- }
84
- }
package/src/utils/env.ts DELETED
@@ -1,138 +0,0 @@
1
- /**
2
- * Environment File Utilities
3
- * Shared functions for reading/writing .env files
4
- */
5
-
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
7
- import { readFile, writeFile } from "node:fs/promises"
8
- import { networkInterfaces } from "node:os"
9
-
10
- import { getComposePath } from "~/config/manager"
11
-
12
- /**
13
- * Get the local IP address of the Docker host
14
- * Returns the first non-internal IPv4 address found
15
- */
16
- export function getLocalIp(): string {
17
- const nets = networkInterfaces()
18
-
19
- for (const name of Object.keys(nets)) {
20
- const interfaces = nets[name]
21
- if (!interfaces) continue
22
-
23
- for (const net of interfaces) {
24
- // Skip internal (loopback) and non-IPv4 addresses
25
- if (net.family === "IPv4" && !net.internal) {
26
- return net.address
27
- }
28
- }
29
- }
30
-
31
- return "localhost"
32
- }
33
-
34
- /**
35
- * Get the path to the .env file
36
- */
37
- export function getEnvPath(): string {
38
- return getComposePath().replace("docker-compose.yml", ".env")
39
- }
40
-
41
- /**
42
- * Parse an .env file into a key-value object
43
- * Preserves existing values and handles multi-part values (e.g., with = in them)
44
- */
45
- export function parseEnvFile(content: string): Record<string, string> {
46
- const env: Record<string, string> = {}
47
-
48
- for (const line of content.split("\n")) {
49
- const trimmed = line.trim()
50
- if (!trimmed || trimmed.startsWith("#")) continue
51
-
52
- const [key, ...val] = trimmed.split("=")
53
- if (key && val.length > 0) {
54
- env[key.trim()] = val.join("=").trim()
55
- }
56
- }
57
-
58
- return env
59
- }
60
-
61
- /**
62
- * Serialize an env object to .env file format
63
- */
64
- export function serializeEnv(env: Record<string, string>): string {
65
- return Object.entries(env)
66
- .map(([k, v]) => `${k}=${v}`)
67
- .join("\n")
68
- }
69
-
70
- /**
71
- * Read the .env file and return parsed key-value object
72
- * Returns empty object if file doesn't exist
73
- */
74
- export function readEnvSync(): Record<string, string> {
75
- const envPath = getEnvPath()
76
- if (!existsSync(envPath)) return {}
77
-
78
- try {
79
- const content = readFileSync(envPath, "utf-8")
80
- return parseEnvFile(content)
81
- } catch {
82
- return {}
83
- }
84
- }
85
-
86
- /**
87
- * Write to .env file synchronously, merging with existing values
88
- * Preserves existing keys not in the updates object
89
- */
90
- export function writeEnvSync(updates: Record<string, string>): void {
91
- const envPath = getEnvPath()
92
- const current = readEnvSync()
93
-
94
- // Merge updates into current
95
- const merged = { ...current, ...updates }
96
-
97
- // Write back
98
- const content = serializeEnv(merged)
99
- writeFileSync(envPath, content, "utf-8")
100
- }
101
-
102
- /**
103
- * Read the .env file asynchronously
104
- */
105
- export async function readEnv(): Promise<Record<string, string>> {
106
- const envPath = getEnvPath()
107
- if (!existsSync(envPath)) return {}
108
-
109
- try {
110
- const content = await readFile(envPath, "utf-8")
111
- return parseEnvFile(content)
112
- } catch {
113
- return {}
114
- }
115
- }
116
-
117
- /**
118
- * Write to .env file, merging with existing values
119
- * Preserves existing keys not in the updates object
120
- */
121
- export async function updateEnv(updates: Record<string, string>): Promise<void> {
122
- const envPath = getEnvPath()
123
- const current = await readEnv()
124
-
125
- // Merge updates into current
126
- const merged = { ...current, ...updates }
127
-
128
- // Write back
129
- const content = serializeEnv(merged)
130
- await writeFile(envPath, content, "utf-8")
131
- }
132
-
133
- /**
134
- * Get a specific value from .env
135
- */
136
- export function getEnvValue(key: string): string | undefined {
137
- return readEnvSync()[key]
138
- }
@@ -1,8 +0,0 @@
1
- export * from "./env"
2
- export * from "./debug"
3
- export * from "./browser"
4
- export * from "./categories"
5
- export * from "./unraid"
6
- export * from "./update-checker"
7
- export * from "./url-utils"
8
- export * from "./password"
package/src/utils/logs.ts DELETED
@@ -1,119 +0,0 @@
1
- /**
2
- * Log File Management Utilities
3
- * Handles saving and managing container logs to XDG config dir
4
- */
5
-
6
- import { mkdir, readdir, stat, writeFile } from "node:fs/promises"
7
- import { join } from "node:path"
8
-
9
- import { debugLog } from "./debug"
10
- import { getConfigPath } from "./paths"
11
-
12
- const LOGS_DIR = getConfigPath("logs")
13
-
14
- /**
15
- * Get the logs directory path for a specific app
16
- */
17
- export function getLogPath(appId: string): string {
18
- return join(LOGS_DIR, appId)
19
- }
20
-
21
- /**
22
- * Ensure the logs directory exists for an app
23
- */
24
- async function ensureLogDir(appId: string): Promise<string> {
25
- const logDir = getLogPath(appId)
26
- await mkdir(logDir, { recursive: true })
27
- return logDir
28
- }
29
-
30
- /**
31
- * Generate a timestamped log filename
32
- */
33
- function generateLogFilename(): string {
34
- const now = new Date()
35
- const timestamp = now.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19)
36
- return `${timestamp}.log`
37
- }
38
-
39
- /**
40
- * Save container logs to a file
41
- * @returns The path to the saved log file
42
- */
43
- export async function saveLog(appId: string, content: string): Promise<string> {
44
- const logDir = await ensureLogDir(appId)
45
- const filename = generateLogFilename()
46
- const filepath = join(logDir, filename)
47
-
48
- await writeFile(filepath, content, "utf-8")
49
- debugLog("Logs", `Saved log for ${appId} to ${filepath}`)
50
-
51
- return filepath
52
- }
53
-
54
- /**
55
- * List saved logs for an app
56
- * @returns Array of log file info sorted by date (newest first)
57
- */
58
- export async function listSavedLogs(
59
- appId: string
60
- ): Promise<Array<{ filename: string; path: string; date: Date; size: number }>> {
61
- const logDir = getLogPath(appId)
62
-
63
- try {
64
- const files = await readdir(logDir)
65
- const logFiles = files.filter((f) => f.endsWith(".log"))
66
-
67
- const fileInfos = await Promise.all(
68
- logFiles.map(async (filename) => {
69
- const path = join(logDir, filename)
70
- const stats = await stat(path)
71
- return {
72
- filename,
73
- path,
74
- date: stats.mtime,
75
- size: stats.size,
76
- }
77
- })
78
- )
79
-
80
- // Sort by date, newest first
81
- return fileInfos.sort((a, b) => b.date.getTime() - a.date.getTime())
82
- } catch {
83
- // Directory doesn't exist yet
84
- return []
85
- }
86
- }
87
-
88
- /**
89
- * Get the base logs directory path
90
- */
91
- export function getLogsBaseDir(): string {
92
- return LOGS_DIR
93
- }
94
-
95
- /**
96
- * Format bytes to human-readable size
97
- */
98
- export function formatBytes(bytes: number): string {
99
- if (bytes < 1024) return `${bytes} B`
100
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
101
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
102
- }
103
-
104
- /**
105
- * Format relative time (e.g., "2 hours ago")
106
- */
107
- export function formatRelativeTime(date: Date): string {
108
- const now = new Date()
109
- const diffMs = now.getTime() - date.getTime()
110
- const diffSecs = Math.floor(diffMs / 1000)
111
- const diffMins = Math.floor(diffSecs / 60)
112
- const diffHours = Math.floor(diffMins / 60)
113
- const diffDays = Math.floor(diffHours / 24)
114
-
115
- if (diffDays > 0) return `${diffDays}d ago`
116
- if (diffHours > 0) return `${diffHours}h ago`
117
- if (diffMins > 0) return `${diffMins}m ago`
118
- return "just now"
119
- }
@@ -1,102 +0,0 @@
1
- /**
2
- * Migration: Rename env variables to new format
3
- *
4
- * OLD: GLOBAL_PASSWORD, QBITTORRENT_USER, etc.
5
- * NEW: PASSWORD_GLOBAL, USERNAME_QBITTORRENT, etc.
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
- import { parseEnvFile, serializeEnv } from "~/utils/env"
14
-
15
- const ENV_FILE = join(homedir(), ".easiarr", ".env")
16
-
17
- export const name = "rename-env-variables"
18
-
19
- const renames: [string, string][] = [
20
- // Global credentials
21
- ["GLOBAL_PASSWORD", "PASSWORD_GLOBAL"],
22
- ["GLOBAL_USERNAME", "USERNAME_GLOBAL"],
23
- // qBittorrent
24
- ["QBITTORRENT_PASSWORD", "PASSWORD_QBITTORRENT"],
25
- ["QBITTORRENT_USER", "USERNAME_QBITTORRENT"],
26
- ["QBITTORRENT_PASS", "PASSWORD_QBITTORRENT"], // Legacy alias
27
- // Portainer
28
- ["PORTAINER_PASSWORD", "PASSWORD_PORTAINER"],
29
- // PostgreSQL
30
- ["POSTGRESQL_USERNAME", "USERNAME_POSTGRESQL"],
31
- ["POSTGRESQL_PASSWORD", "PASSWORD_POSTGRESQL"],
32
- // VPN
33
- ["VPN_USERNAME", "USERNAME_VPN"],
34
- ["VPN_PASSWORD", "PASSWORD_VPN"],
35
- ]
36
-
37
- export function up(): boolean {
38
- if (!existsSync(ENV_FILE)) {
39
- debugLog("Migrations", "No .env file found, skipping migration")
40
- return false
41
- }
42
-
43
- try {
44
- const content = readFileSync(ENV_FILE, "utf-8")
45
- const env = parseEnvFile(content)
46
- let changed = false
47
-
48
- for (const [oldKey, newKey] of renames) {
49
- if (env[oldKey] !== undefined && env[newKey] === undefined) {
50
- env[newKey] = env[oldKey]
51
- delete env[oldKey]
52
- changed = true
53
- debugLog("Migrations", `Renamed ${oldKey} → ${newKey}`)
54
- }
55
- }
56
-
57
- if (changed) {
58
- writeFileSync(ENV_FILE, serializeEnv(env), "utf-8")
59
- debugLog("Migrations", "Migration completed: env variables renamed")
60
- return true
61
- }
62
-
63
- debugLog("Migrations", "No changes needed")
64
- return false
65
- } catch (e) {
66
- debugLog("Migrations", `Migration error: ${e}`)
67
- return false
68
- }
69
- }
70
-
71
- export function down(): boolean {
72
- if (!existsSync(ENV_FILE)) {
73
- return false
74
- }
75
-
76
- try {
77
- const content = readFileSync(ENV_FILE, "utf-8")
78
- const env = parseEnvFile(content)
79
- let changed = false
80
-
81
- for (const [oldKey, newKey] of renames) {
82
- // Reverse operation: restore oldKey from newKey
83
- if (env[newKey] !== undefined && env[oldKey] === undefined) {
84
- env[oldKey] = env[newKey]
85
- delete env[newKey]
86
- changed = true
87
- debugLog("Migrations", `Rolled back ${newKey} → ${oldKey}`)
88
- }
89
- }
90
-
91
- if (changed) {
92
- writeFileSync(ENV_FILE, serializeEnv(env), "utf-8")
93
- debugLog("Migrations", "Rollback completed: env variables restored")
94
- return true
95
- }
96
-
97
- return false
98
- } catch (e) {
99
- debugLog("Migrations", `Rollback error: ${e}`)
100
- return false
101
- }
102
- }