@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.
- package/dist/index.js +29837 -0
- package/package.json +15 -15
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -521
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/base-api.ts +0 -216
- package/src/api/bazarr-api.ts +0 -297
- package/src/api/cloudflare-api.ts +0 -493
- package/src/api/custom-format-api.ts +0 -224
- package/src/api/grafana-api.ts +0 -230
- package/src/api/heimdall-api.ts +0 -141
- package/src/api/homarr-api.ts +0 -209
- package/src/api/huntarr-api.ts +0 -457
- package/src/api/index.ts +0 -9
- package/src/api/jellyfin-api.ts +0 -389
- package/src/api/jellyseerr-api.ts +0 -559
- package/src/api/maintainerr-api.ts +0 -461
- package/src/api/naming-config.ts +0 -101
- package/src/api/overseerr-api.ts +0 -392
- package/src/api/plex-api.ts +0 -308
- package/src/api/portainer-api.ts +0 -374
- package/src/api/profilarr-api.ts +0 -391
- package/src/api/prowlarr-api.ts +0 -488
- package/src/api/qbittorrent-api.ts +0 -300
- package/src/api/quality-profile-api.ts +0 -272
- package/src/api/tautulli-api.ts +0 -244
- package/src/api/uptime-kuma-api.ts +0 -343
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1275
- package/src/compose/caddy-config.ts +0 -130
- package/src/compose/generator.ts +0 -287
- package/src/compose/index.ts +0 -7
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -187
- package/src/config/bookmarks-generator.ts +0 -160
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -381
- package/src/config/index.ts +0 -6
- package/src/config/manager.ts +0 -150
- package/src/config/recyclarr-config.ts +0 -181
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -86
- 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 -333
- package/src/docker/client.ts +0 -312
- package/src/docker/index.ts +0 -18
- package/src/index.ts +0 -77
- package/src/setup/actions/arr-common.ts +0 -307
- package/src/setup/actions/dashboards.ts +0 -79
- package/src/setup/actions/download-clients.ts +0 -54
- package/src/setup/actions/jellyseerr.ts +0 -331
- package/src/setup/actions/media-servers.ts +0 -68
- package/src/setup/actions/monitoring.ts +0 -120
- package/src/setup/actions/prowlarr.ts +0 -137
- package/src/setup/actions/utilities.ts +0 -490
- package/src/setup/index.ts +0 -78
- package/src/setup/types.ts +0 -144
- package/src/structure/manager.ts +0 -129
- package/src/ui/App.ts +0 -111
- package/src/ui/components/ApplicationSelector.ts +0 -275
- package/src/ui/components/CredentialsForm.ts +0 -180
- package/src/ui/components/DownloadClientForm.ts +0 -114
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -160
- package/src/ui/components/UpdateNotification.ts +0 -102
- package/src/ui/index.ts +0 -7
- package/src/ui/screens/AdvancedSettings.ts +0 -188
- package/src/ui/screens/ApiKeyViewer.ts +0 -511
- package/src/ui/screens/AppConfigurator.ts +0 -543
- package/src/ui/screens/AppManager.ts +0 -284
- package/src/ui/screens/ContainerControl.ts +0 -581
- package/src/ui/screens/LogsViewer.ts +0 -478
- package/src/ui/screens/MainMenu.ts +0 -299
- package/src/ui/screens/MonitorDashboard.ts +0 -881
- package/src/ui/screens/SecretsEditor.ts +0 -216
- package/src/ui/screens/SettingsScreen.ts +0 -633
- package/src/ui/screens/index.ts +0 -22
- package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
- package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
- package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
- package/src/ui/screens/setup/HomepageSetup.ts +0 -165
- package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
- package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
- package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
- package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
- package/src/ui/screens/setup/QuickSetup.ts +0 -1169
- package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
- package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
- package/src/utils/arch.ts +0 -78
- package/src/utils/browser.ts +0 -27
- package/src/utils/categories.ts +0 -61
- package/src/utils/debug.ts +0 -84
- package/src/utils/env.ts +0 -138
- package/src/utils/index.ts +0 -8
- package/src/utils/logs.ts +0 -119
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
- package/src/utils/migrations.ts +0 -165
- package/src/utils/password.ts +0 -42
- package/src/utils/paths.ts +0 -33
- package/src/utils/unraid.ts +0 -103
- package/src/utils/update-checker.ts +0 -210
- 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
|
-
}
|
package/src/utils/migrations.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/password.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/paths.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/unraid.ts
DELETED
|
@@ -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
|
-
}
|