@flui-cloud/cli 0.0.1 → 0.1.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/lib/cli/src/commands/app/list.d.ts +3 -0
- package/lib/cli/src/commands/app/list.js +72 -18
- package/lib/cli/src/commands/app/status.d.ts +1 -0
- package/lib/cli/src/commands/app/status.js +27 -2
- package/lib/cli/src/commands/cluster/destroy.d.ts +1 -1
- package/lib/cli/src/commands/cluster/destroy.js +2 -2
- package/lib/cli/src/commands/deploy.d.ts +3 -0
- package/lib/cli/src/commands/deploy.js +19 -0
- package/lib/cli/src/commands/dev/creds.d.ts +0 -1
- package/lib/cli/src/commands/dev/creds.js +6 -27
- package/lib/cli/src/commands/dev/tunnel.js +8 -8
- package/lib/cli/src/commands/env/capacity.js +4 -4
- package/lib/cli/src/commands/env/create.d.ts +4 -1
- package/lib/cli/src/commands/env/create.js +73 -52
- package/lib/cli/src/commands/env/credentials.js +12 -12
- package/lib/cli/src/commands/env/destroy.d.ts +2 -1
- package/lib/cli/src/commands/env/destroy.js +45 -28
- package/lib/cli/src/commands/env/diag-ca.js +5 -5
- package/lib/cli/src/commands/env/export-config.d.ts +0 -17
- package/lib/cli/src/commands/env/export-config.js +45 -44
- package/lib/cli/src/commands/env/force-ready.d.ts +1 -1
- package/lib/cli/src/commands/env/force-ready.js +8 -8
- package/lib/cli/src/commands/env/inspect.js +5 -5
- package/lib/cli/src/commands/env/refresh-kubeconfig.js +4 -4
- package/lib/cli/src/commands/env/repair-ssh-ca.js +4 -4
- package/lib/cli/src/commands/env/repair-storage.d.ts +9 -0
- package/lib/cli/src/commands/env/repair-storage.js +82 -0
- package/lib/cli/src/commands/env/restart.d.ts +1 -1
- package/lib/cli/src/commands/env/restart.js +9 -9
- package/lib/cli/src/commands/env/scale-master.js +4 -4
- package/lib/cli/src/commands/env/scale-node.js +4 -4
- package/lib/cli/src/commands/env/set-master-protection.d.ts +16 -0
- package/lib/cli/src/commands/env/set-master-protection.js +120 -0
- package/lib/cli/src/commands/env/status.d.ts +1 -1
- package/lib/cli/src/commands/env/status.js +10 -10
- package/lib/cli/src/commands/env/stop.d.ts +1 -1
- package/lib/cli/src/commands/env/stop.js +8 -8
- package/lib/cli/src/commands/env/storage-expand.js +4 -4
- package/lib/cli/src/commands/env/storage.d.ts +1 -1
- package/lib/cli/src/commands/env/storage.js +5 -5
- package/lib/cli/src/commands/env/sync.js +5 -5
- package/lib/cli/src/commands/env/uncordon.js +4 -4
- package/lib/cli/src/commands/env/update-firewall.d.ts +13 -1
- package/lib/cli/src/commands/env/update-firewall.js +232 -126
- package/lib/cli/src/commands/integration/connect.d.ts +1 -0
- package/lib/cli/src/commands/integration/connect.js +19 -1
- package/lib/cli/src/commands/integration/reset.d.ts +13 -0
- package/lib/cli/src/commands/integration/reset.js +95 -0
- package/lib/cli/src/commands/integration/setup.d.ts +18 -0
- package/lib/cli/src/commands/integration/setup.js +320 -0
- package/lib/cli/src/commands/integration/status.d.ts +9 -0
- package/lib/cli/src/commands/integration/status.js +117 -0
- package/lib/cli/src/commands/node/list.d.ts +1 -0
- package/lib/cli/src/commands/node/list.js +19 -2
- package/lib/cli/src/commands/server-types/list.d.ts +3 -0
- package/lib/cli/src/commands/server-types/list.js +84 -0
- package/lib/cli/src/commands/ssh.js +5 -5
- package/lib/cli/src/commands/version.d.ts +18 -0
- package/lib/cli/src/commands/version.js +85 -0
- package/lib/cli/src/config/bootstrap.config.d.ts +10 -1
- package/lib/cli/src/config/bootstrap.config.js +21 -4
- package/lib/cli/src/config/preferences-schema.js +5 -5
- package/lib/cli/src/config/release.config.d.ts +31 -0
- package/lib/cli/src/config/release.config.js +38 -0
- package/lib/cli/src/lib/prompts.d.ts +1 -6
- package/lib/cli/src/lib/prompts.js +33 -13
- package/lib/cli/src/lib/services/cli-app.service.d.ts +33 -0
- package/lib/cli/src/lib/services/cli-app.service.js +9 -0
- package/lib/cli/src/lib/services/reconciliation.service.js +1 -1
- package/lib/cli/src/lib/templates/firewall-rules.d.ts +2 -2
- package/lib/cli/src/lib/templates/firewall-rules.js +3 -3
- package/lib/cli/src/modules/cli-infrastructure.module.js +3 -3
- package/lib/cli/src/services/cli-cluster-creator.service.js +31 -6
- package/lib/cli/src/services/cli-clusters.service.d.ts +3 -3
- package/lib/cli/src/services/cli-clusters.service.js +57 -34
- package/lib/cli/src/services/cli-control-cluster.service.d.ts +129 -0
- package/lib/cli/src/services/cli-control-cluster.service.js +544 -0
- package/lib/cli/src/services/cli-endpoint-resolver.service.d.ts +1 -0
- package/lib/cli/src/services/cli-endpoint-resolver.service.js +8 -2
- package/lib/cli/src/services/cli-k3s-script.service.d.ts +8 -1
- package/lib/cli/src/services/cli-k3s-script.service.js +14 -6
- package/lib/src/config/release.config.d.ts +28 -0
- package/lib/src/config/release.config.js +35 -0
- package/lib/src/modules/applications/entities/application.entity.d.ts +13 -20
- package/lib/src/modules/applications/entities/application.entity.js +12 -0
- package/lib/src/modules/applications/enums/application-exposure.enum.d.ts +2 -1
- package/lib/src/modules/applications/enums/application-exposure.enum.js +1 -0
- package/lib/src/modules/applications/interfaces/source-config.interface.d.ts +1 -0
- package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.d.ts +8 -2
- package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.js +16 -1
- package/lib/src/modules/infrastructure/clusters/services/cluster-node-scaling.service.js +2 -2
- package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.d.ts +3 -2
- package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.js +11 -4
- package/lib/src/modules/infrastructure/shared/services/kubernetes.service.d.ts +26 -0
- package/lib/src/modules/infrastructure/shared/services/kubernetes.service.js +105 -8
- package/lib/src/modules/management/entities/provider-capabilities.entity.d.ts +2 -0
- package/lib/src/modules/providers/implementations/contabo/contabo-capabilities.service.js +2 -0
- package/lib/src/modules/providers/implementations/hetzner/hetzner-capabilities.service.js +3 -6
- package/lib/src/modules/providers/implementations/scaleway/scaleway-capabilities.service.js +2 -1
- package/lib/src/modules/providers/implementations/scaleway/scaleway-firewall.service.js +3 -1
- package/lib/src/modules/providers/implementations/scaleway/scaleway-provider.service.js +3 -1
- package/lib/src/modules/providers/interfaces/provider-capabilities.interface.d.ts +0 -2
- package/lib/src/modules/providers/services/hetzner-firewall.service.d.ts +1 -1
- package/lib/src/modules/providers/services/hetzner-firewall.service.js +2 -1
- package/oclif.manifest.json +1025 -678
- package/package.json +2 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* `flui version` — surfaces the CLI version AND the platform release it pins.
|
|
4
|
+
*
|
|
5
|
+
* The CLI's npm version is decoupled from the platform release (carried in
|
|
6
|
+
* RELEASE), so this command makes the mapping explicit: which bootstrap ref and
|
|
7
|
+
* which component image tags a `flui env create` would install. Handy when
|
|
8
|
+
* debugging an install ("which versions did this CLI actually use?").
|
|
9
|
+
*/
|
|
10
|
+
export default class Version extends Command {
|
|
11
|
+
static readonly description = "Show the CLI version and the platform release it pins (component image tags + bootstrap ref). Useful for debugging an install.";
|
|
12
|
+
static readonly examples: string[];
|
|
13
|
+
static readonly flags: {
|
|
14
|
+
latest: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const release_config_1 = require("../../../src/config/release.config");
|
|
9
|
+
const bootstrap_config_1 = require("../config/bootstrap.config");
|
|
10
|
+
/**
|
|
11
|
+
* `flui version` — surfaces the CLI version AND the platform release it pins.
|
|
12
|
+
*
|
|
13
|
+
* The CLI's npm version is decoupled from the platform release (carried in
|
|
14
|
+
* RELEASE), so this command makes the mapping explicit: which bootstrap ref and
|
|
15
|
+
* which component image tags a `flui env create` would install. Handy when
|
|
16
|
+
* debugging an install ("which versions did this CLI actually use?").
|
|
17
|
+
*/
|
|
18
|
+
class Version extends core_1.Command {
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(Version);
|
|
21
|
+
const useLatest = flags.latest;
|
|
22
|
+
const tags = (0, release_config_1.resolveImageTags)(useLatest);
|
|
23
|
+
const bootstrapRef = (0, release_config_1.resolveBootstrapRef)(useLatest);
|
|
24
|
+
const scriptsBaseUrl = (0, bootstrap_config_1.getScriptsBaseUrl)(useLatest);
|
|
25
|
+
const urlOverride = process.env.BOOTSTRAP_SCRIPTS_URL ?? null;
|
|
26
|
+
const images = {
|
|
27
|
+
'flui-api': `ghcr.io/flui-cloud/core:${tags.fluiApi}`,
|
|
28
|
+
'flui-web': `ghcr.io/flui-cloud/dashboard:${tags.fluiWeb}`,
|
|
29
|
+
'flui-authz': `ghcr.io/flui-cloud/flui-authz:${tags.fluiAuthz}`,
|
|
30
|
+
};
|
|
31
|
+
if (flags.json) {
|
|
32
|
+
this.log(JSON.stringify({
|
|
33
|
+
cli: this.config.version,
|
|
34
|
+
mode: useLatest ? 'latest' : 'pinned',
|
|
35
|
+
platform: useLatest ? null : release_config_1.RELEASE.version,
|
|
36
|
+
bootstrapRef,
|
|
37
|
+
scriptsBaseUrl,
|
|
38
|
+
bootstrapUrlOverride: urlOverride,
|
|
39
|
+
images: tags,
|
|
40
|
+
}, null, 2));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const label = (s) => chalk_1.default.dim(s.padEnd(15));
|
|
44
|
+
this.log('');
|
|
45
|
+
this.log(chalk_1.default.bold('Flui CLI'));
|
|
46
|
+
this.log('');
|
|
47
|
+
this.log(` ${label('CLI')}${chalk_1.default.cyan(this.config.version)} ${chalk_1.default.dim(`(${this.config.name})`)}`);
|
|
48
|
+
this.log(` ${label('Platform')}${useLatest
|
|
49
|
+
? chalk_1.default.yellow('latest (mobile tags)')
|
|
50
|
+
: `${chalk_1.default.cyan(release_config_1.RELEASE.version)}${chalk_1.default.dim(' (pinned release)')}`}`);
|
|
51
|
+
this.log(` ${label('Bootstrap ref')}${chalk_1.default.cyan(bootstrapRef)}`);
|
|
52
|
+
this.log('');
|
|
53
|
+
this.log(chalk_1.default.dim(' Component images:'));
|
|
54
|
+
for (const [name, ref] of Object.entries(images)) {
|
|
55
|
+
this.log(` ${chalk_1.default.dim(name.padEnd(12))}${ref}`);
|
|
56
|
+
}
|
|
57
|
+
this.log('');
|
|
58
|
+
this.log(` ${label('Scripts URL')}${chalk_1.default.dim(scriptsBaseUrl)}`);
|
|
59
|
+
if (urlOverride) {
|
|
60
|
+
this.log(` ${label('')}${chalk_1.default.yellow('↑ overridden via BOOTSTRAP_SCRIPTS_URL')}`);
|
|
61
|
+
}
|
|
62
|
+
if (!useLatest) {
|
|
63
|
+
this.log('');
|
|
64
|
+
this.log(chalk_1.default.dim(' Tip: `flui version --latest` shows what `env create --latest` would use.'));
|
|
65
|
+
}
|
|
66
|
+
this.log('');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
Version.description = 'Show the CLI version and the platform release it pins (component image tags + bootstrap ref). Useful for debugging an install.';
|
|
70
|
+
Version.examples = [
|
|
71
|
+
'<%= config.bin %> <%= command.id %>',
|
|
72
|
+
'<%= config.bin %> <%= command.id %> --latest',
|
|
73
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
74
|
+
];
|
|
75
|
+
Version.flags = {
|
|
76
|
+
latest: core_1.Flags.boolean({
|
|
77
|
+
description: 'Show what `env create --latest` would resolve to (mobile tags: bootstrap master, :latest images) instead of the pinned release',
|
|
78
|
+
default: false,
|
|
79
|
+
}),
|
|
80
|
+
json: core_1.Flags.boolean({
|
|
81
|
+
description: 'Output as JSON',
|
|
82
|
+
default: false,
|
|
83
|
+
}),
|
|
84
|
+
};
|
|
85
|
+
exports.default = Version;
|
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
* Bootstrap Scripts Configuration
|
|
3
3
|
*
|
|
4
4
|
* Configuration for downloading initialization scripts from GitHub.
|
|
5
|
-
* Scripts are hosted in the flui-cloud/
|
|
5
|
+
* Scripts are hosted in the flui-cloud/bootstrap-scripts repository.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Base URL for the bootstrap scripts directory.
|
|
9
|
+
*
|
|
10
|
+
* Precedence:
|
|
11
|
+
* 1. `BOOTSTRAP_SCRIPTS_URL` env — full override (dev/CI escape hatch), wins over all.
|
|
12
|
+
* 2. Otherwise derived from the release pin: `<repo>/<ref>/scripts`, where the
|
|
13
|
+
* ref is the pinned release tag, or `master` when `useLatest`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getScriptsBaseUrl(useLatest?: boolean): string;
|
|
7
16
|
export interface BootstrapConfig {
|
|
8
17
|
/**
|
|
9
18
|
* Base URL for downloading scripts
|
|
@@ -3,17 +3,34 @@
|
|
|
3
3
|
* Bootstrap Scripts Configuration
|
|
4
4
|
*
|
|
5
5
|
* Configuration for downloading initialization scripts from GitHub.
|
|
6
|
-
* Scripts are hosted in the flui-cloud/
|
|
6
|
+
* Scripts are hosted in the flui-cloud/bootstrap-scripts repository.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.BOOTSTRAP_CONFIG = void 0;
|
|
10
|
+
exports.getScriptsBaseUrl = getScriptsBaseUrl;
|
|
10
11
|
exports.getScriptUrl = getScriptUrl;
|
|
12
|
+
const release_config_1 = require("../../../src/config/release.config");
|
|
13
|
+
const BOOTSTRAP_REPO_RAW_BASE = 'https://raw.githubusercontent.com/flui-cloud/bootstrap-scripts';
|
|
14
|
+
/**
|
|
15
|
+
* Base URL for the bootstrap scripts directory.
|
|
16
|
+
*
|
|
17
|
+
* Precedence:
|
|
18
|
+
* 1. `BOOTSTRAP_SCRIPTS_URL` env — full override (dev/CI escape hatch), wins over all.
|
|
19
|
+
* 2. Otherwise derived from the release pin: `<repo>/<ref>/scripts`, where the
|
|
20
|
+
* ref is the pinned release tag, or `master` when `useLatest`.
|
|
21
|
+
*/
|
|
22
|
+
function getScriptsBaseUrl(useLatest = false) {
|
|
23
|
+
if (process.env.BOOTSTRAP_SCRIPTS_URL) {
|
|
24
|
+
return process.env.BOOTSTRAP_SCRIPTS_URL;
|
|
25
|
+
}
|
|
26
|
+
return `${BOOTSTRAP_REPO_RAW_BASE}/${(0, release_config_1.resolveBootstrapRef)(useLatest)}/scripts`;
|
|
27
|
+
}
|
|
11
28
|
/**
|
|
12
29
|
* Default bootstrap configuration
|
|
13
30
|
*/
|
|
14
31
|
exports.BOOTSTRAP_CONFIG = {
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
// Pinned-release default; per-install resolution goes through getScriptsBaseUrl().
|
|
33
|
+
scriptsBaseUrl: getScriptsBaseUrl(false),
|
|
17
34
|
scripts: {
|
|
18
35
|
fluiInit: 'flui-init.sh',
|
|
19
36
|
k3sMaster: 'k3s-master-init.sh',
|
|
@@ -22,7 +39,7 @@ exports.BOOTSTRAP_CONFIG = {
|
|
|
22
39
|
repository: {
|
|
23
40
|
org: 'flui-cloud',
|
|
24
41
|
name: 'bootstrap-scripts',
|
|
25
|
-
branch:
|
|
42
|
+
branch: (0, release_config_1.resolveBootstrapRef)(false),
|
|
26
43
|
},
|
|
27
44
|
};
|
|
28
45
|
/**
|
|
@@ -16,15 +16,15 @@ exports.isPreferenceKey = isPreferenceKey;
|
|
|
16
16
|
exports.PREFERENCES = {
|
|
17
17
|
email: {
|
|
18
18
|
key: 'email',
|
|
19
|
-
description:
|
|
19
|
+
description: "Contact email used for ACME/Let's Encrypt and operational notifications",
|
|
20
20
|
envVar: 'FLUI_EMAIL',
|
|
21
21
|
projectOverridable: true,
|
|
22
22
|
required: true,
|
|
23
|
-
validate: (v) =>
|
|
23
|
+
validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? null : 'Not a valid email',
|
|
24
24
|
},
|
|
25
25
|
apiPath: {
|
|
26
26
|
key: 'apiPath',
|
|
27
|
-
description: 'Path to the flui
|
|
27
|
+
description: 'Path to the flui-core repo, used to locate the .env file written by env export-config',
|
|
28
28
|
envVar: 'FLUI_API_PATH',
|
|
29
29
|
projectOverridable: true,
|
|
30
30
|
defaultValue: '.',
|
|
@@ -32,10 +32,10 @@ exports.PREFERENCES = {
|
|
|
32
32
|
},
|
|
33
33
|
dashboardPath: {
|
|
34
34
|
key: 'dashboardPath',
|
|
35
|
-
description: 'Path to the flui
|
|
35
|
+
description: 'Path to the flui-dashboard repo, used when syncing its config.json',
|
|
36
36
|
envVar: 'FLUI_DASHBOARD_PATH',
|
|
37
37
|
projectOverridable: true,
|
|
38
|
-
defaultValue: '../flui
|
|
38
|
+
defaultValue: '../flui-dashboard',
|
|
39
39
|
required: false,
|
|
40
40
|
},
|
|
41
41
|
certificateMode: {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release manifest — single source of truth for the versions Flui pins at
|
|
3
|
+
* install time. Bumped by hand on each CLI release.
|
|
4
|
+
*
|
|
5
|
+
* As long as the installed CLI stays at this version, every component a fresh
|
|
6
|
+
* cluster receives is pinned: the bootstrap scripts/manifests (a git ref on
|
|
7
|
+
* flui-cloud/bootstrap-scripts) and the Docker image tags injected into the
|
|
8
|
+
* cluster. `flui env create --latest` opts back into the mobile dev behaviour
|
|
9
|
+
* (bootstrap ref `master`, `:latest` image tags).
|
|
10
|
+
*/
|
|
11
|
+
export interface ComponentImageTags {
|
|
12
|
+
/** ghcr.io/flui-cloud/core */
|
|
13
|
+
fluiApi: string;
|
|
14
|
+
/** ghcr.io/flui-cloud/dashboard */
|
|
15
|
+
fluiWeb: string;
|
|
16
|
+
/** ghcr.io/flui-cloud/flui-authz */
|
|
17
|
+
fluiAuthz: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ReleaseManifest {
|
|
20
|
+
/** Platform release version, recorded on the cluster at install. */
|
|
21
|
+
version: string;
|
|
22
|
+
/** Git ref (tag) on flui-cloud/bootstrap-scripts holding scripts + manifests. */
|
|
23
|
+
bootstrapRef: string;
|
|
24
|
+
/** Pinned Docker image tags, per Flui component. */
|
|
25
|
+
images: ComponentImageTags;
|
|
26
|
+
}
|
|
27
|
+
export declare const RELEASE: ReleaseManifest;
|
|
28
|
+
/** Bootstrap-scripts git ref to install from. `--latest` → `master`. */
|
|
29
|
+
export declare function resolveBootstrapRef(useLatest: boolean): string;
|
|
30
|
+
/** Docker image tags to deploy. `--latest` → all `latest`. */
|
|
31
|
+
export declare function resolveImageTags(useLatest: boolean): ComponentImageTags;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Release manifest — single source of truth for the versions Flui pins at
|
|
4
|
+
* install time. Bumped by hand on each CLI release.
|
|
5
|
+
*
|
|
6
|
+
* As long as the installed CLI stays at this version, every component a fresh
|
|
7
|
+
* cluster receives is pinned: the bootstrap scripts/manifests (a git ref on
|
|
8
|
+
* flui-cloud/bootstrap-scripts) and the Docker image tags injected into the
|
|
9
|
+
* cluster. `flui env create --latest` opts back into the mobile dev behaviour
|
|
10
|
+
* (bootstrap ref `master`, `:latest` image tags).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.RELEASE = void 0;
|
|
14
|
+
exports.resolveBootstrapRef = resolveBootstrapRef;
|
|
15
|
+
exports.resolveImageTags = resolveImageTags;
|
|
16
|
+
exports.RELEASE = {
|
|
17
|
+
version: '0.5.0',
|
|
18
|
+
bootstrapRef: 'v0.5.0',
|
|
19
|
+
images: {
|
|
20
|
+
fluiApi: '0.5.0',
|
|
21
|
+
fluiWeb: '0.5.0',
|
|
22
|
+
fluiAuthz: '0.5.0',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const LATEST_BOOTSTRAP_REF = 'master';
|
|
26
|
+
const LATEST_IMAGE_TAGS = {
|
|
27
|
+
fluiApi: 'latest',
|
|
28
|
+
fluiWeb: 'latest',
|
|
29
|
+
fluiAuthz: 'latest',
|
|
30
|
+
};
|
|
31
|
+
/** Bootstrap-scripts git ref to install from. `--latest` → `master`. */
|
|
32
|
+
function resolveBootstrapRef(useLatest) {
|
|
33
|
+
return useLatest ? LATEST_BOOTSTRAP_REF : exports.RELEASE.bootstrapRef;
|
|
34
|
+
}
|
|
35
|
+
/** Docker image tags to deploy. `--latest` → all `latest`. */
|
|
36
|
+
function resolveImageTags(useLatest) {
|
|
37
|
+
return useLatest ? { ...LATEST_IMAGE_TAGS } : { ...exports.RELEASE.images };
|
|
38
|
+
}
|
|
@@ -15,12 +15,7 @@ export declare function selectWithArrows(title: string, items: ArrowSelectItem[]
|
|
|
15
15
|
* Supports both typing and paste (Cmd+V), including bracketed paste mode.
|
|
16
16
|
*/
|
|
17
17
|
export declare function promptMaskedInput(message: string): Promise<string>;
|
|
18
|
-
|
|
19
|
-
* Interactive provider setup wizard.
|
|
20
|
-
* Shows arrow-key provider selection, prompts for token, saves to ConfigStorage.
|
|
21
|
-
* Returns true if setup completed successfully, false if cancelled.
|
|
22
|
-
*/
|
|
23
|
-
export declare function runProviderSetupWizard(): Promise<boolean>;
|
|
18
|
+
export declare function runProviderSetupWizard(preselectedProviderId?: string): Promise<string | null>;
|
|
24
19
|
/**
|
|
25
20
|
* Prompt user for yes/no confirmation
|
|
26
21
|
*/
|
|
@@ -226,7 +226,16 @@ async function promptMaskedInput(message) {
|
|
|
226
226
|
* Shows arrow-key provider selection, prompts for token, saves to ConfigStorage.
|
|
227
227
|
* Returns true if setup completed successfully, false if cancelled.
|
|
228
228
|
*/
|
|
229
|
-
async function
|
|
229
|
+
async function pickProviderToConfigure(preselectedProviderId) {
|
|
230
|
+
// When the target provider is already known (explicit --provider, or a
|
|
231
|
+
// single-provider profile with missing creds), configure it directly.
|
|
232
|
+
const preselected = preselectedProviderId
|
|
233
|
+
? SUPPORTED_PROVIDERS.find((p) => p.id === preselectedProviderId && p.available)
|
|
234
|
+
: undefined;
|
|
235
|
+
if (preselected) {
|
|
236
|
+
console.log(chalk_1.default.yellow(`\n⚠ No ${preselected.label} API token configured.`));
|
|
237
|
+
return preselected;
|
|
238
|
+
}
|
|
230
239
|
console.log(chalk_1.default.yellow('\n⚠ No cloud provider API token configured.'));
|
|
231
240
|
const items = SUPPORTED_PROVIDERS.map((p) => ({
|
|
232
241
|
label: p.label,
|
|
@@ -236,15 +245,11 @@ async function runProviderSetupWizard() {
|
|
|
236
245
|
const index = await selectWithArrows('Select a provider to configure:', items);
|
|
237
246
|
if (index === -1) {
|
|
238
247
|
console.log(chalk_1.default.dim('\n Cancelled. Run: flui config set hetzner YOUR_TOKEN\n'));
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
const selectedProvider = SUPPORTED_PROVIDERS[index];
|
|
242
|
-
const schema = (0, provider_credential_schemas_1.getCredentialSchema)(selectedProvider.id);
|
|
243
|
-
if (!schema) {
|
|
244
|
-
console.log(chalk_1.default.red(`\n Missing credential schema for ${selectedProvider.label}\n`));
|
|
245
|
-
return false;
|
|
248
|
+
return null;
|
|
246
249
|
}
|
|
247
|
-
|
|
250
|
+
return SUPPORTED_PROVIDERS[index];
|
|
251
|
+
}
|
|
252
|
+
async function collectProviderCredentials(schema) {
|
|
248
253
|
const collected = {};
|
|
249
254
|
for (const field of schema.fields) {
|
|
250
255
|
const label = ` ${field.label}`;
|
|
@@ -255,17 +260,32 @@ async function runProviderSetupWizard() {
|
|
|
255
260
|
})).trim();
|
|
256
261
|
if (!value.trim()) {
|
|
257
262
|
console.log(chalk_1.default.red(`\n ${field.label} is required. Setup cancelled.\n`));
|
|
258
|
-
return
|
|
263
|
+
return null;
|
|
259
264
|
}
|
|
260
265
|
collected[field.key] = value.trim();
|
|
261
266
|
}
|
|
267
|
+
return collected;
|
|
268
|
+
}
|
|
269
|
+
async function runProviderSetupWizard(preselectedProviderId) {
|
|
270
|
+
const selectedProvider = await pickProviderToConfigure(preselectedProviderId);
|
|
271
|
+
if (!selectedProvider)
|
|
272
|
+
return null;
|
|
273
|
+
const schema = (0, provider_credential_schemas_1.getCredentialSchema)(selectedProvider.id);
|
|
274
|
+
if (!schema) {
|
|
275
|
+
console.log(chalk_1.default.red(`\n Missing credential schema for ${selectedProvider.label}\n`));
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
console.log(chalk_1.default.dim(`\n Enter your ${selectedProvider.label} credentials below.\n They will be stored encrypted on disk.\n`));
|
|
279
|
+
const collected = await collectProviderCredentials(schema);
|
|
280
|
+
if (!collected)
|
|
281
|
+
return null;
|
|
262
282
|
if (selectedProvider.id === 'scaleway') {
|
|
263
283
|
process.stdout.write(chalk_1.default.dim('\n Validating credentials with Scaleway IAM...'));
|
|
264
284
|
const result = await (0, scaleway_validator_1.validateScalewayCredentials)(collected.accessKey, collected.secretKey);
|
|
265
285
|
process.stdout.write('\r\u001B[2K');
|
|
266
286
|
if (!result.success) {
|
|
267
287
|
console.log(chalk_1.default.red(` ✖ ${result.message}\n`));
|
|
268
|
-
return
|
|
288
|
+
return null;
|
|
269
289
|
}
|
|
270
290
|
console.log(chalk_1.default.green(` ✔ ${result.message}`));
|
|
271
291
|
}
|
|
@@ -278,11 +298,11 @@ async function runProviderSetupWizard() {
|
|
|
278
298
|
storage.saveToken(selectedProvider.id, collected[schema.fields[0].key]);
|
|
279
299
|
}
|
|
280
300
|
console.log(chalk_1.default.green(`\n ✔ ${selectedProvider.label} credentials saved (AES-256-GCM encrypted)\n`));
|
|
281
|
-
return
|
|
301
|
+
return selectedProvider.id;
|
|
282
302
|
}
|
|
283
303
|
catch (err) {
|
|
284
304
|
console.log(chalk_1.default.red(`\n Failed to save credentials: ${err instanceof Error ? err.message : String(err)}\n`));
|
|
285
|
-
return
|
|
305
|
+
return null;
|
|
286
306
|
}
|
|
287
307
|
}
|
|
288
308
|
/**
|
|
@@ -10,6 +10,24 @@ export interface AppSummary {
|
|
|
10
10
|
lastDeployedAt?: string;
|
|
11
11
|
clusterId: string;
|
|
12
12
|
}
|
|
13
|
+
export interface AppGroupComponent extends AppSummary {
|
|
14
|
+
isPrimary?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface AppGroup {
|
|
17
|
+
id: string;
|
|
18
|
+
type: 'standalone' | 'composed';
|
|
19
|
+
name: string;
|
|
20
|
+
slug: string;
|
|
21
|
+
status: string;
|
|
22
|
+
category: string;
|
|
23
|
+
clusterId: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
catalogSlug?: string;
|
|
26
|
+
catalogInstallId?: string;
|
|
27
|
+
primaryComponentId?: string;
|
|
28
|
+
componentCount: number;
|
|
29
|
+
components: AppGroupComponent[];
|
|
30
|
+
}
|
|
13
31
|
export interface AppRuntime {
|
|
14
32
|
appId: string;
|
|
15
33
|
deploymentName: string;
|
|
@@ -186,14 +204,29 @@ export interface AvailableVersionsResponse {
|
|
|
186
204
|
nextPage: number | null;
|
|
187
205
|
allowedPatterns: string[] | null;
|
|
188
206
|
}
|
|
207
|
+
export interface AppEndpoint {
|
|
208
|
+
id: string;
|
|
209
|
+
clusterId: string;
|
|
210
|
+
applicationId?: string;
|
|
211
|
+
endpointType: string;
|
|
212
|
+
hostnameMode: string;
|
|
213
|
+
fqdn: string;
|
|
214
|
+
tlsEnabled: boolean;
|
|
215
|
+
certificateStatus?: string;
|
|
216
|
+
certificateMessage?: string;
|
|
217
|
+
reconciliationStatus?: string;
|
|
218
|
+
errorMessage?: string;
|
|
219
|
+
}
|
|
189
220
|
export declare class CliAppService {
|
|
190
221
|
private readonly apiClient;
|
|
191
222
|
private readonly clusterId;
|
|
192
223
|
constructor(apiClient: ApiClient, clusterId: string);
|
|
193
224
|
static create(clusterId: string): Promise<CliAppService>;
|
|
194
225
|
listApps(): Promise<AppSummary[]>;
|
|
226
|
+
listAppGroups(): Promise<AppGroup[]>;
|
|
195
227
|
getAppByName(name: string): Promise<AppSummary>;
|
|
196
228
|
getRuntime(appId: string): Promise<AppRuntime>;
|
|
229
|
+
listEndpoints(applicationId?: string): Promise<AppEndpoint[]>;
|
|
197
230
|
getLogs(options: AppLogsOptions): Promise<AppLogsResponse>;
|
|
198
231
|
scale(appId: string, replicas: number): Promise<AppRuntime>;
|
|
199
232
|
restart(appId: string): Promise<void>;
|
|
@@ -21,6 +21,9 @@ class CliAppService {
|
|
|
21
21
|
async listApps() {
|
|
22
22
|
return this.apiClient.get(`/clusters/${this.clusterId}/applications`);
|
|
23
23
|
}
|
|
24
|
+
async listAppGroups() {
|
|
25
|
+
return this.apiClient.get(`/clusters/${this.clusterId}/applications/grouped`);
|
|
26
|
+
}
|
|
24
27
|
async getAppByName(name) {
|
|
25
28
|
const apps = await this.listApps();
|
|
26
29
|
const app = apps.find((a) => a.name.toLowerCase() === name.toLowerCase() ||
|
|
@@ -33,6 +36,12 @@ class CliAppService {
|
|
|
33
36
|
async getRuntime(appId) {
|
|
34
37
|
return this.apiClient.get(`/applications/${appId}/runtime`);
|
|
35
38
|
}
|
|
39
|
+
async listEndpoints(applicationId) {
|
|
40
|
+
const all = await this.apiClient.get(`/clusters/${this.clusterId}/endpoints`);
|
|
41
|
+
return applicationId
|
|
42
|
+
? all.filter((e) => e.applicationId === applicationId)
|
|
43
|
+
: all;
|
|
44
|
+
}
|
|
36
45
|
async getLogs(options) {
|
|
37
46
|
const params = new URLSearchParams();
|
|
38
47
|
if (options.app)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @deprecated Import from shared template instead:
|
|
3
|
-
* import {
|
|
3
|
+
* import { CONTROL_FIREWALL_RULES, OBSERVABILITY_PORTS } from '../../../../src/modules/infrastructure/firewalls/templates/firewall-rules.template';
|
|
4
4
|
*
|
|
5
5
|
* This file is kept for backward compatibility with existing CLI code.
|
|
6
6
|
* New code should use the shared template.
|
|
7
7
|
*/
|
|
8
|
-
export {
|
|
8
|
+
export { CONTROL_FIREWALL_RULES, OBSERVABILITY_PORTS, WORKLOAD_FIREWALL_RULES, WORKLOAD_PORTS, validateFirewallRules, getFirewallRulesForClusterType, } from '../../../../src/modules/infrastructure/firewalls/templates/firewall-rules.template';
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFirewallRulesForClusterType = exports.validateFirewallRules = exports.WORKLOAD_PORTS = exports.WORKLOAD_FIREWALL_RULES = exports.OBSERVABILITY_PORTS = exports.
|
|
3
|
+
exports.getFirewallRulesForClusterType = exports.validateFirewallRules = exports.WORKLOAD_PORTS = exports.WORKLOAD_FIREWALL_RULES = exports.OBSERVABILITY_PORTS = exports.CONTROL_FIREWALL_RULES = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @deprecated Import from shared template instead:
|
|
6
|
-
* import {
|
|
6
|
+
* import { CONTROL_FIREWALL_RULES, OBSERVABILITY_PORTS } from '../../../../src/modules/infrastructure/firewalls/templates/firewall-rules.template';
|
|
7
7
|
*
|
|
8
8
|
* This file is kept for backward compatibility with existing CLI code.
|
|
9
9
|
* New code should use the shared template.
|
|
10
10
|
*/
|
|
11
11
|
var firewall_rules_template_1 = require("../../../../src/modules/infrastructure/firewalls/templates/firewall-rules.template");
|
|
12
|
-
Object.defineProperty(exports, "
|
|
12
|
+
Object.defineProperty(exports, "CONTROL_FIREWALL_RULES", { enumerable: true, get: function () { return firewall_rules_template_1.CONTROL_FIREWALL_RULES; } });
|
|
13
13
|
Object.defineProperty(exports, "OBSERVABILITY_PORTS", { enumerable: true, get: function () { return firewall_rules_template_1.OBSERVABILITY_PORTS; } });
|
|
14
14
|
Object.defineProperty(exports, "WORKLOAD_FIREWALL_RULES", { enumerable: true, get: function () { return firewall_rules_template_1.WORKLOAD_FIREWALL_RULES; } });
|
|
15
15
|
Object.defineProperty(exports, "WORKLOAD_PORTS", { enumerable: true, get: function () { return firewall_rules_template_1.WORKLOAD_PORTS; } });
|
|
@@ -58,7 +58,7 @@ const label_service_1 = require("../../../src/modules/infrastructure/shared/serv
|
|
|
58
58
|
const cli_k3s_script_service_1 = require("../services/cli-k3s-script.service");
|
|
59
59
|
const cli_cluster_creator_service_1 = require("../services/cli-cluster-creator.service");
|
|
60
60
|
const cli_clusters_service_1 = require("../services/cli-clusters.service");
|
|
61
|
-
const
|
|
61
|
+
const cli_control_cluster_service_1 = require("../services/cli-control-cluster.service");
|
|
62
62
|
const cli_ssh_service_1 = require("../services/cli-ssh.service");
|
|
63
63
|
const cli_ca_service_1 = require("../services/cli-ca.service");
|
|
64
64
|
const cli_logger_service_1 = require("../services/cli-logger.service");
|
|
@@ -174,7 +174,7 @@ exports.CliInfrastructureModule = CliInfrastructureModule = __decorate([
|
|
|
174
174
|
label_service_1.LabelService,
|
|
175
175
|
kubernetes_service_1.KubernetesService,
|
|
176
176
|
cli_clusters_service_1.CliClustersService,
|
|
177
|
-
|
|
177
|
+
cli_control_cluster_service_1.CliControlClusterService,
|
|
178
178
|
cli_ssh_service_1.CliSshService,
|
|
179
179
|
cli_ca_service_1.CliCaService,
|
|
180
180
|
cli_logger_service_1.CliLoggerService,
|
|
@@ -233,7 +233,7 @@ exports.CliInfrastructureModule = CliInfrastructureModule = __decorate([
|
|
|
233
233
|
cluster_node_scaling_service_1.ClusterNodeScalingService,
|
|
234
234
|
],
|
|
235
235
|
exports: [
|
|
236
|
-
|
|
236
|
+
cli_control_cluster_service_1.CliControlClusterService,
|
|
237
237
|
cli_clusters_service_1.CliClustersService,
|
|
238
238
|
kubernetes_service_1.KubernetesService,
|
|
239
239
|
label_service_1.LabelService,
|
|
@@ -61,6 +61,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
61
61
|
const fs = __importStar(require("node:fs"));
|
|
62
62
|
const path = __importStar(require("node:path"));
|
|
63
63
|
const os = __importStar(require("node:os"));
|
|
64
|
+
const profile_manager_1 = require("../lib/profile-manager");
|
|
64
65
|
const https = __importStar(require("node:https"));
|
|
65
66
|
const hetzner_firewall_service_1 = require("../../../src/modules/providers/services/hetzner-firewall.service");
|
|
66
67
|
const cli_firewall_repository_1 = require("../lib/repositories/cli-firewall.repository");
|
|
@@ -167,7 +168,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
167
168
|
provider: cluster.provider,
|
|
168
169
|
caPublicKey,
|
|
169
170
|
operationId: operation.id,
|
|
170
|
-
deployObservabilityStack:
|
|
171
|
+
deployObservabilityStack: (0, cluster_entity_1.isControlClusterType)(cluster.clusterType),
|
|
171
172
|
postgresPassword,
|
|
172
173
|
redisPassword,
|
|
173
174
|
grafanaPassword,
|
|
@@ -190,6 +191,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
190
191
|
clusterFirewallId: firewallId || '',
|
|
191
192
|
nipIoCertEnabled: !clusterMeta?.zitadelDomain,
|
|
192
193
|
acmeStaging: !!clusterMeta?.acmeStaging,
|
|
194
|
+
useLatest: !!clusterMeta?.useLatest,
|
|
193
195
|
nipHostnameToken: cluster.nipHostnameToken || null,
|
|
194
196
|
envVnet: envVnet
|
|
195
197
|
? {
|
|
@@ -277,7 +279,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
277
279
|
sharedStorageVolumeSizeGb: cluster.sharedStorageVolumeSizeGb ?? undefined,
|
|
278
280
|
});
|
|
279
281
|
// Step 3c: Informational — Zitadel PAT injected on demand via sync-auth-domain
|
|
280
|
-
if (
|
|
282
|
+
if ((0, cluster_entity_1.isControlClusterType)(cluster.clusterType)) {
|
|
281
283
|
this.log(opId, 'ℹ️ Zitadel service account PAT will be injected when sync-auth-domain is called.');
|
|
282
284
|
this.log(opId, ' After DNS is configured, call: POST /api/v1/clusters/:id/dns-zone/sync-auth-domain');
|
|
283
285
|
}
|
|
@@ -332,6 +334,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
332
334
|
provider: cluster.provider,
|
|
333
335
|
caPublicKey,
|
|
334
336
|
operationId: operation.id,
|
|
337
|
+
useLatest: !!clusterMeta?.useLatest,
|
|
335
338
|
sharedStorage: workerSharedStorage,
|
|
336
339
|
});
|
|
337
340
|
const workerServer = await provider.createServer({
|
|
@@ -362,12 +365,15 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
362
365
|
try {
|
|
363
366
|
const firewallRecord = await this.firewallRepository.findById(firewallId);
|
|
364
367
|
if (firewallRecord) {
|
|
368
|
+
// Cluster-scoped name so destroy matches by exact name only.
|
|
369
|
+
const scopedFirewallName = `flui-control-firewall-${cluster.id}`;
|
|
365
370
|
firewallRecord.clusterId = cluster.id;
|
|
371
|
+
firewallRecord.name = scopedFirewallName;
|
|
366
372
|
// Get server IDs from cluster nodes
|
|
367
373
|
const serverIds = cluster.nodes.map((node) => node.providerResourceId);
|
|
368
374
|
firewallRecord.appliedToServerIds = serverIds;
|
|
369
375
|
await this.firewallRepository.save(firewallRecord);
|
|
370
|
-
// Update Hetzner firewall labels with cluster ID
|
|
376
|
+
// Update Hetzner firewall labels + name with cluster ID
|
|
371
377
|
try {
|
|
372
378
|
const existingLabels = Object.fromEntries(firewallRecord.labels.map((l) => [l.key, l.value]));
|
|
373
379
|
const updatedLabels = {
|
|
@@ -375,7 +381,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
375
381
|
'flui-cluster-id': cluster.id,
|
|
376
382
|
'flui-cluster-name': cluster.name,
|
|
377
383
|
};
|
|
378
|
-
await this.firewallService.updateFirewallLabels(firewallId, updatedLabels);
|
|
384
|
+
await this.firewallService.updateFirewallLabels(firewallId, updatedLabels, scopedFirewallName);
|
|
379
385
|
this.log(opId, `✅ Firewall ${firewallId} labels updated on Hetzner with cluster ID`);
|
|
380
386
|
}
|
|
381
387
|
catch (labelError) {
|
|
@@ -399,6 +405,23 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
399
405
|
}
|
|
400
406
|
catch (error) {
|
|
401
407
|
this.log(opId, `Failed to create cluster: ${error.message}`, 'ERROR');
|
|
408
|
+
// Roll back the pre-created firewall. If creation failed before the
|
|
409
|
+
// rename/label step it stays with a temporary name and no
|
|
410
|
+
// flui-cluster-id label, which makes it invisible to `env destroy`
|
|
411
|
+
// (it matches neither the label query nor the scoped-name fallback)
|
|
412
|
+
// and leaks as an orphan that blocks the next run's name.
|
|
413
|
+
const orphanFirewallId = operation.metadata?.firewallId;
|
|
414
|
+
if (orphanFirewallId) {
|
|
415
|
+
try {
|
|
416
|
+
await this.firewallService.deleteFirewall(orphanFirewallId);
|
|
417
|
+
await this.firewallRepository.delete(orphanFirewallId);
|
|
418
|
+
this.log(opId, `✅ Rolled back firewall ${orphanFirewallId} after failed cluster creation`);
|
|
419
|
+
}
|
|
420
|
+
catch (cleanupError) {
|
|
421
|
+
this.log(opId, `Failed to roll back firewall ${orphanFirewallId}: ${cleanupError.message}. ` +
|
|
422
|
+
`Delete it manually on the provider to avoid an orphan.`, 'WARN');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
402
425
|
// Mark cluster as FAILED
|
|
403
426
|
cluster.status = cluster_entity_1.ClusterStatus.ERROR;
|
|
404
427
|
await this.clusterRepository.save(cluster);
|
|
@@ -571,8 +594,10 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
571
594
|
async createApiCredentialsSecret(operationId, masterIp, bootstrap) {
|
|
572
595
|
this.log(operationId, 'Patching Kubernetes secret with SSH CA keys + bootstrap vars...');
|
|
573
596
|
try {
|
|
574
|
-
// Load CLI CA keys to share with API for unified SSH access
|
|
575
|
-
|
|
597
|
+
// Load CLI CA keys to share with API for unified SSH access.
|
|
598
|
+
// CA keys are profile-scoped (~/.flui/profiles/<profile>/ca), not global —
|
|
599
|
+
// the old global path threw ENOENT and silently skipped the whole patch.
|
|
600
|
+
const caKeyDir = path.join(profile_manager_1.ProfileManager.getProfileDir(), 'ca');
|
|
576
601
|
const caPrivateKey = fs
|
|
577
602
|
.readFileSync(path.join(caKeyDir, 'ca_key'), 'utf8')
|
|
578
603
|
.trim();
|