@muhammedaksam/easiarr 0.7.6 → 0.7.8
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhammedaksam/easiarr",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
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",
|
package/src/api/portainer-api.ts
CHANGED
|
@@ -64,11 +64,22 @@ export interface PortainerApiKeyResponse {
|
|
|
64
64
|
export class PortainerApiClient {
|
|
65
65
|
private baseUrl: string
|
|
66
66
|
private jwtToken: string | null = null
|
|
67
|
+
private apiKey: string | null = null
|
|
67
68
|
|
|
68
69
|
constructor(host: string, port: number) {
|
|
69
70
|
this.baseUrl = `http://${host}:${port}`
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Set API key for authentication (alternative to JWT login)
|
|
75
|
+
* @param apiKey - The Portainer API key (e.g., ptr_xxx)
|
|
76
|
+
*/
|
|
77
|
+
setApiKey(key: string): void {
|
|
78
|
+
if (key) {
|
|
79
|
+
this.apiKey = key
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
72
83
|
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
73
84
|
const url = `${this.baseUrl}/api${endpoint}`
|
|
74
85
|
const headers: Record<string, string> = {
|
|
@@ -76,8 +87,10 @@ export class PortainerApiClient {
|
|
|
76
87
|
...(options.headers as Record<string, string>),
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
// Add
|
|
80
|
-
if (this.
|
|
90
|
+
// Add authentication - prefer API key, fallback to JWT
|
|
91
|
+
if (this.apiKey) {
|
|
92
|
+
headers["X-API-Key"] = this.apiKey
|
|
93
|
+
} else if (this.jwtToken) {
|
|
81
94
|
headers["Authorization"] = `Bearer ${this.jwtToken}`
|
|
82
95
|
}
|
|
83
96
|
|
|
@@ -240,6 +253,40 @@ export class PortainerApiClient {
|
|
|
240
253
|
return this.request<PortainerEndpoint[]>("/endpoints")
|
|
241
254
|
}
|
|
242
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Get the local Docker socket environment ID.
|
|
258
|
+
* Finds the first endpoint that uses unix:///var/run/docker.sock or is named "local".
|
|
259
|
+
* @returns The environment ID or null if not found
|
|
260
|
+
*/
|
|
261
|
+
async getLocalEnvironmentId(): Promise<number | null> {
|
|
262
|
+
try {
|
|
263
|
+
const endpoints = await this.getEndpoints()
|
|
264
|
+
|
|
265
|
+
// First, try to find one using Docker socket
|
|
266
|
+
const socketEndpoint = endpoints.find(
|
|
267
|
+
(e) => e.URL === "unix:///var/run/docker.sock" || e.URL.includes("docker.sock")
|
|
268
|
+
)
|
|
269
|
+
if (socketEndpoint) {
|
|
270
|
+
return socketEndpoint.Id
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Fallback: find one named "local"
|
|
274
|
+
const localEndpoint = endpoints.find((e) => e.Name.toLowerCase() === "local")
|
|
275
|
+
if (localEndpoint) {
|
|
276
|
+
return localEndpoint.Id
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Last resort: return the first endpoint if any exist
|
|
280
|
+
if (endpoints.length > 0) {
|
|
281
|
+
return endpoints[0].Id
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return null
|
|
285
|
+
} catch {
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
243
290
|
/**
|
|
244
291
|
* Get all containers for an endpoint
|
|
245
292
|
*/
|
package/src/apps/registry.ts
CHANGED
|
@@ -148,6 +148,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
148
148
|
generateIfMissing: true,
|
|
149
149
|
},
|
|
150
150
|
prowlarrCategoryIds: [7030], // Comics
|
|
151
|
+
homepage: { icon: "mylar.png" },
|
|
151
152
|
// Note: Mylar3 is NOT an *arr app - has different API format (?cmd=<endpoint>)
|
|
152
153
|
// Root folder is configured via Web UI settings, not API
|
|
153
154
|
},
|
|
@@ -10,7 +10,8 @@ import type { EasiarrConfig, AppCategory } from "./schema"
|
|
|
10
10
|
import { APP_CATEGORIES } from "./schema"
|
|
11
11
|
import { getApp } from "../apps/registry"
|
|
12
12
|
import { CATEGORY_ORDER } from "../apps/categories"
|
|
13
|
-
import { readEnvSync, getLocalIp } from "../utils/env"
|
|
13
|
+
import { readEnvSync, getLocalIp, updateEnv } from "../utils/env"
|
|
14
|
+
import { PortainerApiClient } from "../api/portainer-api"
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Get the Homepage config directory path
|
|
@@ -86,6 +87,38 @@ export async function generateServicesYaml(config: EasiarrConfig): Promise<strin
|
|
|
86
87
|
service.widget.key = apiKey
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
// Add widget-specific credentials from env
|
|
91
|
+
if (appDef.id === "qbittorrent") {
|
|
92
|
+
const username = env["USERNAME_QBITTORRENT"]
|
|
93
|
+
const password = env["PASSWORD_QBITTORRENT"]
|
|
94
|
+
if (username) service.widget.username = username
|
|
95
|
+
if (password) service.widget.password = password
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (appDef.id === "portainer") {
|
|
99
|
+
// Try to auto-detect Portainer environment ID
|
|
100
|
+
// User can override with PORTAINER_ENV in .env file
|
|
101
|
+
if (env["PORTAINER_ENV"]) {
|
|
102
|
+
service.widget.env = env["PORTAINER_ENV"]
|
|
103
|
+
} else {
|
|
104
|
+
// Auto-detect from Portainer API
|
|
105
|
+
const portainerPort = appConfig.port ?? appDef.defaultPort
|
|
106
|
+
const portainerClient = new PortainerApiClient(localIp, portainerPort)
|
|
107
|
+
const apiKey = env["API_KEY_PORTAINER"]
|
|
108
|
+
if (apiKey) {
|
|
109
|
+
portainerClient.setApiKey(apiKey)
|
|
110
|
+
}
|
|
111
|
+
const localEnvId = await portainerClient.getLocalEnvironmentId()
|
|
112
|
+
const envIdStr = localEnvId?.toString() ?? "1"
|
|
113
|
+
service.widget.env = envIdStr
|
|
114
|
+
|
|
115
|
+
// Persist the detected env ID to .env for future use
|
|
116
|
+
if (localEnvId) {
|
|
117
|
+
await updateEnv({ PORTAINER_ENV: envIdStr })
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
89
122
|
// Add any custom widget fields
|
|
90
123
|
if (appDef.homepage.widgetFields) {
|
|
91
124
|
Object.assign(service.widget, appDef.homepage.widgetFields)
|