@jurislm/coolify-plugin 3.7.11 → 4.0.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/.codex-plugin/plugin.json +1 -1
- package/.mcp.json.example +2 -2
- package/README.md +5 -5
- package/api/manifest.json +15 -4
- package/dist/capabilities.d.ts +1 -1
- package/dist/capabilities.js +28 -24
- package/dist/client.js +3 -18
- package/dist/config.js +5 -5
- package/dist/generated/coolify-api.d.ts +114 -115
- package/dist/generated/coolify-zod.d.ts +84 -28
- package/dist/generated/coolify-zod.js +9 -6
- package/dist/generated/operations.js +44 -22
- package/dist/server.js +3 -25
- package/openapi/coolify-openapi.json +1934 -1830
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/coolify/SKILL.md +1 -1
package/.mcp.json.example
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"command": "bunx",
|
|
5
5
|
"args": ["-y", "@jurislm/coolify-plugin@latest"],
|
|
6
6
|
"env": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"COOLIFY_BASE_URL": "https://coolify.example",
|
|
8
|
+
"COOLIFY_ACCESS_TOKEN": "<set-in-host-environment>"
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
}
|
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# @jurislm/coolify-plugin
|
|
2
2
|
|
|
3
|
-
Portable Coolify MCP plugin. It exposes focused `coolify_*` stdio tools generated from
|
|
3
|
+
Portable Coolify MCP plugin. It exposes focused `coolify_*` stdio tools generated from one pinned Coolify API contract.
|
|
4
4
|
|
|
5
5
|
## Configure
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The plugin reads the same global environment variables used by the Coolify setup:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
export
|
|
11
|
-
export
|
|
10
|
+
export COOLIFY_BASE_URL=https://coolify.example
|
|
11
|
+
export COOLIFY_ACCESS_TOKEN=your-api-token
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Build and run locally:
|
|
@@ -30,7 +30,7 @@ codex plugin add coolify-plugin@coolify-marketplace
|
|
|
30
30
|
|
|
31
31
|
## OpenAPI contract
|
|
32
32
|
|
|
33
|
-
`openapi/coolify-openapi.json` is
|
|
33
|
+
`openapi/coolify-openapi.json` is built from the official Coolify v4.3.23 release. `api/manifest.json` records the upstream SHA-256, the current API contract corrections, and the persisted SHA-256. `bun run api:check` verifies the contract offline before checking generated parity. The runtime uses the generated schemas directly.
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
36
|
bun run api:fetch
|
package/api/manifest.json
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sourceUrl": "https://raw.githubusercontent.com/coollabsio/coolify/
|
|
3
|
-
"fetchedAt": "2026-09-
|
|
4
|
-
"sha256": "
|
|
2
|
+
"sourceUrl": "https://raw.githubusercontent.com/coollabsio/coolify/v4.3.23/openapi.json",
|
|
3
|
+
"fetchedAt": "2026-09-23T12:23:33.000Z",
|
|
4
|
+
"sha256": "e80f5efe8df44d0887285a8b923bca84bfc3af6a62b1e63405164e71939d6595",
|
|
5
|
+
"upstreamSha256": "964360ffb3d4c026252aa4657d6561190f59ac4256395bf0645bf6a4f171ec88",
|
|
5
6
|
"openapiVersion": "3.1.0",
|
|
6
7
|
"infoVersion": "0.1",
|
|
7
8
|
"pathCount": 192,
|
|
8
|
-
"operationCount": 275
|
|
9
|
+
"operationCount": 275,
|
|
10
|
+
"contractCorrections": [
|
|
11
|
+
"List database and resource responses are arrays",
|
|
12
|
+
"Application deployment history is a count plus deployments collection",
|
|
13
|
+
"Resource field nullability and identifiers match the active API",
|
|
14
|
+
"Database updates expose custom Docker run options",
|
|
15
|
+
"Unsupported server validation operation removed",
|
|
16
|
+
"Database PATCH omits unspecified health-check defaults",
|
|
17
|
+
"Server reachability and service status are response fields",
|
|
18
|
+
"Server metadata, deployment git type, and environment descriptions can be null"
|
|
19
|
+
]
|
|
9
20
|
}
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { CoolifyClient } from "./client.js";
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function registerCapabilities(server: McpServer, client: CoolifyClient): void;
|
package/dist/capabilities.js
CHANGED
|
@@ -8,16 +8,10 @@ const truncationPrefix = "...[truncated]...\n";
|
|
|
8
8
|
function rows(value) {
|
|
9
9
|
if (Array.isArray(value))
|
|
10
10
|
return value.filter((item) => !!item && typeof item === "object");
|
|
11
|
-
if (!value || typeof value !== "object")
|
|
12
|
-
return [];
|
|
13
|
-
for (const key of ["data", "items", "results", "deployments", "applications", "databases", "resources", "services"]) {
|
|
14
|
-
const nested = value[key];
|
|
15
|
-
if (Array.isArray(nested))
|
|
16
|
-
return rows(nested);
|
|
17
|
-
}
|
|
18
11
|
return [];
|
|
19
12
|
}
|
|
20
13
|
function status(value) { return typeof value.status === "string" ? value.status : ""; }
|
|
14
|
+
function running(value) { return /(^|:)running($|:)/iu.test(status(value)); }
|
|
21
15
|
function unhealthy(value) { return /exited|unhealthy|error|stopped/iu.test(status(value)); }
|
|
22
16
|
function errorMessage(value) { return value instanceof Error ? value.message : String(value); }
|
|
23
17
|
function truncateLogs(logs, lineLimit = 200, charLimit = 50_000) {
|
|
@@ -31,14 +25,15 @@ function truncateLogs(logs, lineLimit = 200, charLimit = 50_000) {
|
|
|
31
25
|
return result;
|
|
32
26
|
}
|
|
33
27
|
function environmentSummary(value) {
|
|
34
|
-
return rows(value).map((item) => ({ key: item.key, is_build_time: item.
|
|
28
|
+
return rows(value).map((item) => ({ key: item.key, is_build_time: item.is_buildtime === true }));
|
|
35
29
|
}
|
|
36
|
-
export function
|
|
30
|
+
export function registerCapabilities(server, client) {
|
|
37
31
|
const call = async (name, input = {}) => {
|
|
38
32
|
const operation = operations.find((item) => item.name === name);
|
|
39
33
|
if (!operation)
|
|
40
34
|
throw new Error(`Missing generated operation: ${name}`);
|
|
41
|
-
|
|
35
|
+
const data = (await client.request(operation, operation.inputSchema.parse(input))).data;
|
|
36
|
+
return operation.responseSchema.parse(data);
|
|
42
37
|
};
|
|
43
38
|
const result = (name, data) => {
|
|
44
39
|
const structuredContent = redactSensitive({ data, status: 200, request: { method: "COMPOSITE", path: `/capabilities/${name}` } });
|
|
@@ -89,6 +84,14 @@ export function registerV36Capabilities(server, client) {
|
|
|
89
84
|
const servers = rows(await call("coolify_list_servers"));
|
|
90
85
|
return servers.find((item) => [item.uuid, item.name, item.ip].some((value) => typeof value === "string" && value.toLowerCase().includes(query.toLowerCase())));
|
|
91
86
|
};
|
|
87
|
+
const projectApplications = async (projectUuid) => {
|
|
88
|
+
const [apps, environments] = await Promise.all([
|
|
89
|
+
call("coolify_list_applications"),
|
|
90
|
+
call("coolify_get_environments", { uuid: projectUuid }),
|
|
91
|
+
]);
|
|
92
|
+
const environmentIds = new Set(rows(environments).map((environment) => environment.id));
|
|
93
|
+
return rows(apps).filter((app) => environmentIds.has(app.environment_id));
|
|
94
|
+
};
|
|
92
95
|
register("coolify_get_mcp_version", "Get the local Coolify plugin version.", z.object({}), { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async () => ({ name: "@jurislm/coolify-plugin", version: pluginVersion }));
|
|
93
96
|
register("coolify_get_infrastructure_overview", "Summarize Coolify infrastructure.", z.object({}), { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, overview);
|
|
94
97
|
register("coolify_diagnose_application", "Diagnose an application by UUID, name, or domain.", z.object({ query: z.string() }), { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ query }) => {
|
|
@@ -103,7 +106,7 @@ export function registerV36Capabilities(server, client) {
|
|
|
103
106
|
return { application: null, health: { status: "unknown", issues: [] }, logs: null, environment_variables: { count: 0, variables: [] }, recent_deployments: [], errors: [`No application found matching ${query}`] };
|
|
104
107
|
const names = ["application", "logs", "environment_variables", "deployments"];
|
|
105
108
|
const settled = await Promise.allSettled([
|
|
106
|
-
call("coolify_get_application_by_uuid", { uuid: app.uuid }), call("coolify_get_application_logs_by_uuid", { uuid: app.uuid, lines: 50 }), call("coolify_list_envs_by_application_uuid", { uuid: app.uuid }), call("coolify_list_deployments_by_app_uuid", { uuid: app.uuid }),
|
|
109
|
+
call("coolify_get_application_by_uuid", { uuid: app.uuid }), running(app) ? call("coolify_get_application_logs_by_uuid", { uuid: app.uuid, lines: 50 }) : Promise.resolve(null), call("coolify_list_envs_by_application_uuid", { uuid: app.uuid }), call("coolify_list_deployments_by_app_uuid", { uuid: app.uuid }),
|
|
107
110
|
]);
|
|
108
111
|
const errors = [];
|
|
109
112
|
const value = (index) => { const entry = settled[index]; if (entry.status === "fulfilled")
|
|
@@ -111,7 +114,8 @@ export function registerV36Capabilities(server, client) {
|
|
|
111
114
|
const details = value(0);
|
|
112
115
|
const rawLogs = value(1);
|
|
113
116
|
const envVars = environmentSummary(value(2));
|
|
114
|
-
const
|
|
117
|
+
const deploymentCollection = value(3);
|
|
118
|
+
const deployments = rows(deploymentCollection?.deployments);
|
|
115
119
|
const appInfo = details ?? app;
|
|
116
120
|
const appStatus = status(appInfo);
|
|
117
121
|
const issues = [];
|
|
@@ -127,8 +131,8 @@ export function registerV36Capabilities(server, client) {
|
|
|
127
131
|
health = "unhealthy";
|
|
128
132
|
issues.push(`${failed} failed deployment(s) in last 5`);
|
|
129
133
|
}
|
|
130
|
-
const logs =
|
|
131
|
-
return { application: { uuid: appInfo.uuid, name: appInfo.name, status: appStatus || "unknown", fqdn: appInfo.fqdn ?? null, git_repository: appInfo.git_repository ?? null, git_branch: appInfo.git_branch ?? null }, health: { status: health, issues }, logs: logs === null ? null : truncateLogs(logs), environment_variables: { count: envVars.length, variables: envVars }, recent_deployments: deployments.slice(0, 5).map((item) => ({ uuid: item.
|
|
134
|
+
const logs = rawLogs && typeof rawLogs === "object" && typeof rawLogs.logs === "string" ? rawLogs.logs : null;
|
|
135
|
+
return { application: { uuid: appInfo.uuid, name: appInfo.name, status: appStatus || "unknown", fqdn: appInfo.fqdn ?? null, git_repository: appInfo.git_repository ?? null, git_branch: appInfo.git_branch ?? null }, health: { status: health, issues }, logs: logs === null ? null : truncateLogs(logs), environment_variables: { count: envVars.length, variables: envVars }, recent_deployments: deployments.slice(0, 5).map((item) => ({ uuid: item.deployment_uuid, status: item.status, created_at: item.created_at })), ...(errors.length ? { errors } : {}) };
|
|
132
136
|
});
|
|
133
137
|
register("coolify_diagnose_server", "Diagnose a server by UUID, name, or IP.", z.object({ query: z.string() }), { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ query }) => {
|
|
134
138
|
let found;
|
|
@@ -136,13 +140,13 @@ export function registerV36Capabilities(server, client) {
|
|
|
136
140
|
found = await serverByQuery(String(query));
|
|
137
141
|
}
|
|
138
142
|
catch (error) {
|
|
139
|
-
return { server: null, health: { status: "unknown", issues: [] }, resources: [], domains: [],
|
|
143
|
+
return { server: null, health: { status: "unknown", issues: [] }, resources: [], domains: [], errors: [errorMessage(error)] };
|
|
140
144
|
}
|
|
141
145
|
if (!found?.uuid)
|
|
142
|
-
return { server: null, health: { status: "unknown", issues: [] }, resources: [], domains: [],
|
|
143
|
-
const names = ["server", "resources", "domains"
|
|
146
|
+
return { server: null, health: { status: "unknown", issues: [] }, resources: [], domains: [], errors: [`No server found matching ${query}`] };
|
|
147
|
+
const names = ["server", "resources", "domains"];
|
|
144
148
|
const settled = await Promise.allSettled([
|
|
145
|
-
call("coolify_get_server_by_uuid", { uuid: found.uuid }), call("coolify_get_resources_by_server_uuid", { uuid: found.uuid }), call("coolify_get_domains_by_server_uuid", { uuid: found.uuid }),
|
|
149
|
+
call("coolify_get_server_by_uuid", { uuid: found.uuid }), call("coolify_get_resources_by_server_uuid", { uuid: found.uuid }), call("coolify_get_domains_by_server_uuid", { uuid: found.uuid }),
|
|
146
150
|
]);
|
|
147
151
|
const errors = [];
|
|
148
152
|
const value = (index) => { const entry = settled[index]; if (entry.status === "fulfilled")
|
|
@@ -157,7 +161,7 @@ export function registerV36Capabilities(server, client) {
|
|
|
157
161
|
const unhealthyResources = resources.filter(unhealthy).length;
|
|
158
162
|
if (unhealthyResources)
|
|
159
163
|
issues.push(`${unhealthyResources} unhealthy resource(s)`);
|
|
160
|
-
return { server: { uuid: details.uuid, name: details.name, ip: details.ip, status: details.status ?? null, is_reachable: details.is_reachable ?? null }, health: { status: issues.length ? "unhealthy" : details.is_reachable === true ? "healthy" : "unknown", issues }, resources: resources.map((item) => ({ uuid: item.uuid, name: item.name, type: item.type, status: item.status })), domains: rows(value(2)),
|
|
164
|
+
return { server: { uuid: details.uuid, name: details.name, ip: details.ip, status: details.status ?? null, is_reachable: details.is_reachable ?? null }, health: { status: issues.length ? "unhealthy" : details.is_reachable === true ? "healthy" : "unknown", issues }, resources: resources.map((item) => ({ uuid: item.uuid, name: item.name, type: item.type, status: item.status })), domains: rows(value(2)), ...(errors.length ? { errors } : {}) };
|
|
161
165
|
});
|
|
162
166
|
register("coolify_find_issues", "Find unhealthy Coolify infrastructure.", z.object({}), { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async () => {
|
|
163
167
|
const names = ["servers", "applications", "databases", "services"];
|
|
@@ -168,7 +172,7 @@ export function registerV36Capabilities(server, client) {
|
|
|
168
172
|
values.slice(1).forEach((items, index) => items.filter(unhealthy).forEach((item) => issues.push({ type: names[index + 1].slice(0, -1), uuid: item.uuid, name: item.name, issue: `${names[index + 1].slice(0, -1)} status: ${status(item)}`, status: status(item) })));
|
|
169
173
|
return { summary: { total_issues: issues.length, unhealthy_applications: issues.filter((item) => item.type === "application").length, unhealthy_databases: issues.filter((item) => item.type === "database").length, unhealthy_services: issues.filter((item) => item.type === "service").length, unreachable_servers: issues.filter((item) => item.type === "server").length }, issues, ...(errors.length ? { errors } : {}) };
|
|
170
174
|
});
|
|
171
|
-
register("coolify_restart_project_applications", "Restart every application in a project.", z.object({ project_uuid: z.string() }), { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ project_uuid }) => batch(
|
|
175
|
+
register("coolify_restart_project_applications", "Restart every application in a project.", z.object({ project_uuid: z.string() }), { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ project_uuid }) => batch(await projectApplications(String(project_uuid)), (app) => call("coolify_restart_application_by_uuid", { uuid: app.uuid })));
|
|
172
176
|
register("coolify_bulk_update_application_env", "Update an environment variable across applications.", z.object({ app_uuids: z.array(z.string()), key: z.string(), value: z.string() }).strict(), { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ app_uuids, key, value }) => {
|
|
173
177
|
if (!app_uuids.length)
|
|
174
178
|
return batch([], async () => undefined);
|
|
@@ -176,8 +180,8 @@ export function registerV36Capabilities(server, client) {
|
|
|
176
180
|
const labels = new Map(names.map((item) => [item.uuid, item.name ?? item.uuid]));
|
|
177
181
|
return batch(app_uuids.map((uuid) => ({ uuid, name: labels.get(uuid) ?? uuid })), (app) => call("coolify_update_env_by_application_uuid", { uuid: app.uuid, body: { key, value } }));
|
|
178
182
|
});
|
|
179
|
-
register("coolify_stop_all_applications", "Emergency stop every running application.", z.object({ confirm_stop_all_applications: z.literal(true) }), { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, async () => batch(rows(await call("coolify_list_applications")).filter(
|
|
180
|
-
register("coolify_redeploy_project_applications", "Redeploy every application in a project.", z.object({ project_uuid: z.string(), force: z.boolean().optional() }), { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ project_uuid, force }) => batch(
|
|
183
|
+
register("coolify_stop_all_applications", "Emergency stop every running application.", z.object({ confirm_stop_all_applications: z.literal(true) }), { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, async () => batch(rows(await call("coolify_list_applications")).filter(running), (app) => call("coolify_stop_application_by_uuid", { uuid: app.uuid })));
|
|
184
|
+
register("coolify_redeploy_project_applications", "Redeploy every application in a project.", z.object({ project_uuid: z.string(), force: z.boolean().optional() }), { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ project_uuid, force }) => batch(await projectApplications(String(project_uuid)), (app) => call("coolify_deploy_by_tag_or_uuid", { uuid: app.uuid, force: force ?? true })));
|
|
181
185
|
register("coolify_get_environment", "Get an environment and its relevant database types.", z.object({ project_uuid: z.string(), environment_name_or_uuid: z.string() }), { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ project_uuid, environment_name_or_uuid }) => {
|
|
182
186
|
const settled = await Promise.allSettled([call("coolify_get_environment_by_name_or_uuid", { uuid: project_uuid, environment_name_or_uuid }), call("coolify_list_databases")]);
|
|
183
187
|
const errors = [];
|
|
@@ -185,9 +189,9 @@ export function registerV36Capabilities(server, client) {
|
|
|
185
189
|
const databases = settled[1].status === "fulfilled" ? rows(settled[1].value) : (errors.push(`databases: ${errorMessage(settled[1].reason)}`), []);
|
|
186
190
|
if (!environment)
|
|
187
191
|
return { environment: null, missing_database_types: [...databaseTypes], ...(errors.length ? { errors } : {}) };
|
|
188
|
-
const matching = databases.filter((database) =>
|
|
192
|
+
const matching = databases.filter((database) => environment.id !== undefined && database.environment_id === environment.id);
|
|
189
193
|
const byType = new Map();
|
|
190
|
-
matching.forEach((database) => { const type = String(database.database_type ??
|
|
194
|
+
matching.forEach((database) => { const type = String(database.database_type ?? "").toLowerCase(); for (const expected of databaseTypes)
|
|
191
195
|
if (type.includes(expected))
|
|
192
196
|
byType.set(expected, [...(byType.get(expected) ?? []), database]); });
|
|
193
197
|
const missing = databaseTypes.filter((type) => !byType.has(type));
|
package/dist/client.js
CHANGED
|
@@ -30,21 +30,6 @@ function base64(bytes) {
|
|
|
30
30
|
text += String.fromCharCode(byte);
|
|
31
31
|
return btoa(text);
|
|
32
32
|
}
|
|
33
|
-
function normalizeKnownCollection(path, value) {
|
|
34
|
-
const collectionPaths = new Set(["/databases", "/resources", "/deployments", "/deployments/applications/{uuid}"]);
|
|
35
|
-
if (!collectionPaths.has(path))
|
|
36
|
-
return value;
|
|
37
|
-
if (Array.isArray(value))
|
|
38
|
-
return value;
|
|
39
|
-
if (!value || typeof value !== "object")
|
|
40
|
-
throw new Error(`Coolify collection response for ${path} must be an array or named array wrapper`);
|
|
41
|
-
for (const key of ["data", "items", "results", "databases", "resources", "deployments"]) {
|
|
42
|
-
const nested = value[key];
|
|
43
|
-
if (Array.isArray(nested))
|
|
44
|
-
return nested;
|
|
45
|
-
}
|
|
46
|
-
throw new Error(`Coolify collection response for ${path} must be an array or named array wrapper`);
|
|
47
|
-
}
|
|
48
33
|
export class CoolifyClient {
|
|
49
34
|
config;
|
|
50
35
|
fetchImpl;
|
|
@@ -55,10 +40,10 @@ export class CoolifyClient {
|
|
|
55
40
|
async request(operation, input) {
|
|
56
41
|
const baseUrl = this.config.baseUrl;
|
|
57
42
|
if (!baseUrl)
|
|
58
|
-
throw new CoolifyApiError(0, operation.method, operation.path, "
|
|
43
|
+
throw new CoolifyApiError(0, operation.method, operation.path, "COOLIFY_BASE_URL is required");
|
|
59
44
|
const token = this.config.token;
|
|
60
45
|
if (!token)
|
|
61
|
-
throw new CoolifyApiError(0, operation.method, operation.path, "
|
|
46
|
+
throw new CoolifyApiError(0, operation.method, operation.path, "COOLIFY_ACCESS_TOKEN is required");
|
|
62
47
|
let path = operation.path;
|
|
63
48
|
for (const parameter of operation.parameters)
|
|
64
49
|
if (parameter.location === "path") {
|
|
@@ -89,7 +74,7 @@ export class CoolifyClient {
|
|
|
89
74
|
if (response.status !== 204) {
|
|
90
75
|
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
91
76
|
if (contentType.includes("json"))
|
|
92
|
-
data =
|
|
77
|
+
data = await response.json();
|
|
93
78
|
else if (contentType.startsWith("text/") || contentType.includes("xml"))
|
|
94
79
|
data = await response.text();
|
|
95
80
|
else
|
package/dist/config.js
CHANGED
|
@@ -5,8 +5,8 @@ export class ConfigError extends Error {
|
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
export function loadConfig(env = process.env) {
|
|
8
|
-
const rawUrl = env.
|
|
9
|
-
const token = env.
|
|
8
|
+
const rawUrl = env.COOLIFY_BASE_URL?.trim();
|
|
9
|
+
const token = env.COOLIFY_ACCESS_TOKEN?.trim();
|
|
10
10
|
if (!rawUrl)
|
|
11
11
|
return { ...(token ? { token } : {}), timeoutMs: 30_000 };
|
|
12
12
|
let url;
|
|
@@ -14,13 +14,13 @@ export function loadConfig(env = process.env) {
|
|
|
14
14
|
url = new URL(rawUrl);
|
|
15
15
|
}
|
|
16
16
|
catch {
|
|
17
|
-
throw new ConfigError("
|
|
17
|
+
throw new ConfigError("COOLIFY_BASE_URL must be a valid HTTP(S) URL");
|
|
18
18
|
}
|
|
19
19
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
20
|
-
throw new ConfigError("
|
|
20
|
+
throw new ConfigError("COOLIFY_BASE_URL must use http:// or https://");
|
|
21
21
|
}
|
|
22
22
|
if (url.search || url.hash)
|
|
23
|
-
throw new ConfigError("
|
|
23
|
+
throw new ConfigError("COOLIFY_BASE_URL must not include a query or fragment");
|
|
24
24
|
url.pathname = url.pathname.replace(/\/+$/u, "");
|
|
25
25
|
if (!url.pathname.endsWith("/api/v1"))
|
|
26
26
|
url.pathname = `${url.pathname}/api/v1`.replace(/^\/\//u, "/");
|