@muhammedaksam/easiarr 1.1.8 → 1.2.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.
- package/README.md +9 -2
- package/package.json +1 -1
- package/src/api/profilarr-api.ts +284 -0
- package/src/apps/registry.ts +92 -0
- package/src/compose/caddy-config.ts +129 -0
- package/src/compose/generator.ts +10 -1
- package/src/config/recyclarr-config.ts +179 -0
- package/src/config/schema.ts +19 -0
- package/src/docker/client.ts +16 -0
- package/src/structure/manager.ts +41 -1
- package/src/ui/screens/FullAutoSetup.ts +123 -1
- package/src/ui/screens/LogsViewer.ts +468 -0
- package/src/ui/screens/MainMenu.ts +14 -0
- package/src/ui/screens/QuickSetup.ts +52 -3
- package/src/ui/screens/RecyclarrSetup.ts +418 -0
- package/src/ui/screens/SettingsScreen.ts +35 -1
- package/src/utils/logs.ts +118 -0
- package/src/utils/unraid.ts +101 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
> ⚠️ **Work In Progress** - This project is in early experimental development. Features may be incomplete, unstable, or change without notice.
|
|
12
12
|
|
|
13
|
-
TUI tool for generating docker-compose files for the \*arr media ecosystem with
|
|
13
|
+
TUI tool for generating docker-compose files for the \*arr media ecosystem with 49 apps, TRaSH Guides best practices, VPN routing, and Traefik/Caddy reverse proxy support.
|
|
14
14
|
|
|
15
15
|
A terminal-based wizard that helps you set up Radarr, Sonarr, Prowlarr, and other \*arr applications with Docker Compose, following best practices from [TRaSH Guides](https://trash-guides.info/).
|
|
16
16
|
|
|
@@ -19,9 +19,13 @@ A terminal-based wizard that helps you set up Radarr, Sonarr, Prowlarr, and othe
|
|
|
19
19
|
- 📦 **Quick Setup Wizard** - Get started in minutes with a guided setup flow
|
|
20
20
|
- 🐳 **Docker Compose Generation** - Automatically generates optimized `docker-compose.yml`
|
|
21
21
|
- ✅ **TRaSH Guides Compliant** - Follows best practices for folder structure and hardlinks
|
|
22
|
+
- 🔄 **Recyclarr & Profilarr** - Automated TRaSH Guides profile sync
|
|
22
23
|
- 🎮 **Container Control** - Start, stop, and restart containers directly from the TUI
|
|
24
|
+
- 📋 **Container Logs Viewer** - View and save Docker container logs from the TUI
|
|
23
25
|
- ⚙️ **App Management** - Add or remove apps from your stack with ease
|
|
24
26
|
- 💾 **Persistent Configuration** - Settings saved to `~/.easiarr/config.json`
|
|
27
|
+
- 🔀 **Reverse Proxy** - Traefik or Caddy support with automatic SSL
|
|
28
|
+
- 🖥️ **Unraid Support** - Automatic OS detection and compatibility
|
|
25
29
|
|
|
26
30
|
## Quick Start
|
|
27
31
|
|
|
@@ -52,7 +56,7 @@ bun run start
|
|
|
52
56
|
- [Bun](https://bun.sh/) >= 1.0
|
|
53
57
|
- [Docker](https://www.docker.com/) with Docker Compose v2
|
|
54
58
|
|
|
55
|
-
## Supported Applications (
|
|
59
|
+
## Supported Applications (49 apps across 10 categories)
|
|
56
60
|
|
|
57
61
|
### Media Management (Servarr)
|
|
58
62
|
|
|
@@ -99,6 +103,8 @@ bun run start
|
|
|
99
103
|
- **Portainer** - Docker container management UI
|
|
100
104
|
- **Huntarr** - Missing content manager for \*arr apps
|
|
101
105
|
- **Unpackerr** - Archive extraction for \*arr apps
|
|
106
|
+
- **Recyclarr** - TRaSH Guides profile sync (CLI-based)
|
|
107
|
+
- **Profilarr** - TRaSH Guides profile sync (Web UI)
|
|
102
108
|
- **FileBot** - Media file renaming and automator
|
|
103
109
|
- **Chromium** - Web browser for secure remote browsing
|
|
104
110
|
- **Guacamole** - Clientless remote desktop gateway
|
|
@@ -118,6 +124,7 @@ bun run start
|
|
|
118
124
|
### Infrastructure
|
|
119
125
|
|
|
120
126
|
- **Traefik** - Reverse proxy and load balancer
|
|
127
|
+
- **Caddy** - Automatic HTTPS reverse proxy
|
|
121
128
|
- **Cloudflared** - Cloudflare Tunnel for secure external access
|
|
122
129
|
- **Traefik Certs Dumper** - Extracts certificates from Traefik
|
|
123
130
|
- **CrowdSec** - Intrusion prevention system
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhammedaksam/easiarr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "TUI tool for generating docker-compose files for the *arr media ecosystem with 41 apps, TRaSH Guides best practices, VPN routing, and Traefik reverse proxy support",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profilarr API Client
|
|
3
|
+
* Handles authentication and *arr instance configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { debugLog } from "../utils/debug"
|
|
7
|
+
import type { IAutoSetupClient, AutoSetupOptions, AutoSetupResult } from "./auto-setup-types"
|
|
8
|
+
|
|
9
|
+
// ==========================================
|
|
10
|
+
// Types
|
|
11
|
+
// ==========================================
|
|
12
|
+
|
|
13
|
+
export interface ProfilarrConfig {
|
|
14
|
+
id?: number
|
|
15
|
+
name: string
|
|
16
|
+
type: "radarr" | "sonarr"
|
|
17
|
+
tags: string[]
|
|
18
|
+
arrServer: string
|
|
19
|
+
apiKey: string
|
|
20
|
+
sync_method: "manual" | "schedule"
|
|
21
|
+
sync_interval: number
|
|
22
|
+
import_as_unique: boolean
|
|
23
|
+
data_to_sync: {
|
|
24
|
+
profiles: string[]
|
|
25
|
+
customFormats: string[]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ProfilarrSetupStatus {
|
|
30
|
+
needs_setup: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ProfilarrSetupResponse {
|
|
34
|
+
message: string
|
|
35
|
+
username: string
|
|
36
|
+
api_key: string
|
|
37
|
+
authenticated: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ProfilarrSettings {
|
|
41
|
+
username: string
|
|
42
|
+
api_key: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ==========================================
|
|
46
|
+
// Client
|
|
47
|
+
// ==========================================
|
|
48
|
+
|
|
49
|
+
export class ProfilarrApiClient implements IAutoSetupClient {
|
|
50
|
+
private baseUrl: string
|
|
51
|
+
private apiKey: string | null = null
|
|
52
|
+
|
|
53
|
+
constructor(host: string, port: number) {
|
|
54
|
+
this.baseUrl = `http://${host}:${port}/api`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
setApiKey(key: string): void {
|
|
58
|
+
this.apiKey = key
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
62
|
+
const url = `${this.baseUrl}${endpoint}`
|
|
63
|
+
const headers: Record<string, string> = {
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
...(this.apiKey ? { "X-Api-Key": this.apiKey } : {}),
|
|
66
|
+
...(options.headers as Record<string, string>),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
debugLog("ProfilarrApi", `${options.method || "GET"} ${endpoint}`)
|
|
70
|
+
|
|
71
|
+
const response = await fetch(url, { ...options, headers })
|
|
72
|
+
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const text = await response.text()
|
|
75
|
+
debugLog("ProfilarrApi", `Error ${response.status}: ${text}`)
|
|
76
|
+
throw new Error(`Profilarr API error: ${response.status} - ${text}`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const contentType = response.headers.get("content-type")
|
|
80
|
+
if (contentType?.includes("application/json")) {
|
|
81
|
+
return response.json()
|
|
82
|
+
}
|
|
83
|
+
return {} as T
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ==========================================
|
|
87
|
+
// Health & Status
|
|
88
|
+
// ==========================================
|
|
89
|
+
|
|
90
|
+
async isHealthy(): Promise<boolean> {
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(`${this.baseUrl}/auth/setup`)
|
|
93
|
+
return response.status === 200 || response.status === 400
|
|
94
|
+
} catch {
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async isInitialized(): Promise<boolean> {
|
|
100
|
+
try {
|
|
101
|
+
const response = await fetch(`${this.baseUrl}/auth/setup`)
|
|
102
|
+
if (response.status === 400) return true // "Auth already configured"
|
|
103
|
+
if (response.status === 200) {
|
|
104
|
+
const data = (await response.json()) as ProfilarrSetupStatus
|
|
105
|
+
return !data.needs_setup
|
|
106
|
+
}
|
|
107
|
+
return false
|
|
108
|
+
} catch {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ==========================================
|
|
114
|
+
// Authentication
|
|
115
|
+
// ==========================================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Authenticate with Profilarr.
|
|
119
|
+
* If not set up, performs initial setup.
|
|
120
|
+
* If already configured, logs in and retrieves API key.
|
|
121
|
+
*/
|
|
122
|
+
async authenticate(username: string, password: string): Promise<string> {
|
|
123
|
+
// Check if setup is needed
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch(`${this.baseUrl}/auth/setup`)
|
|
126
|
+
if (response.status === 200) {
|
|
127
|
+
const status = (await response.json()) as ProfilarrSetupStatus
|
|
128
|
+
if (status.needs_setup) {
|
|
129
|
+
debugLog("ProfilarrApi", "Performing initial setup")
|
|
130
|
+
const setupRes = await this.request<ProfilarrSetupResponse>("/auth/setup", {
|
|
131
|
+
method: "POST",
|
|
132
|
+
body: JSON.stringify({ username, password }),
|
|
133
|
+
})
|
|
134
|
+
this.apiKey = setupRes.api_key
|
|
135
|
+
return this.apiKey
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Ignore check error, try login
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Already configured, login to get API key
|
|
143
|
+
debugLog("ProfilarrApi", "Logging in to retrieve API key")
|
|
144
|
+
const loginRes = await fetch(`${this.baseUrl}/auth/authenticate`, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: { "Content-Type": "application/json" },
|
|
147
|
+
body: JSON.stringify({ username, password }),
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
if (!loginRes.ok) {
|
|
151
|
+
throw new Error(`Login failed: ${loginRes.statusText}`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const cookie = loginRes.headers.get("set-cookie")
|
|
155
|
+
if (!cookie) {
|
|
156
|
+
throw new Error("Login successful but no cookie received")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Get API key from settings using the cookie
|
|
160
|
+
const settingsRes = await fetch(`${this.baseUrl}/settings/general`, {
|
|
161
|
+
headers: { Cookie: cookie },
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
if (!settingsRes.ok) {
|
|
165
|
+
throw new Error(`Failed to fetch settings: ${settingsRes.statusText}`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const settings = (await settingsRes.json()) as ProfilarrSettings
|
|
169
|
+
this.apiKey = settings.api_key
|
|
170
|
+
return this.apiKey
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ==========================================
|
|
174
|
+
// Arr Configuration
|
|
175
|
+
// ==========================================
|
|
176
|
+
|
|
177
|
+
async getConfigs(): Promise<ProfilarrConfig[]> {
|
|
178
|
+
return this.request<ProfilarrConfig[]>("/arr/config")
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async addConfig(config: ProfilarrConfig): Promise<ProfilarrConfig> {
|
|
182
|
+
return this.request<ProfilarrConfig>("/arr/config", {
|
|
183
|
+
method: "POST",
|
|
184
|
+
body: JSON.stringify(config),
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Configure Radarr connection
|
|
190
|
+
* @returns The created/existing config, or null if failed
|
|
191
|
+
*/
|
|
192
|
+
async configureRadarr(hostname: string, port: number, apiKey: string): Promise<ProfilarrConfig | null> {
|
|
193
|
+
try {
|
|
194
|
+
const existingConfigs = await this.getConfigs()
|
|
195
|
+
const existingConfig = existingConfigs.find((c) => c.type === "radarr")
|
|
196
|
+
|
|
197
|
+
if (existingConfig) {
|
|
198
|
+
debugLog("ProfilarrApi", "Radarr already configured")
|
|
199
|
+
return existingConfig
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const arrServer = `http://${hostname}:${port}`
|
|
203
|
+
return await this.addConfig({
|
|
204
|
+
name: "Radarr",
|
|
205
|
+
type: "radarr",
|
|
206
|
+
tags: [],
|
|
207
|
+
arrServer,
|
|
208
|
+
apiKey,
|
|
209
|
+
sync_method: "manual",
|
|
210
|
+
sync_interval: 60,
|
|
211
|
+
import_as_unique: false,
|
|
212
|
+
data_to_sync: { profiles: [], customFormats: [] },
|
|
213
|
+
})
|
|
214
|
+
} catch (e) {
|
|
215
|
+
debugLog("ProfilarrApi", `Radarr config failed: ${e}`)
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Configure Sonarr connection
|
|
222
|
+
* @returns The created/existing config, or null if failed
|
|
223
|
+
*/
|
|
224
|
+
async configureSonarr(hostname: string, port: number, apiKey: string): Promise<ProfilarrConfig | null> {
|
|
225
|
+
try {
|
|
226
|
+
const existingConfigs = await this.getConfigs()
|
|
227
|
+
const existingConfig = existingConfigs.find((c) => c.type === "sonarr")
|
|
228
|
+
|
|
229
|
+
if (existingConfig) {
|
|
230
|
+
debugLog("ProfilarrApi", "Sonarr already configured")
|
|
231
|
+
return existingConfig
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const arrServer = `http://${hostname}:${port}`
|
|
235
|
+
return await this.addConfig({
|
|
236
|
+
name: "Sonarr",
|
|
237
|
+
type: "sonarr",
|
|
238
|
+
tags: [],
|
|
239
|
+
arrServer,
|
|
240
|
+
apiKey,
|
|
241
|
+
sync_method: "manual",
|
|
242
|
+
sync_interval: 60,
|
|
243
|
+
import_as_unique: false,
|
|
244
|
+
data_to_sync: { profiles: [], customFormats: [] },
|
|
245
|
+
})
|
|
246
|
+
} catch (e) {
|
|
247
|
+
debugLog("ProfilarrApi", `Sonarr config failed: ${e}`)
|
|
248
|
+
return null
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ==========================================
|
|
253
|
+
// Auto-Setup (IAutoSetupClient)
|
|
254
|
+
// ==========================================
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Run the auto-setup process for Profilarr.
|
|
258
|
+
* Only handles authentication, returns API key for env persistence.
|
|
259
|
+
* *arr connections are configured separately via configureRadarr/configureSonarr.
|
|
260
|
+
*/
|
|
261
|
+
async setup(options: AutoSetupOptions): Promise<AutoSetupResult> {
|
|
262
|
+
const { username, password } = options
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
// Check if reachable
|
|
266
|
+
const healthy = await this.isHealthy()
|
|
267
|
+
if (!healthy) {
|
|
268
|
+
return { success: false, message: "Profilarr not reachable" }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Authenticate (setup or login)
|
|
272
|
+
const apiKey = await this.authenticate(username, password)
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
success: true,
|
|
276
|
+
message: "Profilarr configured",
|
|
277
|
+
data: { apiKey },
|
|
278
|
+
envUpdates: { API_KEY_PROFILARR: apiKey },
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return { success: false, message: `${error}` }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
package/src/apps/registry.ts
CHANGED
|
@@ -31,6 +31,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
31
31
|
},
|
|
32
32
|
prowlarrCategoryIds: [2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080, 2090], // Movies + all sub-categories
|
|
33
33
|
homepage: { icon: "radarr.png", widget: "radarr" },
|
|
34
|
+
logVolume: "/config/logs",
|
|
34
35
|
},
|
|
35
36
|
|
|
36
37
|
sonarr: {
|
|
@@ -56,6 +57,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
56
57
|
},
|
|
57
58
|
prowlarrCategoryIds: [5000, 5010, 5020, 5030, 5040, 5045, 5050, 5060, 5070, 5080, 5090], // TV + all sub-categories
|
|
58
59
|
homepage: { icon: "sonarr.png", widget: "sonarr" },
|
|
60
|
+
logVolume: "/config/logs",
|
|
59
61
|
},
|
|
60
62
|
|
|
61
63
|
lidarr: {
|
|
@@ -79,6 +81,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
79
81
|
},
|
|
80
82
|
prowlarrCategoryIds: [3000, 3010, 3020, 3030, 3040, 3050, 3060], // Audio + all sub-categories
|
|
81
83
|
homepage: { icon: "lidarr.png", widget: "lidarr" },
|
|
84
|
+
logVolume: "/config/logs",
|
|
82
85
|
},
|
|
83
86
|
|
|
84
87
|
readarr: {
|
|
@@ -106,6 +109,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
106
109
|
warning: "Readarr is deprecated - no ARM64 support (project abandoned by upstream)",
|
|
107
110
|
},
|
|
108
111
|
homepage: { icon: "readarr.png", widget: "readarr" },
|
|
112
|
+
logVolume: "/config/logs",
|
|
109
113
|
},
|
|
110
114
|
|
|
111
115
|
bazarr: {
|
|
@@ -127,6 +131,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
127
131
|
selector: "auth.apikey",
|
|
128
132
|
},
|
|
129
133
|
homepage: { icon: "bazarr.png", widget: "bazarr" },
|
|
134
|
+
logVolume: "/config/log",
|
|
130
135
|
},
|
|
131
136
|
|
|
132
137
|
mylar3: {
|
|
@@ -149,6 +154,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
149
154
|
},
|
|
150
155
|
prowlarrCategoryIds: [7030], // Comics
|
|
151
156
|
homepage: { icon: "mylar.png", widget: "mylar" },
|
|
157
|
+
logVolume: "/app/mylar/logs",
|
|
152
158
|
// Note: Mylar3 is NOT an *arr app - has different API format (?cmd=<endpoint>)
|
|
153
159
|
// Root folder is configured via Web UI settings, not API
|
|
154
160
|
},
|
|
@@ -174,6 +180,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
174
180
|
},
|
|
175
181
|
prowlarrCategoryIds: [6000, 6010, 6020, 6030, 6040, 6045, 6050, 6060, 6070, 6080, 6090], // XXX + all sub-categories
|
|
176
182
|
homepage: { icon: "whisparr.png", widget: "sonarr" }, // Uses sonarr widget type
|
|
183
|
+
logVolume: "/config/logs",
|
|
177
184
|
},
|
|
178
185
|
|
|
179
186
|
audiobookshelf: {
|
|
@@ -192,6 +199,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
192
199
|
`${root}/data/media/audiobookshelf-metadata:/metadata`,
|
|
193
200
|
],
|
|
194
201
|
homepage: { icon: "audiobookshelf.png", widget: "audiobookshelf" },
|
|
202
|
+
logVolume: "/config/metadata/logs",
|
|
195
203
|
},
|
|
196
204
|
|
|
197
205
|
// === INDEXERS ===
|
|
@@ -213,6 +221,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
213
221
|
selector: "<ApiKey>(.*?)</ApiKey>",
|
|
214
222
|
},
|
|
215
223
|
homepage: { icon: "prowlarr.png", widget: "prowlarr" },
|
|
224
|
+
logVolume: "/config/logs",
|
|
216
225
|
},
|
|
217
226
|
|
|
218
227
|
jackett: {
|
|
@@ -231,6 +240,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
231
240
|
selector: "APIKey",
|
|
232
241
|
},
|
|
233
242
|
homepage: { icon: "jackett.png", widget: "jackett" },
|
|
243
|
+
logVolume: "/config/logs",
|
|
234
244
|
},
|
|
235
245
|
|
|
236
246
|
flaresolverr: {
|
|
@@ -290,6 +300,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
290
300
|
],
|
|
291
301
|
trashGuide: "docs/Downloaders/qBittorrent/",
|
|
292
302
|
homepage: { icon: "qbittorrent.png", widget: "qbittorrent" },
|
|
303
|
+
logVolume: "/config/qBittorrent/logs",
|
|
293
304
|
},
|
|
294
305
|
|
|
295
306
|
sabnzbd: {
|
|
@@ -311,6 +322,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
311
322
|
selector: "api_key\\s*=\\s*(.+)",
|
|
312
323
|
},
|
|
313
324
|
homepage: { icon: "sabnzbd.png", widget: "sabnzbd" },
|
|
325
|
+
logVolume: "/config/logs",
|
|
314
326
|
},
|
|
315
327
|
|
|
316
328
|
slskd: {
|
|
@@ -352,6 +364,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
352
364
|
type: "partial",
|
|
353
365
|
description: "Generates slskd.yml with API key for Homepage widget and Soularr integration",
|
|
354
366
|
},
|
|
367
|
+
logVolume: "/app/logs",
|
|
355
368
|
},
|
|
356
369
|
|
|
357
370
|
soularr: {
|
|
@@ -390,6 +403,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
390
403
|
selector: 'PlexOnlineToken="([^"]+)"',
|
|
391
404
|
},
|
|
392
405
|
homepage: { icon: "plex.png", widget: "plex" },
|
|
406
|
+
logVolume: "/config/Library/Application Support/Plex Media Server/Logs",
|
|
393
407
|
autoSetup: {
|
|
394
408
|
type: "full",
|
|
395
409
|
description: "Claim server with token, create media libraries",
|
|
@@ -408,6 +422,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
408
422
|
pgid: 13000,
|
|
409
423
|
volumes: (root) => [`${root}/config/jellyfin:/config`, `${root}/data/media:/data/media`],
|
|
410
424
|
homepage: { icon: "jellyfin.png", widget: "jellyfin" },
|
|
425
|
+
logVolume: "/config/log",
|
|
411
426
|
},
|
|
412
427
|
|
|
413
428
|
tautulli: {
|
|
@@ -432,6 +447,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
432
447
|
description: "Connect to Plex, enable API",
|
|
433
448
|
requires: ["plex"],
|
|
434
449
|
},
|
|
450
|
+
logVolume: "/config/logs",
|
|
435
451
|
},
|
|
436
452
|
|
|
437
453
|
tdarr: {
|
|
@@ -451,6 +467,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
451
467
|
],
|
|
452
468
|
environment: { serverIP: "0.0.0.0", internalNode: "true" },
|
|
453
469
|
homepage: { icon: "tdarr.png", widget: "tdarr" },
|
|
470
|
+
logVolume: "/app/logs",
|
|
454
471
|
},
|
|
455
472
|
|
|
456
473
|
// === REQUEST MANAGEMENT ===
|
|
@@ -476,6 +493,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
476
493
|
description: "Connect to Plex, configure Radarr/Sonarr",
|
|
477
494
|
requires: ["plex"],
|
|
478
495
|
},
|
|
496
|
+
logVolume: "/app/config/logs",
|
|
479
497
|
},
|
|
480
498
|
|
|
481
499
|
jellyseerr: {
|
|
@@ -495,6 +513,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
495
513
|
selector: "main.apiKey",
|
|
496
514
|
},
|
|
497
515
|
homepage: { icon: "jellyseerr.png", widget: "jellyseerr" },
|
|
516
|
+
logVolume: "/app/config/logs",
|
|
498
517
|
},
|
|
499
518
|
|
|
500
519
|
// === DASHBOARDS ===
|
|
@@ -514,6 +533,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
514
533
|
"/var/run/docker.sock:/var/run/docker.sock",
|
|
515
534
|
],
|
|
516
535
|
homepage: { icon: "homarr.png" }, // No widget, just icon (it's a dashboard itself)
|
|
536
|
+
logVolume: "/app/data/logs",
|
|
517
537
|
},
|
|
518
538
|
|
|
519
539
|
heimdall: {
|
|
@@ -527,6 +547,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
527
547
|
pgid: 13000,
|
|
528
548
|
volumes: (root) => [`${root}/config/heimdall:/config`],
|
|
529
549
|
homepage: { icon: "heimdall.png" }, // No widget, just icon (it's a dashboard itself)
|
|
550
|
+
logVolume: "/config/log",
|
|
530
551
|
},
|
|
531
552
|
|
|
532
553
|
homepage: {
|
|
@@ -544,6 +565,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
544
565
|
HOMEPAGE_ALLOWED_HOSTS:
|
|
545
566
|
"homepage,homepage.${CLOUDFLARE_DNS_ZONE},${CLOUDFLARE_DNS_ZONE},localhost,${LOCAL_DOCKER_IP},${LOCAL_DOCKER_IP}:3009",
|
|
546
567
|
},
|
|
568
|
+
logVolume: "/app/config/logs",
|
|
547
569
|
},
|
|
548
570
|
|
|
549
571
|
// === UTILITIES ===
|
|
@@ -559,6 +581,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
559
581
|
volumes: (root) => [`${root}/config/portainer:/data`, "/var/run/docker.sock:/var/run/docker.sock"],
|
|
560
582
|
minPasswordLength: 12, // Portainer requires minimum 12 character password
|
|
561
583
|
homepage: { icon: "portainer.png", widget: "portainer" },
|
|
584
|
+
logVolume: "/data/logs",
|
|
562
585
|
},
|
|
563
586
|
|
|
564
587
|
huntarr: {
|
|
@@ -591,6 +614,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
591
614
|
description: "Test connections to Sonarr, Radarr, Lidarr, Readarr, Whisparr",
|
|
592
615
|
requires: ["sonarr", "radarr"],
|
|
593
616
|
},
|
|
617
|
+
logVolume: "/config/logs",
|
|
594
618
|
},
|
|
595
619
|
|
|
596
620
|
unpackerr: {
|
|
@@ -603,6 +627,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
603
627
|
puid: 0,
|
|
604
628
|
pgid: 13000,
|
|
605
629
|
volumes: (root) => [`${root}/config/unpackerr:/config`, `${root}/data:/data`],
|
|
630
|
+
logVolume: "/config/logs",
|
|
606
631
|
},
|
|
607
632
|
|
|
608
633
|
filebot: {
|
|
@@ -616,6 +641,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
616
641
|
pgid: 13000,
|
|
617
642
|
volumes: (root) => [`${root}/config/filebot:/data`, `${root}/data:/data`],
|
|
618
643
|
environment: { DARK_MODE: "1" },
|
|
644
|
+
logVolume: "/data/logs",
|
|
619
645
|
},
|
|
620
646
|
|
|
621
647
|
chromium: {
|
|
@@ -629,6 +655,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
629
655
|
pgid: 13000,
|
|
630
656
|
volumes: (root) => [`${root}/config/chromium:/config`],
|
|
631
657
|
environment: { TITLE: "Chromium" },
|
|
658
|
+
logVolume: "/config/logs",
|
|
632
659
|
},
|
|
633
660
|
|
|
634
661
|
guacamole: {
|
|
@@ -664,6 +691,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
664
691
|
mask: true,
|
|
665
692
|
},
|
|
666
693
|
],
|
|
694
|
+
logVolume: "/guacamole/logs",
|
|
667
695
|
},
|
|
668
696
|
|
|
669
697
|
guacd: {
|
|
@@ -677,6 +705,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
677
705
|
pgid: 13000,
|
|
678
706
|
volumes: (root) => [`${root}/config/guacd:/config`], // Not really used but keeps structure
|
|
679
707
|
dependsOn: ["postgresql"],
|
|
708
|
+
logVolume: "/guacd/logs",
|
|
680
709
|
},
|
|
681
710
|
|
|
682
711
|
"ddns-updater": {
|
|
@@ -689,6 +718,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
689
718
|
puid: 13000,
|
|
690
719
|
pgid: 13000,
|
|
691
720
|
volumes: (root) => [`${root}/config/ddns-updater:/data`],
|
|
721
|
+
logVolume: "/data/logs",
|
|
692
722
|
},
|
|
693
723
|
|
|
694
724
|
easiarr: {
|
|
@@ -723,6 +753,43 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
723
753
|
},
|
|
724
754
|
},
|
|
725
755
|
|
|
756
|
+
recyclarr: {
|
|
757
|
+
id: "recyclarr",
|
|
758
|
+
name: "Recyclarr",
|
|
759
|
+
description: "Automatic TRaSH Guides profile sync for *arr apps",
|
|
760
|
+
category: "utility",
|
|
761
|
+
defaultPort: 0, // No web UI - runs as cron job
|
|
762
|
+
image: "ghcr.io/recyclarr/recyclarr:latest",
|
|
763
|
+
puid: 0, // Uses Docker user: directive
|
|
764
|
+
pgid: 0,
|
|
765
|
+
useDockerUser: true,
|
|
766
|
+
volumes: (root) => [`${root}/config/recyclarr:/config`],
|
|
767
|
+
environment: {
|
|
768
|
+
RECYCLARR_CREATE_CONFIG: "false", // We generate the config ourselves
|
|
769
|
+
CRON_SCHEDULE: "@daily", // Run sync daily at midnight
|
|
770
|
+
},
|
|
771
|
+
dependsOn: ["radarr", "sonarr"],
|
|
772
|
+
autoSetup: {
|
|
773
|
+
type: "full",
|
|
774
|
+
description: "Generate recyclarr.yml config for enabled *arr apps with TRaSH profiles",
|
|
775
|
+
requires: ["radarr", "sonarr"],
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
profilarr: {
|
|
779
|
+
id: "profilarr",
|
|
780
|
+
name: "Profilarr",
|
|
781
|
+
description: "Web UI for managing TRaSH Guides profiles (alternative to Recyclarr)",
|
|
782
|
+
category: "utility",
|
|
783
|
+
defaultPort: 6868,
|
|
784
|
+
image: "santiagosayshey/profilarr:latest",
|
|
785
|
+
puid: 1000,
|
|
786
|
+
pgid: 1000,
|
|
787
|
+
volumes: (root) => [`${root}/config/profilarr:/config`],
|
|
788
|
+
dependsOn: ["radarr", "sonarr"],
|
|
789
|
+
homepage: {
|
|
790
|
+
icon: "profilarr",
|
|
791
|
+
},
|
|
792
|
+
},
|
|
726
793
|
// === VPN ===
|
|
727
794
|
gluetun: {
|
|
728
795
|
id: "gluetun",
|
|
@@ -770,6 +837,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
770
837
|
},
|
|
771
838
|
],
|
|
772
839
|
homepage: { icon: "gluetun.png", widget: "gluetun" },
|
|
840
|
+
logVolume: "/gluetun",
|
|
773
841
|
},
|
|
774
842
|
|
|
775
843
|
// === MONITORING ===
|
|
@@ -789,6 +857,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
789
857
|
description: "Setup admin user, configure Prometheus datasource",
|
|
790
858
|
requires: ["prometheus"],
|
|
791
859
|
},
|
|
860
|
+
logVolume: "/var/log/grafana",
|
|
792
861
|
},
|
|
793
862
|
|
|
794
863
|
prometheus: {
|
|
@@ -802,6 +871,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
802
871
|
pgid: 13000,
|
|
803
872
|
volumes: (root) => [`${root}/config/prometheus:/prometheus`],
|
|
804
873
|
homepage: { icon: "prometheus.png", widget: "prometheus" },
|
|
874
|
+
logVolume: "/prometheus/logs",
|
|
805
875
|
},
|
|
806
876
|
|
|
807
877
|
dozzle: {
|
|
@@ -834,6 +904,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
834
904
|
type: "full",
|
|
835
905
|
description: "Create admin user, add monitors for enabled apps",
|
|
836
906
|
},
|
|
907
|
+
logVolume: "/app/data/logs",
|
|
837
908
|
},
|
|
838
909
|
|
|
839
910
|
// === INFRASTRUCTURE ===
|
|
@@ -862,6 +933,27 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
862
933
|
},
|
|
863
934
|
],
|
|
864
935
|
homepage: { icon: "traefik.png", widget: "traefik" },
|
|
936
|
+
logVolume: "/etc/traefik/logs",
|
|
937
|
+
},
|
|
938
|
+
|
|
939
|
+
caddy: {
|
|
940
|
+
id: "caddy",
|
|
941
|
+
name: "Caddy",
|
|
942
|
+
description: "Web server with automatic HTTPS and reverse proxy",
|
|
943
|
+
category: "infrastructure",
|
|
944
|
+
defaultPort: 80,
|
|
945
|
+
internalPort: 80,
|
|
946
|
+
secondaryPorts: ["443:443"],
|
|
947
|
+
image: "caddy:latest",
|
|
948
|
+
puid: 0,
|
|
949
|
+
pgid: 0,
|
|
950
|
+
volumes: (root) => [
|
|
951
|
+
`${root}/config/caddy/Caddyfile:/etc/caddy/Caddyfile`,
|
|
952
|
+
`${root}/config/caddy/data:/data`,
|
|
953
|
+
`${root}/config/caddy/config:/config`,
|
|
954
|
+
],
|
|
955
|
+
homepage: { icon: "caddy.png" },
|
|
956
|
+
logVolume: "/data/logs",
|
|
865
957
|
},
|
|
866
958
|
|
|
867
959
|
cloudflared: {
|