@muhammedaksam/easiarr 1.3.5 → 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.
- package/README.md +8 -5
- package/dist/index.js +29837 -0
- package/package.json +18 -16
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -508
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/bazarr-api.ts +0 -356
- package/src/api/cloudflare-api.ts +0 -577
- package/src/api/custom-format-api.ts +0 -212
- package/src/api/grafana-api.ts +0 -314
- package/src/api/heimdall-api.ts +0 -209
- package/src/api/homarr-api.ts +0 -296
- package/src/api/huntarr-api.ts +0 -622
- package/src/api/index.ts +0 -1
- package/src/api/jellyfin-api.ts +0 -473
- package/src/api/jellyseerr-api.ts +0 -660
- package/src/api/maintainerr-api.ts +0 -731
- package/src/api/naming-config.ts +0 -93
- package/src/api/overseerr-api.ts +0 -513
- package/src/api/plex-api.ts +0 -358
- package/src/api/portainer-api.ts +0 -467
- package/src/api/profilarr-api.ts +0 -380
- package/src/api/prowlarr-api.ts +0 -859
- package/src/api/qbittorrent-api.ts +0 -339
- package/src/api/quality-profile-api.ts +0 -269
- package/src/api/tautulli-api.ts +0 -277
- package/src/api/uptime-kuma-api.ts +0 -342
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1255
- package/src/compose/caddy-config.ts +0 -129
- package/src/compose/generator.ts +0 -262
- package/src/compose/index.ts +0 -3
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -185
- package/src/config/bookmarks-generator.ts +0 -149
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -376
- package/src/config/index.ts +0 -3
- package/src/config/manager.ts +0 -149
- package/src/config/recyclarr-config.ts +0 -179
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -85
- package/src/config/soularr-config.ts +0 -105
- package/src/config/trash-quality-definitions.ts +0 -213
- package/src/data/lidarr-custom-formats.ts +0 -127
- package/src/data/trash-profiles.ts +0 -326
- package/src/docker/client.ts +0 -291
- package/src/docker/index.ts +0 -1
- package/src/index.ts +0 -77
- package/src/structure/manager.ts +0 -128
- package/src/ui/App.ts +0 -109
- package/src/ui/components/ApplicationSelector.ts +0 -272
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -152
- package/src/ui/components/UpdateNotification.ts +0 -101
- package/src/ui/index.ts +0 -1
- package/src/ui/screens/AdvancedSettings.ts +0 -181
- package/src/ui/screens/ApiKeyViewer.ts +0 -500
- package/src/ui/screens/AppConfigurator.ts +0 -731
- package/src/ui/screens/AppManager.ts +0 -276
- package/src/ui/screens/CloudflaredSetup.ts +0 -974
- package/src/ui/screens/ContainerControl.ts +0 -579
- package/src/ui/screens/FullAutoSetup.ts +0 -1719
- package/src/ui/screens/HomepageSetup.ts +0 -306
- package/src/ui/screens/JellyfinSetup.ts +0 -471
- package/src/ui/screens/JellyseerrSetup.ts +0 -612
- package/src/ui/screens/LogsViewer.ts +0 -468
- package/src/ui/screens/MainMenu.ts +0 -288
- package/src/ui/screens/MonitorDashboard.ts +0 -866
- package/src/ui/screens/ProwlarrSetup.ts +0 -623
- package/src/ui/screens/QBittorrentSetup.ts +0 -268
- package/src/ui/screens/QuickSetup.ts +0 -1161
- package/src/ui/screens/RecyclarrSetup.ts +0 -418
- package/src/ui/screens/SecretsEditor.ts +0 -214
- package/src/ui/screens/SettingsScreen.ts +0 -605
- package/src/ui/screens/TRaSHProfileSetup.ts +0 -395
- package/src/ui/screens/index.ts +0 -4
- package/src/util/arch.ts +0 -78
- package/src/utils/browser.ts +0 -26
- package/src/utils/categories.ts +0 -59
- package/src/utils/debug.ts +0 -91
- package/src/utils/env.ts +0 -137
- package/src/utils/logs.ts +0 -118
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -101
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -90
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -44
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -107
- package/src/utils/migrations.ts +0 -164
- package/src/utils/password.ts +0 -42
- package/src/utils/unraid.ts +0 -101
- package/src/utils/update-checker.ts +0 -210
- package/src/utils/url-utils.ts +0 -38
package/src/utils/env.ts
DELETED
|
@@ -1,137 +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 { writeFile, readFile } from "node:fs/promises"
|
|
8
|
-
import { networkInterfaces } from "node:os"
|
|
9
|
-
import { getComposePath } from "../config/manager"
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Get the local IP address of the Docker host
|
|
13
|
-
* Returns the first non-internal IPv4 address found
|
|
14
|
-
*/
|
|
15
|
-
export function getLocalIp(): string {
|
|
16
|
-
const nets = networkInterfaces()
|
|
17
|
-
|
|
18
|
-
for (const name of Object.keys(nets)) {
|
|
19
|
-
const interfaces = nets[name]
|
|
20
|
-
if (!interfaces) continue
|
|
21
|
-
|
|
22
|
-
for (const net of interfaces) {
|
|
23
|
-
// Skip internal (loopback) and non-IPv4 addresses
|
|
24
|
-
if (net.family === "IPv4" && !net.internal) {
|
|
25
|
-
return net.address
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return "localhost"
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get the path to the .env file
|
|
35
|
-
*/
|
|
36
|
-
export function getEnvPath(): string {
|
|
37
|
-
return getComposePath().replace("docker-compose.yml", ".env")
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Parse an .env file into a key-value object
|
|
42
|
-
* Preserves existing values and handles multi-part values (e.g., with = in them)
|
|
43
|
-
*/
|
|
44
|
-
export function parseEnvFile(content: string): Record<string, string> {
|
|
45
|
-
const env: Record<string, string> = {}
|
|
46
|
-
|
|
47
|
-
for (const line of content.split("\n")) {
|
|
48
|
-
const trimmed = line.trim()
|
|
49
|
-
if (!trimmed || trimmed.startsWith("#")) continue
|
|
50
|
-
|
|
51
|
-
const [key, ...val] = trimmed.split("=")
|
|
52
|
-
if (key && val.length > 0) {
|
|
53
|
-
env[key.trim()] = val.join("=").trim()
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return env
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Serialize an env object to .env file format
|
|
62
|
-
*/
|
|
63
|
-
export function serializeEnv(env: Record<string, string>): string {
|
|
64
|
-
return Object.entries(env)
|
|
65
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
66
|
-
.join("\n")
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Read the .env file and return parsed key-value object
|
|
71
|
-
* Returns empty object if file doesn't exist
|
|
72
|
-
*/
|
|
73
|
-
export function readEnvSync(): Record<string, string> {
|
|
74
|
-
const envPath = getEnvPath()
|
|
75
|
-
if (!existsSync(envPath)) return {}
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
const content = readFileSync(envPath, "utf-8")
|
|
79
|
-
return parseEnvFile(content)
|
|
80
|
-
} catch {
|
|
81
|
-
return {}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Write to .env file synchronously, merging with existing values
|
|
87
|
-
* Preserves existing keys not in the updates object
|
|
88
|
-
*/
|
|
89
|
-
export function writeEnvSync(updates: Record<string, string>): void {
|
|
90
|
-
const envPath = getEnvPath()
|
|
91
|
-
const current = readEnvSync()
|
|
92
|
-
|
|
93
|
-
// Merge updates into current
|
|
94
|
-
const merged = { ...current, ...updates }
|
|
95
|
-
|
|
96
|
-
// Write back
|
|
97
|
-
const content = serializeEnv(merged)
|
|
98
|
-
writeFileSync(envPath, content, "utf-8")
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Read the .env file asynchronously
|
|
103
|
-
*/
|
|
104
|
-
export async function readEnv(): Promise<Record<string, string>> {
|
|
105
|
-
const envPath = getEnvPath()
|
|
106
|
-
if (!existsSync(envPath)) return {}
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
const content = await readFile(envPath, "utf-8")
|
|
110
|
-
return parseEnvFile(content)
|
|
111
|
-
} catch {
|
|
112
|
-
return {}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Write to .env file, merging with existing values
|
|
118
|
-
* Preserves existing keys not in the updates object
|
|
119
|
-
*/
|
|
120
|
-
export async function updateEnv(updates: Record<string, string>): Promise<void> {
|
|
121
|
-
const envPath = getEnvPath()
|
|
122
|
-
const current = await readEnv()
|
|
123
|
-
|
|
124
|
-
// Merge updates into current
|
|
125
|
-
const merged = { ...current, ...updates }
|
|
126
|
-
|
|
127
|
-
// Write back
|
|
128
|
-
const content = serializeEnv(merged)
|
|
129
|
-
await writeFile(envPath, content, "utf-8")
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Get a specific value from .env
|
|
134
|
-
*/
|
|
135
|
-
export function getEnvValue(key: string): string | undefined {
|
|
136
|
-
return readEnvSync()[key]
|
|
137
|
-
}
|
package/src/utils/logs.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Log File Management Utilities
|
|
3
|
-
* Handles saving and managing container logs to ~/.easiarr/logs/
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { mkdir, writeFile, readdir, stat } from "node:fs/promises"
|
|
7
|
-
import { join } from "node:path"
|
|
8
|
-
import { homedir } from "node:os"
|
|
9
|
-
import { debugLog } from "./debug"
|
|
10
|
-
|
|
11
|
-
const LOGS_DIR = join(homedir(), ".easiarr", "logs")
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Get the logs directory path for a specific app
|
|
15
|
-
*/
|
|
16
|
-
export function getLogPath(appId: string): string {
|
|
17
|
-
return join(LOGS_DIR, appId)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Ensure the logs directory exists for an app
|
|
22
|
-
*/
|
|
23
|
-
async function ensureLogDir(appId: string): Promise<string> {
|
|
24
|
-
const logDir = getLogPath(appId)
|
|
25
|
-
await mkdir(logDir, { recursive: true })
|
|
26
|
-
return logDir
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Generate a timestamped log filename
|
|
31
|
-
*/
|
|
32
|
-
function generateLogFilename(): string {
|
|
33
|
-
const now = new Date()
|
|
34
|
-
const timestamp = now.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19)
|
|
35
|
-
return `${timestamp}.log`
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Save container logs to a file
|
|
40
|
-
* @returns The path to the saved log file
|
|
41
|
-
*/
|
|
42
|
-
export async function saveLog(appId: string, content: string): Promise<string> {
|
|
43
|
-
const logDir = await ensureLogDir(appId)
|
|
44
|
-
const filename = generateLogFilename()
|
|
45
|
-
const filepath = join(logDir, filename)
|
|
46
|
-
|
|
47
|
-
await writeFile(filepath, content, "utf-8")
|
|
48
|
-
debugLog("Logs", `Saved log for ${appId} to ${filepath}`)
|
|
49
|
-
|
|
50
|
-
return filepath
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* List saved logs for an app
|
|
55
|
-
* @returns Array of log file info sorted by date (newest first)
|
|
56
|
-
*/
|
|
57
|
-
export async function listSavedLogs(
|
|
58
|
-
appId: string
|
|
59
|
-
): Promise<Array<{ filename: string; path: string; date: Date; size: number }>> {
|
|
60
|
-
const logDir = getLogPath(appId)
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const files = await readdir(logDir)
|
|
64
|
-
const logFiles = files.filter((f) => f.endsWith(".log"))
|
|
65
|
-
|
|
66
|
-
const fileInfos = await Promise.all(
|
|
67
|
-
logFiles.map(async (filename) => {
|
|
68
|
-
const path = join(logDir, filename)
|
|
69
|
-
const stats = await stat(path)
|
|
70
|
-
return {
|
|
71
|
-
filename,
|
|
72
|
-
path,
|
|
73
|
-
date: stats.mtime,
|
|
74
|
-
size: stats.size,
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
// Sort by date, newest first
|
|
80
|
-
return fileInfos.sort((a, b) => b.date.getTime() - a.date.getTime())
|
|
81
|
-
} catch {
|
|
82
|
-
// Directory doesn't exist yet
|
|
83
|
-
return []
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Get the base logs directory path
|
|
89
|
-
*/
|
|
90
|
-
export function getLogsBaseDir(): string {
|
|
91
|
-
return LOGS_DIR
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Format bytes to human-readable size
|
|
96
|
-
*/
|
|
97
|
-
export function formatBytes(bytes: number): string {
|
|
98
|
-
if (bytes < 1024) return `${bytes} B`
|
|
99
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
100
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Format relative time (e.g., "2 hours ago")
|
|
105
|
-
*/
|
|
106
|
-
export function formatRelativeTime(date: Date): string {
|
|
107
|
-
const now = new Date()
|
|
108
|
-
const diffMs = now.getTime() - date.getTime()
|
|
109
|
-
const diffSecs = Math.floor(diffMs / 1000)
|
|
110
|
-
const diffMins = Math.floor(diffSecs / 60)
|
|
111
|
-
const diffHours = Math.floor(diffMins / 60)
|
|
112
|
-
const diffDays = Math.floor(diffHours / 24)
|
|
113
|
-
|
|
114
|
-
if (diffDays > 0) return `${diffDays}d ago`
|
|
115
|
-
if (diffHours > 0) return `${diffHours}h ago`
|
|
116
|
-
if (diffMins > 0) return `${diffMins}m ago`
|
|
117
|
-
return "just now"
|
|
118
|
-
}
|
|
@@ -1,101 +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 { join } from "node:path"
|
|
10
|
-
import { homedir } from "node:os"
|
|
11
|
-
import { parseEnvFile, serializeEnv } from "../env"
|
|
12
|
-
import { debugLog } from "../debug"
|
|
13
|
-
|
|
14
|
-
const ENV_FILE = join(homedir(), ".easiarr", ".env")
|
|
15
|
-
|
|
16
|
-
export const name = "rename-env-variables"
|
|
17
|
-
|
|
18
|
-
const renames: [string, string][] = [
|
|
19
|
-
// Global credentials
|
|
20
|
-
["GLOBAL_PASSWORD", "PASSWORD_GLOBAL"],
|
|
21
|
-
["GLOBAL_USERNAME", "USERNAME_GLOBAL"],
|
|
22
|
-
// qBittorrent
|
|
23
|
-
["QBITTORRENT_PASSWORD", "PASSWORD_QBITTORRENT"],
|
|
24
|
-
["QBITTORRENT_USER", "USERNAME_QBITTORRENT"],
|
|
25
|
-
["QBITTORRENT_PASS", "PASSWORD_QBITTORRENT"], // Legacy alias
|
|
26
|
-
// Portainer
|
|
27
|
-
["PORTAINER_PASSWORD", "PASSWORD_PORTAINER"],
|
|
28
|
-
// PostgreSQL
|
|
29
|
-
["POSTGRESQL_USERNAME", "USERNAME_POSTGRESQL"],
|
|
30
|
-
["POSTGRESQL_PASSWORD", "PASSWORD_POSTGRESQL"],
|
|
31
|
-
// VPN
|
|
32
|
-
["VPN_USERNAME", "USERNAME_VPN"],
|
|
33
|
-
["VPN_PASSWORD", "PASSWORD_VPN"],
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
export function up(): boolean {
|
|
37
|
-
if (!existsSync(ENV_FILE)) {
|
|
38
|
-
debugLog("Migrations", "No .env file found, skipping migration")
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
const content = readFileSync(ENV_FILE, "utf-8")
|
|
44
|
-
const env = parseEnvFile(content)
|
|
45
|
-
let changed = false
|
|
46
|
-
|
|
47
|
-
for (const [oldKey, newKey] of renames) {
|
|
48
|
-
if (env[oldKey] !== undefined && env[newKey] === undefined) {
|
|
49
|
-
env[newKey] = env[oldKey]
|
|
50
|
-
delete env[oldKey]
|
|
51
|
-
changed = true
|
|
52
|
-
debugLog("Migrations", `Renamed ${oldKey} → ${newKey}`)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (changed) {
|
|
57
|
-
writeFileSync(ENV_FILE, serializeEnv(env), "utf-8")
|
|
58
|
-
debugLog("Migrations", "Migration completed: env variables renamed")
|
|
59
|
-
return true
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
debugLog("Migrations", "No changes needed")
|
|
63
|
-
return false
|
|
64
|
-
} catch (e) {
|
|
65
|
-
debugLog("Migrations", `Migration error: ${e}`)
|
|
66
|
-
return false
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function down(): boolean {
|
|
71
|
-
if (!existsSync(ENV_FILE)) {
|
|
72
|
-
return false
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
const content = readFileSync(ENV_FILE, "utf-8")
|
|
77
|
-
const env = parseEnvFile(content)
|
|
78
|
-
let changed = false
|
|
79
|
-
|
|
80
|
-
for (const [oldKey, newKey] of renames) {
|
|
81
|
-
// Reverse operation: restore oldKey from newKey
|
|
82
|
-
if (env[newKey] !== undefined && env[oldKey] === undefined) {
|
|
83
|
-
env[oldKey] = env[newKey]
|
|
84
|
-
delete env[newKey]
|
|
85
|
-
changed = true
|
|
86
|
-
debugLog("Migrations", `Rolled back ${newKey} → ${oldKey}`)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (changed) {
|
|
91
|
-
writeFileSync(ENV_FILE, serializeEnv(env), "utf-8")
|
|
92
|
-
debugLog("Migrations", "Rollback completed: env variables restored")
|
|
93
|
-
return true
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return false
|
|
97
|
-
} catch (e) {
|
|
98
|
-
debugLog("Migrations", `Rollback error: ${e}`)
|
|
99
|
-
return false
|
|
100
|
-
}
|
|
101
|
-
}
|
|
@@ -1,90 +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 { join } from "node:path"
|
|
10
|
-
import { homedir } from "node:os"
|
|
11
|
-
import { debugLog } from "../debug"
|
|
12
|
-
|
|
13
|
-
const CONFIG_FILE = join(homedir(), ".easiarr", "config.json")
|
|
14
|
-
|
|
15
|
-
export const name = "rename_easiarr_status"
|
|
16
|
-
|
|
17
|
-
export function up(): boolean {
|
|
18
|
-
if (!existsSync(CONFIG_FILE)) {
|
|
19
|
-
debugLog("Migrations", "No config.json file found, skipping migration")
|
|
20
|
-
return false
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const content = readFileSync(CONFIG_FILE, "utf-8")
|
|
25
|
-
const config = JSON.parse(content)
|
|
26
|
-
|
|
27
|
-
if (!config.apps || !Array.isArray(config.apps)) {
|
|
28
|
-
debugLog("Migrations", "No apps array in config, skipping migration")
|
|
29
|
-
return false
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let changed = false
|
|
33
|
-
|
|
34
|
-
for (const app of config.apps) {
|
|
35
|
-
if (app.id === "easiarr-status") {
|
|
36
|
-
app.id = "easiarr"
|
|
37
|
-
changed = true
|
|
38
|
-
debugLog("Migrations", "Renamed easiarr-status → easiarr")
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (changed) {
|
|
43
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8")
|
|
44
|
-
debugLog("Migrations", "Migration completed: app id renamed")
|
|
45
|
-
return true
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
debugLog("Migrations", "No changes needed")
|
|
49
|
-
return false
|
|
50
|
-
} catch (e) {
|
|
51
|
-
debugLog("Migrations", `Migration error: ${e}`)
|
|
52
|
-
return false
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function down(): boolean {
|
|
57
|
-
if (!existsSync(CONFIG_FILE)) {
|
|
58
|
-
return false
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const content = readFileSync(CONFIG_FILE, "utf-8")
|
|
63
|
-
const config = JSON.parse(content)
|
|
64
|
-
|
|
65
|
-
if (!config.apps || !Array.isArray(config.apps)) {
|
|
66
|
-
return false
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let changed = false
|
|
70
|
-
|
|
71
|
-
for (const app of config.apps) {
|
|
72
|
-
if (app.id === "easiarr") {
|
|
73
|
-
app.id = "easiarr-status"
|
|
74
|
-
changed = true
|
|
75
|
-
debugLog("Migrations", "Rolled back easiarr → easiarr-status")
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (changed) {
|
|
80
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8")
|
|
81
|
-
debugLog("Migrations", "Rollback completed: app id restored")
|
|
82
|
-
return true
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return false
|
|
86
|
-
} catch (e) {
|
|
87
|
-
debugLog("Migrations", `Rollback error: ${e}`)
|
|
88
|
-
return false
|
|
89
|
-
}
|
|
90
|
-
}
|
|
@@ -1,44 +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
|
-
import { getEnvPath } from "../env"
|
|
8
|
-
import { debugLog } from "../debug"
|
|
9
|
-
|
|
10
|
-
export const name = "remove_cloudflare_dns_api_token"
|
|
11
|
-
|
|
12
|
-
export function up(): boolean {
|
|
13
|
-
debugLog("Migrations", "Running migration: remove_cloudflare_dns_api_token")
|
|
14
|
-
|
|
15
|
-
const envPath = getEnvPath()
|
|
16
|
-
if (!existsSync(envPath)) {
|
|
17
|
-
debugLog("Migrations", ".env file not found, skipping")
|
|
18
|
-
return true
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const content = readFileSync(envPath, "utf-8")
|
|
22
|
-
|
|
23
|
-
// Check if CLOUDFLARE_DNS_API_TOKEN exists
|
|
24
|
-
if (!content.includes("CLOUDFLARE_DNS_API_TOKEN")) {
|
|
25
|
-
debugLog("Migrations", "CLOUDFLARE_DNS_API_TOKEN not found, skipping")
|
|
26
|
-
return true
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Remove the line containing CLOUDFLARE_DNS_API_TOKEN
|
|
30
|
-
const lines = content.split("\n")
|
|
31
|
-
const filtered = lines.filter((line) => !line.startsWith("CLOUDFLARE_DNS_API_TOKEN="))
|
|
32
|
-
const newContent = filtered.join("\n")
|
|
33
|
-
|
|
34
|
-
writeFileSync(envPath, newContent, "utf-8")
|
|
35
|
-
debugLog("Migrations", "Removed CLOUDFLARE_DNS_API_TOKEN from .env")
|
|
36
|
-
|
|
37
|
-
return true
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function down(): boolean {
|
|
41
|
-
debugLog("Migrations", "Reverting migration: remove_cloudflare_dns_api_token")
|
|
42
|
-
// No revert needed - the token was unused
|
|
43
|
-
return true
|
|
44
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Migration: migrate to xdg
|
|
3
|
-
*
|
|
4
|
-
* Moves config from ~/.easiarr to $XDG_CONFIG_HOME/easiarr
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { existsSync, lstatSync, readdirSync, rmSync } from "node:fs"
|
|
8
|
-
import { copyFileSync, cpSync, mkdirSync } from "node:fs"
|
|
9
|
-
import { homedir } from "node:os"
|
|
10
|
-
import { join } from "node:path"
|
|
11
|
-
|
|
12
|
-
import { debugLog } from "../debug"
|
|
13
|
-
|
|
14
|
-
export const name = "migrate_to_xdg"
|
|
15
|
-
|
|
16
|
-
const OLD_DIR = join(homedir(), ".easiarr")
|
|
17
|
-
|
|
18
|
-
function getNewDir(): string {
|
|
19
|
-
const xdgConfigHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
|
|
20
|
-
return join(xdgConfigHome, "easiarr")
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function up(): boolean {
|
|
24
|
-
const newDir = getNewDir()
|
|
25
|
-
|
|
26
|
-
// Skip if old directory doesn't exist
|
|
27
|
-
if (!existsSync(OLD_DIR)) {
|
|
28
|
-
debugLog("Migrations", "No legacy ~/.easiarr directory found, skipping migration")
|
|
29
|
-
return false
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Skip if already at XDG location (same path)
|
|
33
|
-
if (OLD_DIR === newDir) {
|
|
34
|
-
debugLog("Migrations", "Old and new paths are identical, skipping")
|
|
35
|
-
return false
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Skip if new directory already has config
|
|
39
|
-
if (existsSync(join(newDir, "config.json"))) {
|
|
40
|
-
debugLog("Migrations", "XDG config already exists, skipping migration")
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
debugLog("Migrations", `Migrating from ${OLD_DIR} to ${newDir}`)
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
// Ensure target directory exists
|
|
48
|
-
if (!existsSync(newDir)) {
|
|
49
|
-
mkdirSync(newDir, { recursive: true })
|
|
50
|
-
debugLog("Migrations", `Created directory ${newDir}`)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Get all entries in old directory
|
|
54
|
-
const entries = readdirSync(OLD_DIR)
|
|
55
|
-
|
|
56
|
-
for (const entry of entries) {
|
|
57
|
-
// Skip migrations file - it will be recreated in new location
|
|
58
|
-
if (entry === ".migrations.json") continue
|
|
59
|
-
|
|
60
|
-
const oldPath = join(OLD_DIR, entry)
|
|
61
|
-
const newPath = join(newDir, entry)
|
|
62
|
-
|
|
63
|
-
// Skip if entry already exists in new location
|
|
64
|
-
if (existsSync(newPath)) {
|
|
65
|
-
debugLog("Migrations", `Skipping ${entry}, already exists in new location`)
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const stat = lstatSync(oldPath)
|
|
71
|
-
|
|
72
|
-
if (stat.isDirectory()) {
|
|
73
|
-
// Copy directory recursively (e.g., backups/)
|
|
74
|
-
cpSync(oldPath, newPath, { recursive: true })
|
|
75
|
-
debugLog("Migrations", `Copied directory ${entry} to XDG location`)
|
|
76
|
-
} else if (stat.isFile()) {
|
|
77
|
-
// Copy file
|
|
78
|
-
copyFileSync(oldPath, newPath)
|
|
79
|
-
debugLog("Migrations", `Copied ${entry} to XDG location`)
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
debugLog("Migrations", `Failed to copy ${entry}`)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Remove old directory completely
|
|
87
|
-
try {
|
|
88
|
-
rmSync(OLD_DIR, { recursive: true, force: true })
|
|
89
|
-
debugLog("Migrations", `Removed legacy directory ${OLD_DIR}`)
|
|
90
|
-
} catch {
|
|
91
|
-
debugLog("Migrations", `Could not remove old directory`)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
debugLog("Migrations", "XDG migration completed successfully")
|
|
95
|
-
return true
|
|
96
|
-
} catch (e) {
|
|
97
|
-
debugLog("Migrations", `Migration error: ${e}`)
|
|
98
|
-
return false
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function down(): boolean {
|
|
103
|
-
// This migration is not easily reversible
|
|
104
|
-
// The old config location is deprecated
|
|
105
|
-
debugLog("Migrations", "XDG migration rollback not supported")
|
|
106
|
-
return false
|
|
107
|
-
}
|