@muhammedaksam/easiarr 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/index.js +29750 -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,160 +0,0 @@
1
- /**
2
- * Bookmarks Generator
3
- * Generates Netscape-format HTML bookmarks for browser import
4
- */
5
-
6
- import { writeFile } from "node:fs/promises"
7
-
8
- import type { AppCategory, EasiarrConfig } from "./schema"
9
- import { CATEGORY_ORDER } from "~/apps/categories"
10
- import { getApp } from "~/apps/registry"
11
- import { readEnvSync } from "~/utils/env"
12
- import { getConfigPath } from "~/utils/paths"
13
- import { APP_CATEGORIES } from "./schema"
14
-
15
- interface BookmarkEntry {
16
- name: string
17
- url: string
18
- description: string
19
- }
20
-
21
- type CategoryBookmarks = Map<AppCategory, BookmarkEntry[]>
22
-
23
- /**
24
- * Get the URL for an app based on Traefik configuration
25
- */
26
- function getAppUrl(
27
- appId: string,
28
- port: number,
29
- config: EasiarrConfig,
30
- useLocalUrls: boolean
31
- ): string {
32
- if (!useLocalUrls && config.traefik?.enabled && config.traefik.domain) {
33
- return `https://${appId}.${config.traefik.domain}/`
34
- }
35
- // Read LOCAL_DOCKER_IP from .env file, fallback to localhost
36
- const env = readEnvSync()
37
- const host = env.LOCAL_DOCKER_IP || "localhost"
38
- return `http://${host}:${port}/`
39
- }
40
-
41
- /**
42
- * Generate bookmark entries grouped by category
43
- */
44
- function generateBookmarksByCategory(
45
- config: EasiarrConfig,
46
- useLocalUrls: boolean
47
- ): CategoryBookmarks {
48
- const categoryBookmarks: CategoryBookmarks = new Map()
49
-
50
- for (const appConfig of config.apps) {
51
- if (!appConfig.enabled) continue
52
-
53
- const appDef = getApp(appConfig.id)
54
- if (!appDef) continue
55
-
56
- const port = appConfig.port ?? appDef.defaultPort
57
- const url = getAppUrl(appConfig.id, port, config, useLocalUrls)
58
-
59
- const entry: BookmarkEntry = {
60
- name: appDef.name,
61
- url,
62
- description: appDef.description,
63
- }
64
-
65
- const category = appDef.category
66
- if (!categoryBookmarks.has(category)) {
67
- categoryBookmarks.set(category, [])
68
- }
69
- categoryBookmarks.get(category)!.push(entry)
70
- }
71
-
72
- return categoryBookmarks
73
- }
74
-
75
- /**
76
- * Generate Netscape-format HTML bookmarks
77
- */
78
- export function generateBookmarksHtml(config: EasiarrConfig, useLocalUrls = false): string {
79
- const categoryBookmarks = generateBookmarksByCategory(config, useLocalUrls)
80
-
81
- let html = `<!DOCTYPE NETSCAPE-Bookmark-file-1>
82
- <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
83
- <TITLE>Bookmarks</TITLE>
84
- <H1>Bookmarks</H1>
85
- <DL><p>
86
- <DT><H3 PERSONAL_TOOLBAR_FOLDER="true">easiarr</H3>
87
- <DL><p>
88
- `
89
-
90
- // Add external resources first
91
- html += ` <DT><A HREF="https://github.com/muhammedaksam/easiarr/">GitHub | easiarr Project Repo</A>\n`
92
- html += ` <DT><A HREF="https://trash-guides.info/">TRaSH Guides</A>\n`
93
-
94
- // Add apps grouped by category in defined order
95
- for (const { id: categoryId } of CATEGORY_ORDER) {
96
- const bookmarks = categoryBookmarks.get(categoryId)
97
- if (!bookmarks || bookmarks.length === 0) continue
98
-
99
- const categoryName = APP_CATEGORIES[categoryId]
100
-
101
- // Add category header as a folder
102
- html += ` <DT><H3>${categoryName}</H3>\n`
103
- html += ` <DL><p>\n`
104
-
105
- for (const bookmark of bookmarks) {
106
- html += ` <DT><A HREF="${bookmark.url}">${bookmark.name} | ${bookmark.description}</A>\n`
107
- }
108
-
109
- html += ` </DL><p>\n`
110
- }
111
-
112
- // Close the structure
113
- html += ` </DL><p>
114
- </DL><p>
115
- `
116
-
117
- return html
118
- }
119
-
120
- /**
121
- * Get the path to the bookmarks file
122
- * @param type - 'local' for local URLs, 'remote' for Traefik URLs
123
- */
124
- export function getBookmarksPath(type: "local" | "remote" = "local"): string {
125
- const filename = type === "remote" ? "bookmarks-remote.html" : "bookmarks-local.html"
126
- return getConfigPath(filename)
127
- }
128
-
129
- /**
130
- * Save bookmarks HTML file
131
- * @param type - 'local' for local URLs, 'remote' for Traefik URLs
132
- */
133
- export async function saveBookmarks(
134
- config: EasiarrConfig,
135
- type: "local" | "remote" = "local"
136
- ): Promise<string> {
137
- const useLocalUrls = type === "local"
138
- const html = generateBookmarksHtml(config, useLocalUrls)
139
- const path = getBookmarksPath(type)
140
- await writeFile(path, html, "utf-8")
141
- return path
142
- }
143
-
144
- /**
145
- * Save all bookmarks files
146
- * Always saves local bookmarks, and remote bookmarks only if Traefik is enabled
147
- */
148
- export async function saveAllBookmarks(config: EasiarrConfig): Promise<string[]> {
149
- const paths: string[] = []
150
-
151
- // Always save local bookmarks
152
- paths.push(await saveBookmarks(config, "local"))
153
-
154
- // Save remote bookmarks only if Traefik is enabled with a domain
155
- if (config.traefik?.enabled && config.traefik.domain) {
156
- paths.push(await saveBookmarks(config, "remote"))
157
- }
158
-
159
- return paths
160
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * Default Value Detection
3
- * Auto-detect timezone and UID/GID from system
4
- */
5
-
6
- import { existsSync, readlinkSync } from "node:fs"
7
-
8
- export function detectTimezone(): string {
9
- const tzPath = "/etc/localtime"
10
-
11
- if (existsSync(tzPath)) {
12
- try {
13
- const link = readlinkSync(tzPath)
14
- const parts = link.split("zoneinfo/")
15
- if (parts.length > 1) {
16
- return parts[1]
17
- }
18
- } catch {
19
- // Fallback if not a symlink
20
- }
21
- }
22
-
23
- // Try TZ environment variable
24
- if (process.env.TZ) {
25
- return process.env.TZ
26
- }
27
-
28
- return "UTC"
29
- }
30
-
31
- export function detectUid(): number {
32
- return process.getuid?.() ?? 1000
33
- }
34
-
35
- export function detectGid(): number {
36
- return process.getgid?.() ?? 1000
37
- }
@@ -1,381 +0,0 @@
1
- /**
2
- * Homepage Configuration Generator
3
- * Generates services.yaml and other config files for Homepage dashboard
4
- */
5
-
6
- import { existsSync } from "node:fs"
7
- import { mkdir, writeFile } from "node:fs/promises"
8
- import { join } from "node:path"
9
-
10
- import type { AppCategory, EasiarrConfig } from "./schema"
11
- import { PortainerApiClient } from "~/api/portainer-api"
12
- import { CATEGORY_ORDER } from "~/apps/categories"
13
- import { getApp } from "~/apps/registry"
14
- import { getLocalIp, readEnvSync, updateEnv } from "~/utils/env"
15
- import { APP_CATEGORIES } from "./schema"
16
-
17
- /**
18
- * Get the Homepage config directory path
19
- */
20
- export function getHomepageConfigPath(config: EasiarrConfig): string {
21
- return join(config.rootDir, "config", "homepage")
22
- }
23
-
24
- /**
25
- * Homepage service entry
26
- */
27
- interface HomepageService {
28
- href?: string // Optional - cloudflared has no web UI
29
- icon?: string
30
- description?: string
31
- ping?: string
32
- widget?: {
33
- type: string
34
- url?: string // Optional - cloudflared doesn't need url
35
- key?: string
36
- [key: string]: unknown
37
- }
38
- }
39
-
40
- /**
41
- * Generate services.yaml content from enabled apps
42
- */
43
- export async function generateServicesYaml(config: EasiarrConfig): Promise<string> {
44
- const env = readEnvSync()
45
- const localIp = getLocalIp()
46
-
47
- // Group apps by category
48
- const categoryGroups = new Map<AppCategory, Array<{ name: string; service: HomepageService }>>()
49
-
50
- for (const appConfig of config.apps) {
51
- if (!appConfig.enabled) continue
52
-
53
- const appDef = getApp(appConfig.id)
54
- if (!appDef) continue
55
-
56
- // Skip homepage itself
57
- if (appDef.id === "homepage") continue
58
-
59
- // Skip apps with no web UI (port 0) and no homepage config
60
- if (appDef.defaultPort === 0 && !appDef.homepage) continue
61
-
62
- const port = appConfig.port ?? appDef.defaultPort
63
- const internalPort = appDef.internalPort ?? port
64
- // External URL for user browser access (href, ping)
65
- const baseUrl = `http://${localIp}:${port}`
66
- // Internal Docker URL for container-to-container API calls (widgets)
67
- const dockerUrl = `http://${appDef.id}:${internalPort}`
68
-
69
- const service: HomepageService = {
70
- description: appDef.description,
71
- }
72
-
73
- // Special cases for href/ping
74
- if (appDef.id === "cloudflared") {
75
- // Cloudflared has no web UI - skip href/ping
76
- } else if (appDef.id === "traefik") {
77
- // Traefik dashboard is on port 8083 (secondary port), not 80 (proxy)
78
- const dashboardUrl = `http://${localIp}:8083`
79
- service.href = dashboardUrl
80
- service.ping = dashboardUrl
81
- } else {
82
- service.href = baseUrl
83
- service.ping = baseUrl
84
- }
85
-
86
- // Add icon if defined in homepage meta
87
- if (appDef.homepage?.icon) {
88
- service.icon = appDef.homepage.icon
89
- } else {
90
- // Default to app ID as icon name
91
- service.icon = `${appDef.id}.png`
92
- }
93
-
94
- // Add widget if defined
95
- if (appDef.homepage?.widget) {
96
- const apiKey = env[`API_KEY_${appDef.id.toUpperCase()}`]
97
-
98
- // Some widgets require specific config - skip if not available
99
- const widgetType = appDef.homepage.widget
100
-
101
- // Cloudflared requires accountid and tunnelid
102
- if (appDef.id === "cloudflared") {
103
- const accountId = env["CLOUDFLARE_ACCOUNT_ID"]
104
- const tunnelId = env["CLOUDFLARE_TUNNEL_ID"]
105
- const apiToken = env["CLOUDFLARE_API_TOKEN"]
106
-
107
- if (accountId && tunnelId && apiToken) {
108
- service.widget = {
109
- type: widgetType,
110
- accountid: accountId,
111
- tunnelid: tunnelId,
112
- key: apiToken,
113
- }
114
- }
115
- // Skip widget entirely if missing required params
116
- }
117
- // Headscale requires API key
118
- else if (appDef.id === "headscale") {
119
- const headscaleKey = env["API_KEY_HEADSCALE"]
120
- if (headscaleKey) {
121
- service.widget = {
122
- type: widgetType,
123
- url: dockerUrl,
124
- key: headscaleKey,
125
- }
126
- }
127
- // Skip widget if no API key
128
- }
129
- // Most widgets need API key - only add if available
130
- else if (
131
- apiKey ||
132
- ["qbittorrent", "gluetun", "traefik", "huntarr", "easiarr", "flaresolverr"].includes(
133
- appDef.id
134
- )
135
- ) {
136
- service.widget = {
137
- type: widgetType,
138
- url: dockerUrl,
139
- }
140
-
141
- if (apiKey) {
142
- service.widget.key = apiKey
143
- }
144
-
145
- // Add widget-specific credentials from env
146
- if (appDef.id === "qbittorrent") {
147
- const username = env["USERNAME_QBITTORRENT"]
148
- const password = env["PASSWORD_QBITTORRENT"]
149
- if (username) service.widget.username = username
150
- if (password) service.widget.password = password
151
- }
152
-
153
- // Traefik widget needs the dashboard/API port (8080 internal)
154
- if (appDef.id === "traefik") {
155
- service.widget.url = `http://traefik:8080`
156
- }
157
-
158
- if (appDef.id === "portainer") {
159
- // Try to auto-detect Portainer environment ID
160
- // User can override with PORTAINER_ENV in .env file
161
- if (env["PORTAINER_ENV"]) {
162
- service.widget.env = env["PORTAINER_ENV"]
163
- } else {
164
- // Auto-detect from Portainer API
165
- const portainerPort = appConfig.port ?? appDef.defaultPort
166
- const portainerClient = new PortainerApiClient(localIp, portainerPort)
167
- const portainerApiKey = env["API_KEY_PORTAINER"]
168
- if (portainerApiKey) {
169
- portainerClient.setApiKey(portainerApiKey)
170
- }
171
- const localEnvId = await portainerClient.getLocalEnvironmentId()
172
- const envIdStr = localEnvId?.toString() ?? "1"
173
- service.widget.env = envIdStr
174
-
175
- // Persist the detected env ID to .env for future use
176
- if (localEnvId) {
177
- await updateEnv({ PORTAINER_ENV: envIdStr })
178
- }
179
- }
180
- }
181
-
182
- // Add any custom widget fields
183
- if (appDef.homepage.widgetFields) {
184
- Object.assign(service.widget, appDef.homepage.widgetFields)
185
- }
186
-
187
- // Huntarr: dynamically build mappings based on enabled *arr apps
188
- if (appDef.id === "huntarr") {
189
- const huntarrApps = ["radarr", "sonarr", "lidarr", "whisparr", "readarr"]
190
- const mappings = huntarrApps
191
- .filter((appId) => config.apps.some((a) => a.id === appId && a.enabled))
192
- .map((appId) => ({
193
- field: `${appId}.next_cycle`,
194
- label: appId.charAt(0).toUpperCase() + appId.slice(1),
195
- format: "relativeDate",
196
- }))
197
- service.widget.mappings = JSON.stringify(mappings)
198
- }
199
- }
200
- // If widget requires API key and none is set, skip widget but keep ping/icon
201
- }
202
-
203
- // Add to category group
204
- const category = appDef.category
205
- if (!categoryGroups.has(category)) {
206
- categoryGroups.set(category, [])
207
- }
208
- categoryGroups.get(category)!.push({ name: appDef.name, service })
209
- }
210
-
211
- // Build YAML output
212
- let yaml = "---\n# Auto-generated by easiarr\n# https://github.com/muhammedaksam/easiarr\n\n"
213
-
214
- // Add easiarr info section with two widgets - one for installed, one for latest
215
- yaml += `- easiarr:\n`
216
- // Installed version from local easiarr container
217
- yaml += ` - Installed:\n`
218
- yaml += ` href: https://github.com/muhammedaksam/easiarr\n`
219
- yaml += ` icon: mdi-docker\n`
220
- yaml += ` description: Your current version\n`
221
- yaml += ` widget:\n`
222
- yaml += ` type: customapi\n`
223
- yaml += ` url: http://easiarr:8080/config.json\n`
224
- yaml += ` refreshInterval: 3600000\n` // 1 hour
225
- yaml += ` mappings:\n`
226
- yaml += ` - field: version\n`
227
- yaml += ` label: Version\n`
228
- // Latest version from GitHub API
229
- yaml += ` - Latest:\n`
230
- yaml += ` href: https://github.com/muhammedaksam/easiarr/releases\n`
231
- yaml += ` icon: mdi-github\n`
232
- yaml += ` description: Check for updates\n`
233
- yaml += ` widget:\n`
234
- yaml += ` type: customapi\n`
235
- yaml += ` url: https://api.github.com/repos/muhammedaksam/easiarr/releases/latest\n`
236
- yaml += ` headers:\n`
237
- yaml += ` User-Agent: easiarr-homepage-widget\n`
238
- yaml += ` refreshInterval: 86400000\n` // 24 hours
239
- yaml += ` mappings:\n`
240
- yaml += ` - field: tag_name\n`
241
- yaml += ` label: Version\n`
242
- yaml += ` - field: published_at\n`
243
- yaml += ` label: Released\n`
244
- yaml += ` format: relativeDate\n`
245
- yaml += `\n`
246
-
247
- // Use CATEGORY_ORDER for consistent ordering
248
- for (const { id: category } of CATEGORY_ORDER) {
249
- const services = categoryGroups.get(category)
250
- if (!services || services.length === 0) continue
251
-
252
- const categoryName = APP_CATEGORIES[category]
253
- yaml += `- ${categoryName}:\n`
254
-
255
- for (const { name, service } of services) {
256
- yaml += ` - ${name}:\n`
257
-
258
- if (service.href) {
259
- yaml += ` href: ${service.href}\n`
260
- }
261
-
262
- if (service.icon) {
263
- yaml += ` icon: ${service.icon}\n`
264
- }
265
-
266
- if (service.description) {
267
- yaml += ` description: ${service.description}\n`
268
- }
269
-
270
- if (service.ping) {
271
- yaml += ` ping: ${service.ping}\n`
272
- }
273
-
274
- if (service.widget) {
275
- yaml += ` widget:\n`
276
- yaml += ` type: ${service.widget.type}\n`
277
- if (service.widget.url) {
278
- yaml += ` url: ${service.widget.url}\n`
279
- }
280
-
281
- if (service.widget.key) {
282
- yaml += ` key: ${service.widget.key}\n`
283
- }
284
-
285
- // Add any other widget fields
286
- for (const [key, value] of Object.entries(service.widget)) {
287
- if (key !== "type" && key !== "url" && key !== "key") {
288
- // Handle mappings array specially
289
- if (key === "mappings") {
290
- const mappings = typeof value === "string" ? JSON.parse(value) : value
291
- if (Array.isArray(mappings)) {
292
- yaml += ` mappings:\n`
293
- for (const mapping of mappings) {
294
- yaml += ` - field: ${mapping.field}\n`
295
- yaml += ` label: ${mapping.label}\n`
296
- if (mapping.format) {
297
- yaml += ` format: ${mapping.format}\n`
298
- }
299
- }
300
- }
301
- } else {
302
- yaml += ` ${key}: ${value}\n`
303
- }
304
- }
305
- }
306
- }
307
- }
308
-
309
- yaml += "\n"
310
- }
311
-
312
- return yaml
313
- }
314
-
315
- /**
316
- * Generate settings.yaml with sensible defaults
317
- */
318
- export function generateSettingsYaml(): string {
319
- return `---
320
- # Auto-generated by easiarr
321
- # For configuration options: https://gethomepage.dev/configs/settings/
322
-
323
- title: easiarr Dashboard
324
-
325
- # Background: "Close-up Photography of Leaves With Droplets"
326
- # Photo by Sohail Nachiti: https://www.pexels.com/photo/close-up-photography-of-leaves-with-droplets-807598/
327
- background:
328
- image: https://images.pexels.com/photos/807598/pexels-photo-807598.jpeg?cs=srgb&fm=jpg&w=4128&h=3096
329
- blur: sm
330
- saturate: 50
331
- brightness: 50
332
- opacity: 50
333
-
334
- cardBlur: md
335
- theme: dark
336
- color: slate
337
-
338
- # Docker integration for label-based widget autodiscovery
339
- # Some apps (e.g., Huntarr) use Docker labels instead of API keys
340
- docker:
341
- enable: true
342
-
343
- layout:
344
- Media Management:
345
- style: row
346
- columns: 4
347
- Media Servers:
348
- style: row
349
- columns: 2
350
- Download Clients:
351
- style: row
352
- columns: 2
353
- `
354
- }
355
-
356
- /**
357
- * Save Homepage configuration files
358
- */
359
- export async function saveHomepageConfig(
360
- config: EasiarrConfig
361
- ): Promise<{ services: string; settings: string }> {
362
- const configPath = getHomepageConfigPath(config)
363
-
364
- // Ensure directory exists
365
- if (!existsSync(configPath)) {
366
- await mkdir(configPath, { recursive: true })
367
- }
368
-
369
- const servicesPath = join(configPath, "services.yaml")
370
- const settingsPath = join(configPath, "settings.yaml")
371
-
372
- // Generate and save services.yaml
373
- const servicesYaml = await generateServicesYaml(config)
374
- await writeFile(servicesPath, servicesYaml, "utf-8")
375
-
376
- // Generate and save settings.yaml (always regenerate to include docker integration)
377
- const settingsYaml = generateSettingsYaml()
378
- await writeFile(settingsPath, settingsYaml, "utf-8")
379
-
380
- return { services: servicesPath, settings: settingsPath }
381
- }
@@ -1,6 +0,0 @@
1
- export * from "./schema"
2
- export * from "./defaults"
3
- export * from "./manager"
4
- export * from "./homepage-config"
5
- export * from "./recyclarr-config"
6
- export * from "./bookmarks-generator"