@mks2508/coolify-mks-cli-mcp 0.8.0 → 0.9.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/dist/cli/coolify-state.d.ts +12 -4
- package/dist/cli/coolify-state.d.ts.map +1 -1
- package/dist/cli/index.js +8886 -7957
- package/dist/coolify/config.d.ts +25 -0
- package/dist/coolify/config.d.ts.map +1 -1
- package/dist/coolify/index.d.ts +118 -10
- package/dist/coolify/index.d.ts.map +1 -1
- package/dist/coolify/types.d.ts +61 -1
- package/dist/coolify/types.d.ts.map +1 -1
- package/dist/index.cjs +2267 -227
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2289 -227
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +56 -8
- package/dist/sdk.d.ts.map +1 -1
- package/dist/server/stdio.js +253 -100
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/utils/env-parser.d.ts +24 -0
- package/dist/utils/env-parser.d.ts.map +1 -0
- package/dist/utils/format.d.ts +32 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/cli/commands/create.ts +279 -37
- package/src/cli/commands/env.ts +348 -54
- package/src/cli/commands/init.ts +69 -15
- package/src/cli/commands/main-menu.ts +1 -1
- package/src/cli/commands/projects.ts +3 -3
- package/src/cli/commands/show.ts +39 -10
- package/src/cli/commands/status.ts +23 -7
- package/src/cli/commands/svc.ts +7 -1
- package/src/cli/commands/update.ts +52 -0
- package/src/cli/commands/volumes.ts +293 -0
- package/src/cli/coolify-state.ts +42 -4
- package/src/cli/index.ts +50 -4
- package/src/cli/ui/banner.ts +3 -3
- package/src/cli/ui/screen.ts +26 -2
- package/src/coolify/config.ts +75 -0
- package/src/coolify/index.ts +325 -106
- package/src/coolify/types.ts +62 -1
- package/src/sdk.ts +87 -39
- package/src/tools/definitions.ts +22 -0
- package/src/tools/handlers.ts +19 -0
- package/src/utils/env-parser.ts +45 -0
- package/src/utils/format.ts +178 -0
package/src/coolify/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type ICoolifyDeployResult,
|
|
22
22
|
type ICoolifyDestination,
|
|
23
23
|
type ICoolifyEnvironment,
|
|
24
|
+
type ICoolifyGithubApp,
|
|
24
25
|
type ICoolifyLogs,
|
|
25
26
|
type ICoolifyLogsOptions,
|
|
26
27
|
type ICoolifyPrivateKey,
|
|
@@ -184,7 +185,12 @@ export class CoolifyService {
|
|
|
184
185
|
|
|
185
186
|
try {
|
|
186
187
|
data = text ? JSON.parse(text) : undefined;
|
|
187
|
-
} catch {
|
|
188
|
+
} catch (parseErr) {
|
|
189
|
+
const preview = text ? text.slice(0, 200) : "(empty body)";
|
|
190
|
+
const method = options.method ?? "GET";
|
|
191
|
+
log.warn(
|
|
192
|
+
`Failed to parse JSON response from ${method} ${endpoint} (status ${response.status}): ${parseErr instanceof Error ? parseErr.message : String(parseErr)}. Body preview: ${preview}`,
|
|
193
|
+
);
|
|
188
194
|
if (!response.ok) {
|
|
189
195
|
return {
|
|
190
196
|
error: text || `HTTP ${response.status}`,
|
|
@@ -192,6 +198,13 @@ export class CoolifyService {
|
|
|
192
198
|
durationMs,
|
|
193
199
|
};
|
|
194
200
|
}
|
|
201
|
+
// OK status but body is not JSON — synthesize an error so the caller's error path triggers.
|
|
202
|
+
// Avoids silently returning undefined data which causes cryptic downstream crashes.
|
|
203
|
+
return {
|
|
204
|
+
error: `Response was not valid JSON (status ${response.status}): ${preview}`,
|
|
205
|
+
status: response.status,
|
|
206
|
+
durationMs,
|
|
207
|
+
};
|
|
195
208
|
}
|
|
196
209
|
|
|
197
210
|
if (!response.ok) {
|
|
@@ -320,8 +333,8 @@ export class CoolifyService {
|
|
|
320
333
|
"private-github-app": "/applications/private-github-app",
|
|
321
334
|
"private-deploy-key": "/applications/private-deploy-key",
|
|
322
335
|
dockerfile: "/applications/dockerfile",
|
|
323
|
-
"docker-image": "/applications/
|
|
324
|
-
"docker-compose": "/applications/
|
|
336
|
+
"docker-image": "/applications/dockerimage",
|
|
337
|
+
"docker-compose": "/applications/dockercompose",
|
|
325
338
|
dockerimage: "/applications/dockerimage",
|
|
326
339
|
dockercompose: "/applications/dockercompose",
|
|
327
340
|
};
|
|
@@ -334,15 +347,14 @@ export class CoolifyService {
|
|
|
334
347
|
description: options.description,
|
|
335
348
|
project_uuid: options.projectUuid,
|
|
336
349
|
environment_uuid: options.environmentUuid,
|
|
350
|
+
environment_name: options.environmentName,
|
|
337
351
|
server_uuid: options.serverUuid,
|
|
338
352
|
};
|
|
339
353
|
|
|
340
|
-
// Git repository — applies to all types that have a repo URL
|
|
354
|
+
// Git repository — applies to all types that have a repo URL.
|
|
355
|
+
// Coolify's API expects the full URL (https://, http://, git://, or git@).
|
|
341
356
|
if (options.githubRepoUrl) {
|
|
342
|
-
|
|
343
|
-
body.git_repository = options.githubRepoUrl
|
|
344
|
-
.replace(/^https?:\/\/github\.com\//, "")
|
|
345
|
-
.replace(/\.git$/, "");
|
|
357
|
+
body.git_repository = options.githubRepoUrl;
|
|
346
358
|
}
|
|
347
359
|
|
|
348
360
|
// Type-specific fields
|
|
@@ -354,25 +366,34 @@ export class CoolifyService {
|
|
|
354
366
|
if (options.githubAppUuid) {
|
|
355
367
|
body.github_app_uuid = options.githubAppUuid;
|
|
356
368
|
}
|
|
369
|
+
// private_key_uuid is required for private-deploy-key type
|
|
370
|
+
if (appType === "private-deploy-key" && options.privateKeyUuid) {
|
|
371
|
+
body.private_key_uuid = options.privateKeyUuid;
|
|
372
|
+
}
|
|
357
373
|
body.git_branch = options.branch || "main";
|
|
358
374
|
body.build_pack = options.buildPack || "dockerfile";
|
|
359
375
|
if (options.portsExposes) {
|
|
360
376
|
body.ports_exposes = options.portsExposes;
|
|
361
377
|
}
|
|
362
378
|
// Dockerfile / Docker Compose configuration
|
|
379
|
+
// Coolify validates these paths against /^\/.*$/ regex — prepend leading slash
|
|
380
|
+
// if caller passed a relative path (CLI --help suggests "apps/x/Dockerfile" without /)
|
|
363
381
|
if (options.dockerfileLocation) {
|
|
364
|
-
|
|
382
|
+
const p = options.dockerfileLocation;
|
|
383
|
+
body.dockerfile_location = p.startsWith("/") ? p : `/${p}`;
|
|
365
384
|
}
|
|
366
385
|
if (options.dockerComposeLocation) {
|
|
367
|
-
|
|
386
|
+
const p = options.dockerComposeLocation;
|
|
387
|
+
body.docker_compose_location = p.startsWith("/") ? p : `/${p}`;
|
|
368
388
|
}
|
|
369
389
|
if (options.baseDirectory) {
|
|
370
|
-
|
|
390
|
+
const p = options.baseDirectory;
|
|
391
|
+
body.base_directory = p.startsWith("/") ? p : `/${p}`;
|
|
371
392
|
}
|
|
372
393
|
} else if (appType === "docker-image" && options.dockerImage) {
|
|
373
|
-
body.
|
|
394
|
+
body.docker_registry_image_name = options.dockerImage;
|
|
374
395
|
} else if (appType === "docker-compose" && options.dockerCompose) {
|
|
375
|
-
body.
|
|
396
|
+
body.docker_compose_raw = options.dockerCompose;
|
|
376
397
|
}
|
|
377
398
|
|
|
378
399
|
log.debug(`Create application body: ${JSON.stringify(body, null, 2)}`);
|
|
@@ -400,6 +421,15 @@ export class CoolifyService {
|
|
|
400
421
|
/**
|
|
401
422
|
* Sets environment variables for an application.
|
|
402
423
|
*
|
|
424
|
+
* Delegates to {@link bulkUpdateEnvironmentVariables} so the same
|
|
425
|
+
* create-or-update logic applies. This fixes the post-delete scenario
|
|
426
|
+
* (where the variable was deleted via the API and only its base value
|
|
427
|
+
* from docker-compose/git remains visible) and prevents duplicates
|
|
428
|
+
* that the singular POST endpoint would create when called for a key
|
|
429
|
+
* that already exists. Also eliminates the N+1 API calls and the
|
|
430
|
+
* TOCTOU race between the DELETE and the re-POST that the previous
|
|
431
|
+
* per-key POST -> DELETE -> re-POST dance had.
|
|
432
|
+
*
|
|
403
433
|
* @param appUuid - Application UUID
|
|
404
434
|
* @param envVars - Environment variables to set
|
|
405
435
|
* @returns Result indicating success or error
|
|
@@ -412,43 +442,19 @@ export class CoolifyService {
|
|
|
412
442
|
`Setting ${Object.keys(envVars).length} environment variables for ${appUuid}`,
|
|
413
443
|
);
|
|
414
444
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
445
|
+
const vars = Object.entries(envVars).map(([key, value]) => ({
|
|
446
|
+
key,
|
|
447
|
+
value,
|
|
448
|
+
is_buildtime: false,
|
|
449
|
+
is_runtime: true,
|
|
450
|
+
}));
|
|
421
451
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
Array<{ uuid: string; key: string }>
|
|
426
|
-
>(`/applications/${appUuid}/envs`);
|
|
427
|
-
const existing = listResult.data?.find((v) => v.key === key);
|
|
428
|
-
if (existing) {
|
|
429
|
-
await this.request(`/applications/${appUuid}/envs/${existing.uuid}`, {
|
|
430
|
-
method: "DELETE",
|
|
431
|
-
});
|
|
432
|
-
const repost = await this.request(`/applications/${appUuid}/envs`, {
|
|
433
|
-
method: "POST",
|
|
434
|
-
body: JSON.stringify({ key, value, is_preview: false, is_buildtime: false }),
|
|
435
|
-
});
|
|
436
|
-
if (repost.error) {
|
|
437
|
-
log.error(`Failed to update env var ${key}: ${repost.error}`);
|
|
438
|
-
return err(new Error(`Failed to update ${key}: ${repost.error}`));
|
|
439
|
-
}
|
|
440
|
-
log.debug(`Updated existing env var: ${key}`);
|
|
441
|
-
} else {
|
|
442
|
-
log.error(`Failed to set env var ${key}: ${result.error}`);
|
|
443
|
-
return err(new Error(`Failed to set ${key}: ${result.error}`));
|
|
444
|
-
}
|
|
445
|
-
} else if (result.error) {
|
|
446
|
-
log.error(`Failed to set env var ${key}: ${result.error}`);
|
|
447
|
-
return err(new Error(`Failed to set ${key}: ${result.error}`));
|
|
448
|
-
}
|
|
452
|
+
const result = await this.bulkUpdateEnvironmentVariables(appUuid, vars);
|
|
453
|
+
if (isErr(result)) {
|
|
454
|
+
return err(result.error);
|
|
449
455
|
}
|
|
450
456
|
|
|
451
|
-
log.success(`${
|
|
457
|
+
log.success(`${vars.length} environment variables set`);
|
|
452
458
|
return ok(undefined);
|
|
453
459
|
}
|
|
454
460
|
|
|
@@ -478,12 +484,20 @@ export class CoolifyService {
|
|
|
478
484
|
|
|
479
485
|
/**
|
|
480
486
|
* Sets a single environment variable for an application.
|
|
481
|
-
*
|
|
487
|
+
*
|
|
488
|
+
* Delegates to {@link bulkUpdateEnvironmentVariables} so the same
|
|
489
|
+
* create-or-update logic applies. This fixes the post-delete scenario
|
|
490
|
+
* (where the variable was deleted via the API and only its base value
|
|
491
|
+
* from docker-compose/git remains visible) and prevents duplicates
|
|
492
|
+
* that the singular PATCH endpoint would create when called without
|
|
493
|
+
* the variable's UUID.
|
|
482
494
|
*
|
|
483
495
|
* @param appUuid - Application UUID
|
|
484
496
|
* @param key - Variable name
|
|
485
497
|
* @param value - Variable value
|
|
486
|
-
* @param isBuildTime - Whether the variable is available at build time
|
|
498
|
+
* @param isBuildTime - Whether the variable is available at build time
|
|
499
|
+
* (only for new vars; existing runtime-only vars will be flipped to
|
|
500
|
+
* build-time and vice versa).
|
|
487
501
|
* @returns Result indicating success or error
|
|
488
502
|
*/
|
|
489
503
|
async setEnvironmentVariable(
|
|
@@ -494,52 +508,17 @@ export class CoolifyService {
|
|
|
494
508
|
): Promise<Result<void, Error>> {
|
|
495
509
|
log.info(`Setting environment variable ${key} for ${appUuid} (buildtime: ${isBuildTime})`);
|
|
496
510
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (exists) {
|
|
506
|
-
// Use PATCH to update existing variable
|
|
507
|
-
log.debug(`Variable ${key} exists, using PATCH to update`);
|
|
508
|
-
const result = await this.request<{ uuid: string }>(
|
|
509
|
-
`/applications/${appUuid}/envs`,
|
|
510
|
-
{
|
|
511
|
-
method: "PATCH",
|
|
512
|
-
body: JSON.stringify({
|
|
513
|
-
key,
|
|
514
|
-
value,
|
|
515
|
-
is_buildtime: isBuildTime,
|
|
516
|
-
}),
|
|
517
|
-
},
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
if (result.error) {
|
|
521
|
-
log.error(`Failed to update env var: ${result.error}`);
|
|
522
|
-
return err(new Error(result.error));
|
|
523
|
-
}
|
|
524
|
-
} else {
|
|
525
|
-
// Use POST to create new variable
|
|
526
|
-
log.debug(`Variable ${key} does not exist, using POST to create`);
|
|
527
|
-
const result = await this.request<{ uuid: string }>(
|
|
528
|
-
`/applications/${appUuid}/envs`,
|
|
529
|
-
{
|
|
530
|
-
method: "POST",
|
|
531
|
-
body: JSON.stringify({
|
|
532
|
-
key,
|
|
533
|
-
value,
|
|
534
|
-
is_buildtime: isBuildTime,
|
|
535
|
-
}),
|
|
536
|
-
},
|
|
537
|
-
);
|
|
511
|
+
const result = await this.bulkUpdateEnvironmentVariables(appUuid, [
|
|
512
|
+
{
|
|
513
|
+
key,
|
|
514
|
+
value,
|
|
515
|
+
is_buildtime: isBuildTime,
|
|
516
|
+
is_runtime: !isBuildTime,
|
|
517
|
+
},
|
|
518
|
+
]);
|
|
538
519
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
return err(new Error(result.error));
|
|
542
|
-
}
|
|
520
|
+
if (isErr(result)) {
|
|
521
|
+
return err(result.error);
|
|
543
522
|
}
|
|
544
523
|
|
|
545
524
|
log.success(`Environment variable ${key} set for ${appUuid}`);
|
|
@@ -663,12 +642,7 @@ export class CoolifyService {
|
|
|
663
642
|
async listGithubApps(
|
|
664
643
|
page?: number,
|
|
665
644
|
perPage?: number,
|
|
666
|
-
): Promise<
|
|
667
|
-
Result<
|
|
668
|
-
Array<{ id: number; uuid: string; name: string; is_public: boolean }>,
|
|
669
|
-
Error
|
|
670
|
-
>
|
|
671
|
-
> {
|
|
645
|
+
): Promise<Result<ICoolifyGithubApp[], Error>> {
|
|
672
646
|
let endpoint = "/github-apps";
|
|
673
647
|
const params = new URLSearchParams();
|
|
674
648
|
if (page) params.set("page", page.toString());
|
|
@@ -677,14 +651,40 @@ export class CoolifyService {
|
|
|
677
651
|
endpoint += `?${params.toString()}`;
|
|
678
652
|
}
|
|
679
653
|
|
|
680
|
-
const result =
|
|
681
|
-
await this.request<
|
|
682
|
-
Array<{ id: number; uuid: string; name: string; is_public: boolean }>
|
|
683
|
-
>(endpoint);
|
|
654
|
+
const result = await this.request<ICoolifyGithubApp[]>(endpoint);
|
|
684
655
|
if (result.error) return err(new Error(result.error));
|
|
685
656
|
return ok(result.data || []);
|
|
686
657
|
}
|
|
687
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Lists all GitHub Apps configured in Coolify.
|
|
661
|
+
* Fetches all pages internally and returns a flat list.
|
|
662
|
+
*
|
|
663
|
+
* @param perPage - Items per page for each request (default: 50)
|
|
664
|
+
* @returns Result with all GitHub Apps or error
|
|
665
|
+
*/
|
|
666
|
+
async listGithubAppsAll(
|
|
667
|
+
perPage = 50,
|
|
668
|
+
): Promise<Result<ICoolifyGithubApp[], Error>> {
|
|
669
|
+
const allApps: ICoolifyGithubApp[] = [];
|
|
670
|
+
let page = 1;
|
|
671
|
+
|
|
672
|
+
while (true) {
|
|
673
|
+
const result = await this.listGithubApps(page, perPage);
|
|
674
|
+
if (isErr(result)) return err(result.error);
|
|
675
|
+
const apps = result.value;
|
|
676
|
+
|
|
677
|
+
if (apps.length === 0) break;
|
|
678
|
+
allApps.push(...apps);
|
|
679
|
+
|
|
680
|
+
// If we got fewer than perPage, we've reached the last page
|
|
681
|
+
if (apps.length < perPage) break;
|
|
682
|
+
page++;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return ok(allApps);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
688
|
/**
|
|
689
689
|
* Lists all projects.
|
|
690
690
|
*
|
|
@@ -920,10 +920,14 @@ export class CoolifyService {
|
|
|
920
920
|
if (options.domains) body.domains = options.domains;
|
|
921
921
|
if (options.dockerComposeDomains)
|
|
922
922
|
body.docker_compose_domains = options.dockerComposeDomains;
|
|
923
|
+
if (options.dockerComposeRaw !== undefined)
|
|
924
|
+
body.docker_compose_raw = options.dockerComposeRaw;
|
|
923
925
|
if (options.isForceHttpsEnabled !== undefined)
|
|
924
926
|
body.is_force_https_enabled = options.isForceHttpsEnabled;
|
|
925
927
|
if (options.isAutoDeployEnabled !== undefined)
|
|
926
928
|
body.is_auto_deploy_enabled = options.isAutoDeployEnabled;
|
|
929
|
+
if (options.watchPaths !== undefined)
|
|
930
|
+
body.watch_paths = options.watchPaths;
|
|
927
931
|
if (options.healthCheckEnabled !== undefined)
|
|
928
932
|
body.health_check_enabled = options.healthCheckEnabled;
|
|
929
933
|
if (options.healthCheckPath)
|
|
@@ -1036,8 +1040,15 @@ export class CoolifyService {
|
|
|
1036
1040
|
/**
|
|
1037
1041
|
* Bulk updates environment variables for an application.
|
|
1038
1042
|
*
|
|
1043
|
+
* Uses the bulk endpoint `PATCH /applications/{appUuid}/envs/bulk` which
|
|
1044
|
+
* has create-or-update semantics: if the variable exists in the override
|
|
1045
|
+
* table, its value is updated; otherwise a new override is created. This
|
|
1046
|
+
* is the only reliable way to update a variable that has been deleted
|
|
1047
|
+
* (i.e. its base value from docker-compose/git is still visible in
|
|
1048
|
+
* `getEnvironmentVariables` but no override row exists).
|
|
1049
|
+
*
|
|
1039
1050
|
* @param appUuid - Application UUID
|
|
1040
|
-
* @param envVars - Array of
|
|
1051
|
+
* @param envVars - Array of variable definitions to upsert
|
|
1041
1052
|
* @returns Result indicating success or error
|
|
1042
1053
|
*/
|
|
1043
1054
|
async bulkUpdateEnvironmentVariables(
|
|
@@ -1046,15 +1057,19 @@ export class CoolifyService {
|
|
|
1046
1057
|
key: string;
|
|
1047
1058
|
value: string;
|
|
1048
1059
|
is_preview?: boolean;
|
|
1060
|
+
is_buildtime?: boolean;
|
|
1061
|
+
is_runtime?: boolean;
|
|
1049
1062
|
}>,
|
|
1050
1063
|
): Promise<Result<{ message: string }, Error>> {
|
|
1051
1064
|
log.info(`Bulk updating ${envVars.length} env vars for ${appUuid}`);
|
|
1052
1065
|
|
|
1066
|
+
// Coolify bulk endpoint requires { data: [...] } envelope, not a raw array.
|
|
1067
|
+
// Sending a raw array yields 400 "Bulk data is required." from the API.
|
|
1053
1068
|
const result = await this.request<{ message: string }>(
|
|
1054
1069
|
`/applications/${appUuid}/envs/bulk`,
|
|
1055
1070
|
{
|
|
1056
1071
|
method: "PATCH",
|
|
1057
|
-
body: JSON.stringify(envVars),
|
|
1072
|
+
body: JSON.stringify({ data: envVars }),
|
|
1058
1073
|
},
|
|
1059
1074
|
);
|
|
1060
1075
|
|
|
@@ -1067,6 +1082,50 @@ export class CoolifyService {
|
|
|
1067
1082
|
return ok(result.data || { message: "Environment variables updated" });
|
|
1068
1083
|
}
|
|
1069
1084
|
|
|
1085
|
+
/**
|
|
1086
|
+
* Bulk updates environment variables for a database.
|
|
1087
|
+
*
|
|
1088
|
+
* Uses the bulk endpoint `PATCH /databases/{databaseUuid}/envs/bulk` which
|
|
1089
|
+
* has create-or-update semantics. Note: the database schema is narrower
|
|
1090
|
+
* than applications — it accepts `key`, `value`, `is_literal`,
|
|
1091
|
+
* `is_multiline`, `is_shown_once` but does NOT accept `is_preview`,
|
|
1092
|
+
* `is_buildtime` or `is_runtime` (those flags are application-only).
|
|
1093
|
+
*
|
|
1094
|
+
* @param databaseUuid - Database UUID
|
|
1095
|
+
* @param envVars - Array of variable definitions to upsert
|
|
1096
|
+
* @returns Result indicating success or error
|
|
1097
|
+
*/
|
|
1098
|
+
async bulkUpdateDatabaseEnvVars(
|
|
1099
|
+
databaseUuid: string,
|
|
1100
|
+
envVars: Array<{
|
|
1101
|
+
key: string;
|
|
1102
|
+
value: string;
|
|
1103
|
+
is_literal?: boolean;
|
|
1104
|
+
is_multiline?: boolean;
|
|
1105
|
+
is_shown_once?: boolean;
|
|
1106
|
+
}>,
|
|
1107
|
+
): Promise<Result<{ message: string }, Error>> {
|
|
1108
|
+
log.info(`Bulk updating ${envVars.length} env vars for database ${databaseUuid}`);
|
|
1109
|
+
|
|
1110
|
+
// Coolify bulk endpoint requires { data: [...] } envelope, not a raw array.
|
|
1111
|
+
// Sending a raw array yields 400 "Bulk data is required." from the API.
|
|
1112
|
+
const result = await this.request<{ message: string }>(
|
|
1113
|
+
`/databases/${databaseUuid}/envs/bulk`,
|
|
1114
|
+
{
|
|
1115
|
+
method: "PATCH",
|
|
1116
|
+
body: JSON.stringify({ data: envVars }),
|
|
1117
|
+
},
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
if (result.error) {
|
|
1121
|
+
log.error(`Failed to bulk update database env vars: ${result.error}`);
|
|
1122
|
+
return err(new Error(result.error));
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
log.success(`Bulk updated ${envVars.length} env vars for database ${databaseUuid}`);
|
|
1126
|
+
return ok(result.data || { message: "Environment variables updated" });
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1070
1129
|
/**
|
|
1071
1130
|
* Gets deployment history for an application.
|
|
1072
1131
|
*
|
|
@@ -1913,9 +1972,65 @@ export class CoolifyService {
|
|
|
1913
1972
|
return ok(result.data || []);
|
|
1914
1973
|
}
|
|
1915
1974
|
|
|
1975
|
+
/**
|
|
1976
|
+
* Bulk updates environment variables for a service.
|
|
1977
|
+
*
|
|
1978
|
+
* Uses the bulk endpoint `PATCH /services/{uuid}/envs/bulk` which has
|
|
1979
|
+
* create-or-update semantics: if the variable exists in the override
|
|
1980
|
+
* table, its value is updated; otherwise a new override is created.
|
|
1981
|
+
*
|
|
1982
|
+
* This is the only reliable way to update a variable that has been
|
|
1983
|
+
* deleted (i.e. its base value from docker-compose/git is still visible
|
|
1984
|
+
* in `listServiceEnvVars` but no override row exists), and the only way
|
|
1985
|
+
* to set the same key twice without the `POST /services/{uuid}/envs`
|
|
1986
|
+
* returning 409 "already exists".
|
|
1987
|
+
*
|
|
1988
|
+
* @param serviceUuid - Service UUID
|
|
1989
|
+
* @param envVars - Array of variable definitions to upsert
|
|
1990
|
+
* @returns Result indicating success or error
|
|
1991
|
+
*/
|
|
1992
|
+
async bulkUpdateServiceEnvVars(
|
|
1993
|
+
serviceUuid: string,
|
|
1994
|
+
envVars: Array<{
|
|
1995
|
+
key: string;
|
|
1996
|
+
value: string;
|
|
1997
|
+
is_preview?: boolean;
|
|
1998
|
+
is_buildtime?: boolean;
|
|
1999
|
+
is_runtime?: boolean;
|
|
2000
|
+
}>,
|
|
2001
|
+
): Promise<Result<{ message: string }, Error>> {
|
|
2002
|
+
log.info(`Bulk updating ${envVars.length} env vars for service ${serviceUuid}`);
|
|
2003
|
+
|
|
2004
|
+
// Coolify bulk endpoint requires { data: [...] } envelope, not a raw array.
|
|
2005
|
+
// Sending a raw array yields 400 "Bulk data is required." from the API.
|
|
2006
|
+
const result = await this.request<{ message: string }>(
|
|
2007
|
+
`/services/${serviceUuid}/envs/bulk`,
|
|
2008
|
+
{
|
|
2009
|
+
method: "PATCH",
|
|
2010
|
+
body: JSON.stringify({ data: envVars }),
|
|
2011
|
+
},
|
|
2012
|
+
);
|
|
2013
|
+
|
|
2014
|
+
if (result.error) {
|
|
2015
|
+
log.error(`Failed to bulk update service env vars: ${result.error}`);
|
|
2016
|
+
return err(new Error(result.error));
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
log.success(`Bulk updated ${envVars.length} env vars for service ${serviceUuid}`);
|
|
2020
|
+
return ok(result.data || { message: "Environment variables updated" });
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1916
2023
|
/**
|
|
1917
2024
|
* Creates an environment variable for a service.
|
|
1918
2025
|
*
|
|
2026
|
+
* NOTE: Prefer `bulkUpdateServiceEnvVars` for any non-trivial use case.
|
|
2027
|
+
* This endpoint uses `POST /services/{uuid}/envs` and returns 409
|
|
2028
|
+
* "already exists" when the key is already present as an override,
|
|
2029
|
+
* with no recovery path on the server side. Bulk has create-or-update
|
|
2030
|
+
* semantics and handles both new and existing keys reliably.
|
|
2031
|
+
*
|
|
2032
|
+
* Kept for callers that explicitly want a strict create-only operation.
|
|
2033
|
+
*
|
|
1919
2034
|
* @param uuid - Service UUID
|
|
1920
2035
|
* @param data - Env var data (key, value, is_preview)
|
|
1921
2036
|
* @returns Result with created env var UUID or error
|
|
@@ -1933,6 +2048,110 @@ export class CoolifyService {
|
|
|
1933
2048
|
return ok(result.data!);
|
|
1934
2049
|
}
|
|
1935
2050
|
|
|
2051
|
+
/**
|
|
2052
|
+
* Deletes an environment variable from a service.
|
|
2053
|
+
*
|
|
2054
|
+
* @param serviceUuid - Service UUID
|
|
2055
|
+
* @param key - Variable name to delete
|
|
2056
|
+
* @returns Result indicating success or error
|
|
2057
|
+
*/
|
|
2058
|
+
async deleteServiceEnvVar(
|
|
2059
|
+
serviceUuid: string,
|
|
2060
|
+
key: string,
|
|
2061
|
+
): Promise<Result<void, Error>> {
|
|
2062
|
+
log.info(`Deleting environment variable ${key} from service ${serviceUuid}`);
|
|
2063
|
+
|
|
2064
|
+
// First get all env vars to find the UUID of the one to delete
|
|
2065
|
+
const envVarsResult = await this.listServiceEnvVars(serviceUuid);
|
|
2066
|
+
if (isErr(envVarsResult)) {
|
|
2067
|
+
return err(envVarsResult.error);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
const envVar = envVarsResult.value.find((ev) => ev.key === key);
|
|
2071
|
+
if (!envVar) {
|
|
2072
|
+
log.error(`Environment variable ${key} not found on service ${serviceUuid}`);
|
|
2073
|
+
return err(new Error(`Environment variable ${key} not found`));
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
const result = await this.request(
|
|
2077
|
+
`/services/${serviceUuid}/envs/${envVar.uuid}`,
|
|
2078
|
+
{
|
|
2079
|
+
method: "DELETE",
|
|
2080
|
+
},
|
|
2081
|
+
);
|
|
2082
|
+
|
|
2083
|
+
if (result.error) {
|
|
2084
|
+
log.error(`Failed to delete service env var: ${result.error}`);
|
|
2085
|
+
return err(new Error(result.error));
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
log.success(`Environment variable ${key} deleted from service ${serviceUuid}`);
|
|
2089
|
+
return ok(undefined);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
/**
|
|
2093
|
+
* Lists environment variables for a database.
|
|
2094
|
+
*
|
|
2095
|
+
* @param databaseUuid - Database UUID
|
|
2096
|
+
* @returns Result with env vars list or error
|
|
2097
|
+
*/
|
|
2098
|
+
async listDatabaseEnvVars(
|
|
2099
|
+
databaseUuid: string,
|
|
2100
|
+
): Promise<Result<ICoolifyEnvVar[], Error>> {
|
|
2101
|
+
log.info(`Listing env vars for database ${databaseUuid}`);
|
|
2102
|
+
|
|
2103
|
+
const result = await this.request<ICoolifyEnvVar[]>(
|
|
2104
|
+
`/databases/${databaseUuid}/envs`,
|
|
2105
|
+
);
|
|
2106
|
+
if (result.error) {
|
|
2107
|
+
log.error(`Failed to list database env vars: ${result.error}`);
|
|
2108
|
+
return err(new Error(result.error));
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
return ok(result.data || []);
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
/**
|
|
2115
|
+
* Deletes an environment variable from a database.
|
|
2116
|
+
*
|
|
2117
|
+
* @param databaseUuid - Database UUID
|
|
2118
|
+
* @param key - Variable name to delete
|
|
2119
|
+
* @returns Result indicating success or error
|
|
2120
|
+
*/
|
|
2121
|
+
async deleteDatabaseEnvVar(
|
|
2122
|
+
databaseUuid: string,
|
|
2123
|
+
key: string,
|
|
2124
|
+
): Promise<Result<void, Error>> {
|
|
2125
|
+
log.info(`Deleting environment variable ${key} from database ${databaseUuid}`);
|
|
2126
|
+
|
|
2127
|
+
// First get all env vars to find the UUID of the one to delete
|
|
2128
|
+
const envVarsResult = await this.listDatabaseEnvVars(databaseUuid);
|
|
2129
|
+
if (isErr(envVarsResult)) {
|
|
2130
|
+
return err(envVarsResult.error);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
const envVar = envVarsResult.value.find((ev) => ev.key === key);
|
|
2134
|
+
if (!envVar) {
|
|
2135
|
+
log.error(`Environment variable ${key} not found on database ${databaseUuid}`);
|
|
2136
|
+
return err(new Error(`Environment variable ${key} not found`));
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
const result = await this.request(
|
|
2140
|
+
`/databases/${databaseUuid}/envs/${envVar.uuid}`,
|
|
2141
|
+
{
|
|
2142
|
+
method: "DELETE",
|
|
2143
|
+
},
|
|
2144
|
+
);
|
|
2145
|
+
|
|
2146
|
+
if (result.error) {
|
|
2147
|
+
log.error(`Failed to delete database env var: ${result.error}`);
|
|
2148
|
+
return err(new Error(result.error));
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
log.success(`Environment variable ${key} deleted from database ${databaseUuid}`);
|
|
2152
|
+
return ok(undefined);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
1936
2155
|
// ===========================================================================
|
|
1937
2156
|
// Additional Server endpoints
|
|
1938
2157
|
// ===========================================================================
|