@flui-cloud/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cli/src/commands/env/create.js +5 -0
- package/lib/cli/src/commands/env/export-config.js +5 -3
- package/lib/cli/src/commands/version.js +25 -10
- package/lib/cli/src/config/bootstrap.config.js +6 -3
- package/lib/cli/src/config/release-override.d.ts +43 -0
- package/lib/cli/src/config/release-override.js +203 -0
- package/lib/cli/src/services/cli-control-cluster.service.js +5 -4
- package/lib/cli/src/services/cli-endpoint-resolver.service.js +18 -10
- package/lib/cli/src/services/cli-k3s-script.service.js +2 -2
- package/lib/src/config/release.config.js +3 -3
- package/oclif.manifest.json +730 -730
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ const api_client_1 = require("../../lib/api-client");
|
|
|
22
22
|
const config_storage_1 = require("../../lib/config-storage");
|
|
23
23
|
const provider_credential_schemas_1 = require("../../lib/provider-credential-schemas");
|
|
24
24
|
const context_banner_1 = require("../../lib/context-banner");
|
|
25
|
+
const release_override_1 = require("../../config/release-override");
|
|
25
26
|
const server_type_cache_service_1 = require("../../services/server-type-cache.service");
|
|
26
27
|
const server_type_validator_service_1 = require("../../services/server-type-validator.service");
|
|
27
28
|
const defaults_1 = require("../../config/defaults");
|
|
@@ -114,6 +115,10 @@ class EnvCreate extends core_1.Command {
|
|
|
114
115
|
const acmeStaging = flags['acme-staging'];
|
|
115
116
|
const useLatest = flags.latest;
|
|
116
117
|
spinner.stop();
|
|
118
|
+
const overrideBanner = (0, release_override_1.formatReleaseOverrideBanner)((0, release_override_1.getEffectiveRelease)(useLatest));
|
|
119
|
+
if (overrideBanner) {
|
|
120
|
+
console.log(overrideBanner);
|
|
121
|
+
}
|
|
117
122
|
if (acmeStaging) {
|
|
118
123
|
console.log(chalk_1.default.yellow(`\n⚠ ACME endpoint: Let's Encrypt STAGING — cert will not be browser-trusted (warning expected).\n`));
|
|
119
124
|
}
|
|
@@ -307,9 +307,11 @@ class EnvExportConfig extends core_1.Command {
|
|
|
307
307
|
apiBaseUrl: 'http://localhost:3000',
|
|
308
308
|
wsUrl: 'ws://localhost:3000',
|
|
309
309
|
authMode: opts.authMode,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
// Always reflect the resolved cluster — never keep a previous cluster's
|
|
311
|
+
// value. Empty oidcClientId is fine: the dashboard fetches it at runtime.
|
|
312
|
+
oidcIssuer: opts.oidcIssuer || '',
|
|
313
|
+
oidcClientId: opts.oidcClientId || '',
|
|
314
|
+
oidcAudience: opts.oidcAudience || '',
|
|
313
315
|
certificateMode,
|
|
314
316
|
};
|
|
315
317
|
fs.writeFileSync(configPath, JSON.stringify(updated, null, 2) + '\n', 'utf-8');
|
|
@@ -5,8 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const core_1 = require("@oclif/core");
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const release_config_1 = require("../../../src/config/release.config");
|
|
9
8
|
const bootstrap_config_1 = require("../config/bootstrap.config");
|
|
9
|
+
const release_override_1 = require("../config/release-override");
|
|
10
|
+
function describePlatform(release) {
|
|
11
|
+
if (release.source === 'latest') {
|
|
12
|
+
return chalk_1.default.yellow('latest (mobile tags)');
|
|
13
|
+
}
|
|
14
|
+
if (release.source === 'override') {
|
|
15
|
+
return `${chalk_1.default.yellow(release.version ?? 'override')}${chalk_1.default.dim(' (release override)')}`;
|
|
16
|
+
}
|
|
17
|
+
return `${chalk_1.default.cyan(release.version ?? '')}${chalk_1.default.dim(' (pinned release)')}`;
|
|
18
|
+
}
|
|
10
19
|
/**
|
|
11
20
|
* `flui version` — surfaces the CLI version AND the platform release it pins.
|
|
12
21
|
*
|
|
@@ -19,8 +28,9 @@ class Version extends core_1.Command {
|
|
|
19
28
|
async run() {
|
|
20
29
|
const { flags } = await this.parse(Version);
|
|
21
30
|
const useLatest = flags.latest;
|
|
22
|
-
const
|
|
23
|
-
const
|
|
31
|
+
const release = (0, release_override_1.getEffectiveRelease)(useLatest);
|
|
32
|
+
const tags = release.images;
|
|
33
|
+
const bootstrapRef = release.bootstrapRef;
|
|
24
34
|
const scriptsBaseUrl = (0, bootstrap_config_1.getScriptsBaseUrl)(useLatest);
|
|
25
35
|
const urlOverride = process.env.BOOTSTRAP_SCRIPTS_URL ?? null;
|
|
26
36
|
const images = {
|
|
@@ -31,23 +41,24 @@ class Version extends core_1.Command {
|
|
|
31
41
|
if (flags.json) {
|
|
32
42
|
this.log(JSON.stringify({
|
|
33
43
|
cli: this.config.version,
|
|
34
|
-
mode:
|
|
35
|
-
platform:
|
|
44
|
+
mode: release.source,
|
|
45
|
+
platform: release.version,
|
|
36
46
|
bootstrapRef,
|
|
37
47
|
scriptsBaseUrl,
|
|
38
48
|
bootstrapUrlOverride: urlOverride,
|
|
49
|
+
releaseFile: release.filePath,
|
|
39
50
|
images: tags,
|
|
40
51
|
}, null, 2));
|
|
41
52
|
return;
|
|
42
53
|
}
|
|
43
54
|
const label = (s) => chalk_1.default.dim(s.padEnd(15));
|
|
55
|
+
const cliName = chalk_1.default.dim(`(${this.config.name})`);
|
|
56
|
+
const platformLabel = describePlatform(release);
|
|
44
57
|
this.log('');
|
|
45
58
|
this.log(chalk_1.default.bold('Flui CLI'));
|
|
46
59
|
this.log('');
|
|
47
|
-
this.log(` ${label('CLI')}${chalk_1.default.cyan(this.config.version)} ${
|
|
48
|
-
this.log(` ${label('Platform')}${
|
|
49
|
-
? chalk_1.default.yellow('latest (mobile tags)')
|
|
50
|
-
: `${chalk_1.default.cyan(release_config_1.RELEASE.version)}${chalk_1.default.dim(' (pinned release)')}`}`);
|
|
60
|
+
this.log(` ${label('CLI')}${chalk_1.default.cyan(this.config.version)} ${cliName}`);
|
|
61
|
+
this.log(` ${label('Platform')}${platformLabel}`);
|
|
51
62
|
this.log(` ${label('Bootstrap ref')}${chalk_1.default.cyan(bootstrapRef)}`);
|
|
52
63
|
this.log('');
|
|
53
64
|
this.log(chalk_1.default.dim(' Component images:'));
|
|
@@ -59,7 +70,11 @@ class Version extends core_1.Command {
|
|
|
59
70
|
if (urlOverride) {
|
|
60
71
|
this.log(` ${label('')}${chalk_1.default.yellow('↑ overridden via BOOTSTRAP_SCRIPTS_URL')}`);
|
|
61
72
|
}
|
|
62
|
-
if (
|
|
73
|
+
if (release.source === 'override' && release.filePath) {
|
|
74
|
+
const rel = (0, release_override_1.displayReleaseFilePath)(release.filePath);
|
|
75
|
+
this.log(` ${label('Release file')}${chalk_1.default.yellow(rel)}`);
|
|
76
|
+
}
|
|
77
|
+
if (!useLatest && release.source !== 'override') {
|
|
63
78
|
this.log('');
|
|
64
79
|
this.log(chalk_1.default.dim(' Tip: `flui version --latest` shows what `env create --latest` would use.'));
|
|
65
80
|
}
|
|
@@ -10,6 +10,7 @@ exports.BOOTSTRAP_CONFIG = void 0;
|
|
|
10
10
|
exports.getScriptsBaseUrl = getScriptsBaseUrl;
|
|
11
11
|
exports.getScriptUrl = getScriptUrl;
|
|
12
12
|
const release_config_1 = require("../../../src/config/release.config");
|
|
13
|
+
const release_override_1 = require("./release-override");
|
|
13
14
|
const BOOTSTRAP_REPO_RAW_BASE = 'https://raw.githubusercontent.com/flui-cloud/bootstrap-scripts';
|
|
14
15
|
/**
|
|
15
16
|
* Base URL for the bootstrap scripts directory.
|
|
@@ -23,14 +24,16 @@ function getScriptsBaseUrl(useLatest = false) {
|
|
|
23
24
|
if (process.env.BOOTSTRAP_SCRIPTS_URL) {
|
|
24
25
|
return process.env.BOOTSTRAP_SCRIPTS_URL;
|
|
25
26
|
}
|
|
26
|
-
return `${BOOTSTRAP_REPO_RAW_BASE}/${(0,
|
|
27
|
+
return `${BOOTSTRAP_REPO_RAW_BASE}/${(0, release_override_1.resolveEffectiveBootstrapRef)(useLatest)}/scripts`;
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* Default bootstrap configuration
|
|
30
31
|
*/
|
|
31
32
|
exports.BOOTSTRAP_CONFIG = {
|
|
32
|
-
//
|
|
33
|
-
|
|
33
|
+
// Static pinned default; per-install (override-aware) resolution goes through
|
|
34
|
+
// getScriptsBaseUrl() at runtime — keep this off the override path so importing
|
|
35
|
+
// the module never reads flui.release.json.
|
|
36
|
+
scriptsBaseUrl: `${BOOTSTRAP_REPO_RAW_BASE}/${(0, release_config_1.resolveBootstrapRef)(false)}/scripts`,
|
|
34
37
|
scripts: {
|
|
35
38
|
fluiInit: 'flui-init.sh',
|
|
36
39
|
k3sMaster: 'k3s-master-init.sh',
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-install override of the pinned release manifest.
|
|
3
|
+
*
|
|
4
|
+
* Default install pins every component to the built-in RELEASE (see
|
|
5
|
+
* src/config/release.config.ts). A local `flui.release.json` in the cwd (or the
|
|
6
|
+
* path in FLUI_RELEASE_FILE) overrides any subset of it — used to install a
|
|
7
|
+
* dev/staging build from a branch, tag or commit. Omitted fields stay pinned.
|
|
8
|
+
*
|
|
9
|
+
* Each value is `branch:<x>` / `tag:<x>` / `commit:<x>` (or a bare literal).
|
|
10
|
+
* Scripts and images resolve a ref differently: scripts take any git ref
|
|
11
|
+
* verbatim (raw.githubusercontent serves branch/tag/sha), while an image tag
|
|
12
|
+
* must match what CI publishes — a branch name sanitized to a tag, or a
|
|
13
|
+
* short commit sha.
|
|
14
|
+
*/
|
|
15
|
+
import { type ComponentImageTags } from '../../../src/config/release.config';
|
|
16
|
+
declare const IMAGE_KEYS: readonly ["fluiApi", "fluiWeb", "fluiAuthz"];
|
|
17
|
+
type ImageKey = (typeof IMAGE_KEYS)[number];
|
|
18
|
+
export interface ReleaseOverrideFile {
|
|
19
|
+
version?: string;
|
|
20
|
+
bootstrapRef?: string;
|
|
21
|
+
images?: Partial<Record<ImageKey, string>>;
|
|
22
|
+
}
|
|
23
|
+
export interface OverrideEntry {
|
|
24
|
+
label: string;
|
|
25
|
+
spec: string;
|
|
26
|
+
resolved: string;
|
|
27
|
+
}
|
|
28
|
+
export interface EffectiveRelease {
|
|
29
|
+
version: string | null;
|
|
30
|
+
bootstrapRef: string;
|
|
31
|
+
images: ComponentImageTags;
|
|
32
|
+
source: 'pinned' | 'latest' | 'override';
|
|
33
|
+
overrides: OverrideEntry[];
|
|
34
|
+
filePath: string | null;
|
|
35
|
+
}
|
|
36
|
+
export declare function getEffectiveRelease(useLatest: boolean): EffectiveRelease;
|
|
37
|
+
export declare function resolveEffectiveBootstrapRef(useLatest: boolean): string;
|
|
38
|
+
export declare function resolveEffectiveImageTags(useLatest: boolean): ComponentImageTags;
|
|
39
|
+
/** Display path: relative when inside cwd, absolute when it would escape it. */
|
|
40
|
+
export declare function displayReleaseFilePath(filePath: string): string;
|
|
41
|
+
/** Loud banner so a stale override is never installed unnoticed. */
|
|
42
|
+
export declare function formatReleaseOverrideBanner(eff: EffectiveRelease): string | null;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-install override of the pinned release manifest.
|
|
4
|
+
*
|
|
5
|
+
* Default install pins every component to the built-in RELEASE (see
|
|
6
|
+
* src/config/release.config.ts). A local `flui.release.json` in the cwd (or the
|
|
7
|
+
* path in FLUI_RELEASE_FILE) overrides any subset of it — used to install a
|
|
8
|
+
* dev/staging build from a branch, tag or commit. Omitted fields stay pinned.
|
|
9
|
+
*
|
|
10
|
+
* Each value is `branch:<x>` / `tag:<x>` / `commit:<x>` (or a bare literal).
|
|
11
|
+
* Scripts and images resolve a ref differently: scripts take any git ref
|
|
12
|
+
* verbatim (raw.githubusercontent serves branch/tag/sha), while an image tag
|
|
13
|
+
* must match what CI publishes — a branch name sanitized to a tag, or a
|
|
14
|
+
* short commit sha.
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.getEffectiveRelease = getEffectiveRelease;
|
|
54
|
+
exports.resolveEffectiveBootstrapRef = resolveEffectiveBootstrapRef;
|
|
55
|
+
exports.resolveEffectiveImageTags = resolveEffectiveImageTags;
|
|
56
|
+
exports.displayReleaseFilePath = displayReleaseFilePath;
|
|
57
|
+
exports.formatReleaseOverrideBanner = formatReleaseOverrideBanner;
|
|
58
|
+
const fs = __importStar(require("node:fs"));
|
|
59
|
+
const path = __importStar(require("node:path"));
|
|
60
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
61
|
+
const release_config_1 = require("../../../src/config/release.config");
|
|
62
|
+
const OVERRIDE_FILE = 'flui.release.json';
|
|
63
|
+
const IMAGE_KEYS = ['fluiApi', 'fluiWeb', 'fluiAuthz'];
|
|
64
|
+
const COMPONENT_LABELS = {
|
|
65
|
+
fluiApi: 'flui-api (core)',
|
|
66
|
+
fluiWeb: 'flui-web (dashboard)',
|
|
67
|
+
fluiAuthz: 'flui-authz',
|
|
68
|
+
};
|
|
69
|
+
function parseRef(raw) {
|
|
70
|
+
const idx = raw.indexOf(':');
|
|
71
|
+
if (idx === -1)
|
|
72
|
+
return { kind: 'literal', value: raw };
|
|
73
|
+
const prefix = raw.slice(0, idx);
|
|
74
|
+
const value = raw.slice(idx + 1);
|
|
75
|
+
switch (prefix) {
|
|
76
|
+
case 'branch':
|
|
77
|
+
return { kind: 'branch', value };
|
|
78
|
+
case 'tag':
|
|
79
|
+
return { kind: 'tag', value };
|
|
80
|
+
case 'commit':
|
|
81
|
+
case 'sha':
|
|
82
|
+
return { kind: 'commit', value };
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Invalid ref "${raw}" in ${OVERRIDE_FILE}: unknown prefix "${prefix}:" — use branch:/tag:/commit:, or a bare tag.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Mirror docker/metadata-action `type=ref,event=branch`: invalid tag chars → "-". */
|
|
88
|
+
function branchToImageTag(branch) {
|
|
89
|
+
return branch.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
90
|
+
}
|
|
91
|
+
function resolveScriptsRef(spec) {
|
|
92
|
+
return parseRef(spec).value;
|
|
93
|
+
}
|
|
94
|
+
function resolveImageTag(spec) {
|
|
95
|
+
const ref = parseRef(spec);
|
|
96
|
+
switch (ref.kind) {
|
|
97
|
+
case 'branch':
|
|
98
|
+
return branchToImageTag(ref.value);
|
|
99
|
+
case 'commit':
|
|
100
|
+
return ref.value.slice(0, 7); // CI publishes type=sha,format=short
|
|
101
|
+
default:
|
|
102
|
+
return ref.value;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function loadOverrideFile() {
|
|
106
|
+
const explicit = process.env.FLUI_RELEASE_FILE;
|
|
107
|
+
const filePath = explicit
|
|
108
|
+
? path.resolve(explicit)
|
|
109
|
+
: path.join(process.cwd(), OVERRIDE_FILE);
|
|
110
|
+
if (!fs.existsSync(filePath))
|
|
111
|
+
return null;
|
|
112
|
+
let data;
|
|
113
|
+
try {
|
|
114
|
+
data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
throw new Error(`Failed to parse ${filePath}: ${err.message}`);
|
|
118
|
+
}
|
|
119
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
120
|
+
throw new Error(`${filePath} must contain a JSON object.`);
|
|
121
|
+
}
|
|
122
|
+
return { data, filePath };
|
|
123
|
+
}
|
|
124
|
+
let cache;
|
|
125
|
+
let cacheKey;
|
|
126
|
+
function applyOverride(base, data) {
|
|
127
|
+
const images = { ...base.images };
|
|
128
|
+
const overrides = [];
|
|
129
|
+
let bootstrapRef = base.bootstrapRef;
|
|
130
|
+
if (data.bootstrapRef !== undefined) {
|
|
131
|
+
bootstrapRef = resolveScriptsRef(data.bootstrapRef);
|
|
132
|
+
overrides.push({
|
|
133
|
+
label: 'bootstrap scripts',
|
|
134
|
+
spec: data.bootstrapRef,
|
|
135
|
+
resolved: bootstrapRef,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
for (const k of IMAGE_KEYS) {
|
|
139
|
+
const spec = data.images?.[k];
|
|
140
|
+
if (spec === undefined)
|
|
141
|
+
continue;
|
|
142
|
+
const resolved = resolveImageTag(spec);
|
|
143
|
+
images[k] = resolved;
|
|
144
|
+
overrides.push({ label: COMPONENT_LABELS[k], spec, resolved });
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
version: data.version ?? base.version,
|
|
148
|
+
bootstrapRef,
|
|
149
|
+
images,
|
|
150
|
+
overrides,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function getEffectiveRelease(useLatest) {
|
|
154
|
+
const key = `${useLatest}|${process.env.FLUI_RELEASE_FILE ?? ''}|${process.cwd()}`;
|
|
155
|
+
if (cache && cacheKey === key)
|
|
156
|
+
return cache;
|
|
157
|
+
const base = {
|
|
158
|
+
version: useLatest ? null : release_config_1.RELEASE.version,
|
|
159
|
+
bootstrapRef: (0, release_config_1.resolveBootstrapRef)(useLatest),
|
|
160
|
+
images: { ...(0, release_config_1.resolveImageTags)(useLatest) },
|
|
161
|
+
overrides: [],
|
|
162
|
+
};
|
|
163
|
+
const loaded = loadOverrideFile();
|
|
164
|
+
const resolved = loaded ? applyOverride(base, loaded.data) : base;
|
|
165
|
+
const isOverride = resolved.overrides.length > 0;
|
|
166
|
+
const pinnedSource = useLatest ? 'latest' : 'pinned';
|
|
167
|
+
const eff = {
|
|
168
|
+
version: resolved.version,
|
|
169
|
+
bootstrapRef: resolved.bootstrapRef,
|
|
170
|
+
images: resolved.images,
|
|
171
|
+
source: isOverride ? 'override' : pinnedSource,
|
|
172
|
+
overrides: resolved.overrides,
|
|
173
|
+
filePath: isOverride && loaded ? loaded.filePath : null,
|
|
174
|
+
};
|
|
175
|
+
cache = eff;
|
|
176
|
+
cacheKey = key;
|
|
177
|
+
return eff;
|
|
178
|
+
}
|
|
179
|
+
function resolveEffectiveBootstrapRef(useLatest) {
|
|
180
|
+
return getEffectiveRelease(useLatest).bootstrapRef;
|
|
181
|
+
}
|
|
182
|
+
function resolveEffectiveImageTags(useLatest) {
|
|
183
|
+
return getEffectiveRelease(useLatest).images;
|
|
184
|
+
}
|
|
185
|
+
/** Display path: relative when inside cwd, absolute when it would escape it. */
|
|
186
|
+
function displayReleaseFilePath(filePath) {
|
|
187
|
+
const rel = path.relative(process.cwd(), filePath);
|
|
188
|
+
return !rel || rel.startsWith('..') ? filePath : rel;
|
|
189
|
+
}
|
|
190
|
+
/** Loud banner so a stale override is never installed unnoticed. */
|
|
191
|
+
function formatReleaseOverrideBanner(eff) {
|
|
192
|
+
if (eff.source !== 'override' || !eff.filePath)
|
|
193
|
+
return null;
|
|
194
|
+
const rel = displayReleaseFilePath(eff.filePath);
|
|
195
|
+
const lines = eff.overrides.map((o) => {
|
|
196
|
+
const spec = chalk_1.default.dim(`(${o.spec})`);
|
|
197
|
+
return ` • ${o.label}: ${chalk_1.default.cyan(o.resolved)} ${spec}`;
|
|
198
|
+
});
|
|
199
|
+
return (chalk_1.default.yellow.bold('\n⚠ Release override active') +
|
|
200
|
+
chalk_1.default.dim(` — ${rel}\n`) +
|
|
201
|
+
chalk_1.default.dim(' Installing non-default component versions:\n') +
|
|
202
|
+
`${lines.join('\n')}\n`);
|
|
203
|
+
}
|
|
@@ -47,7 +47,7 @@ exports.CliControlClusterService = void 0;
|
|
|
47
47
|
const common_1 = require("@nestjs/common");
|
|
48
48
|
const cluster_entity_1 = require("../../../src/modules/infrastructure/clusters/entities/cluster.entity");
|
|
49
49
|
const cli_cluster_repository_1 = require("../lib/repositories/cli-cluster.repository");
|
|
50
|
-
const
|
|
50
|
+
const release_override_1 = require("../config/release-override");
|
|
51
51
|
const nip_base_domain_util_1 = require("../lib/nip-base-domain.util");
|
|
52
52
|
const cli_node_repository_1 = require("../lib/repositories/cli-node.repository");
|
|
53
53
|
const cli_clusters_service_1 = require("./cli-clusters.service");
|
|
@@ -127,6 +127,7 @@ let CliControlClusterService = CliControlClusterService_1 = class CliControlClus
|
|
|
127
127
|
// --latest opted into mobile dev tags. Recorded on the cluster so the
|
|
128
128
|
// installed version stays queryable after creation.
|
|
129
129
|
const useLatest = options?.useLatest ?? false;
|
|
130
|
+
const release = (0, release_override_1.getEffectiveRelease)(useLatest);
|
|
130
131
|
const createDto = {
|
|
131
132
|
name: 'control-cluster',
|
|
132
133
|
provider: provider,
|
|
@@ -146,9 +147,9 @@ let CliControlClusterService = CliControlClusterService_1 = class CliControlClus
|
|
|
146
147
|
adminEmail,
|
|
147
148
|
acmeStaging,
|
|
148
149
|
useLatest,
|
|
149
|
-
platformVersion:
|
|
150
|
-
componentVersions:
|
|
151
|
-
bootstrapRef:
|
|
150
|
+
platformVersion: release.version,
|
|
151
|
+
componentVersions: release.images,
|
|
152
|
+
bootstrapRef: release.bootstrapRef,
|
|
152
153
|
},
|
|
153
154
|
};
|
|
154
155
|
const { cluster } = await this.clustersService.create(createDto);
|
|
@@ -61,28 +61,33 @@ let CliEndpointResolverService = CliEndpointResolverService_1 = class CliEndpoin
|
|
|
61
61
|
const snapshot = await this.fetchSnapshot(masterIp);
|
|
62
62
|
const configMapData = snapshot.configmap?.data ?? {};
|
|
63
63
|
const secretData = snapshot.secret?.data ?? {};
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const oidcCliClientId = configMapData['OIDC_CLI_CLIENT_ID'] ?? '';
|
|
68
|
-
const oidcAudience = secretData['OIDC_AUDIENCE']
|
|
64
|
+
const live = snapshot.authConfig ?? {};
|
|
65
|
+
const pick = (...vals) => vals.find((v) => typeof v === 'string' && v.trim() !== '')?.trim() ?? '';
|
|
66
|
+
const frozenAudience = secretData['OIDC_AUDIENCE']
|
|
69
67
|
? Buffer.from(secretData['OIDC_AUDIENCE'], 'base64').toString('utf-8')
|
|
70
68
|
: '';
|
|
71
|
-
|
|
72
|
-
// `config.json` data key — written by OidcBootstrapService.patchWebConfigMap.
|
|
73
|
-
let oidcClientId = '';
|
|
69
|
+
let frozenClientId = '';
|
|
74
70
|
const webConfigJson = snapshot.webConfigMap?.data?.['config.json'];
|
|
75
71
|
if (webConfigJson) {
|
|
76
72
|
try {
|
|
77
73
|
const parsed = JSON.parse(webConfigJson);
|
|
78
74
|
if (typeof parsed.oidcClientId === 'string') {
|
|
79
|
-
|
|
75
|
+
frozenClientId = parsed.oidcClientId;
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
catch (err) {
|
|
83
79
|
this.logger.warn(`flui-web-config/config.json is not valid JSON: ${err.message}`);
|
|
84
80
|
}
|
|
85
81
|
}
|
|
82
|
+
// Prefer the live values; fall back to the frozen ConfigMap/Secret only if
|
|
83
|
+
// the API was unreachable (k3s auto-deploy resets the frozen ones).
|
|
84
|
+
const authMode = pick(live.authMode, configMapData['AUTH_MODE'], 'unknown');
|
|
85
|
+
const oidcIssuer = pick(live.issuer, configMapData['OIDC_ISSUER']);
|
|
86
|
+
const oidcJwksUri = configMapData['OIDC_JWKS_URI'] ?? '';
|
|
87
|
+
const oidcCliClientId = pick(live.cliClientId, configMapData['OIDC_CLI_CLIENT_ID']);
|
|
88
|
+
const oidcClientId = pick(live.clientId, frozenClientId);
|
|
89
|
+
// audience = web client id
|
|
90
|
+
const oidcAudience = pick(live.clientId, frozenAudience);
|
|
86
91
|
const endpoints = {};
|
|
87
92
|
for (const key of Object.keys(SYSTEM_APPS)) {
|
|
88
93
|
endpoints[key] = this.resolveApp(key, SYSTEM_APPS[key], snapshot.ingresses.items ?? [], snapshot.ingressRoutes.items ?? [], masterIp, nipHostnameToken);
|
|
@@ -104,7 +109,10 @@ let CliEndpointResolverService = CliEndpointResolverService_1 = class CliEndpoin
|
|
|
104
109
|
`CM=$(kubectl get configmap flui-api-config -n flui-system -o json 2>/dev/null || echo '{}')`,
|
|
105
110
|
`SEC=$(kubectl get secret flui-secrets -n flui-system -o json 2>/dev/null || echo '{}')`,
|
|
106
111
|
`WCM=$(kubectl get configmap flui-web-config -n flui-system -o json 2>/dev/null || echo '{}')`,
|
|
107
|
-
`
|
|
112
|
+
`AIP=$(kubectl get svc flui-api -n flui-system -o jsonpath='{.spec.clusterIP}' 2>/dev/null)`,
|
|
113
|
+
`APT=$(kubectl get svc flui-api -n flui-system -o jsonpath='{.spec.ports[0].port}' 2>/dev/null)`,
|
|
114
|
+
`AC=$(curl -s --max-time 5 "http://$AIP:$APT/api/v1/auth/config" 2>/dev/null || echo '{}')`,
|
|
115
|
+
`printf '{"ingresses":%s,"ingressRoutes":%s,"configmap":%s,"secret":%s,"webConfigMap":%s,"authConfig":%s}' "$ING" "$IR" "$CM" "$SEC" "$WCM" "$AC"`,
|
|
108
116
|
].join('; ');
|
|
109
117
|
const output = await this.sshService.sshExec(masterIp, command);
|
|
110
118
|
try {
|
|
@@ -50,7 +50,7 @@ const path = __importStar(require("node:path"));
|
|
|
50
50
|
const os = __importStar(require("node:os"));
|
|
51
51
|
const cli_logger_service_1 = require("./cli-logger.service");
|
|
52
52
|
const bootstrap_config_1 = require("../config/bootstrap.config");
|
|
53
|
-
const
|
|
53
|
+
const release_override_1 = require("../config/release-override");
|
|
54
54
|
/**
|
|
55
55
|
* CLI K3s Script Service
|
|
56
56
|
*
|
|
@@ -95,7 +95,7 @@ let CliK3sScriptService = CliK3sScriptService_1 = class CliK3sScriptService {
|
|
|
95
95
|
this.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, opId);
|
|
96
96
|
this.log(`[BOOTSTRAP MASTER SCRIPT] Cluster: ${config.clusterName}`, opId);
|
|
97
97
|
const scriptsBaseUrl = (0, bootstrap_config_1.getScriptsBaseUrl)(config.useLatest ?? false);
|
|
98
|
-
const imageTags = (0,
|
|
98
|
+
const imageTags = (0, release_override_1.resolveEffectiveImageTags)(config.useLatest ?? false);
|
|
99
99
|
this.log(`Scripts URL: ${scriptsBaseUrl}`, opId);
|
|
100
100
|
// Generate bootstrap script that downloads and executes k3s-master-init.sh from GitHub
|
|
101
101
|
const script = this.generateBootstrapScript('master', {
|
|
@@ -11,10 +11,10 @@ exports.RELEASE = void 0;
|
|
|
11
11
|
exports.resolveBootstrapRef = resolveBootstrapRef;
|
|
12
12
|
exports.resolveImageTags = resolveImageTags;
|
|
13
13
|
exports.RELEASE = {
|
|
14
|
-
version: '0.5.
|
|
15
|
-
bootstrapRef: 'v0.5.
|
|
14
|
+
version: '0.5.1',
|
|
15
|
+
bootstrapRef: 'v0.5.1',
|
|
16
16
|
images: {
|
|
17
|
-
fluiApi: '0.5.
|
|
17
|
+
fluiApi: '0.5.1',
|
|
18
18
|
fluiWeb: '0.5.0',
|
|
19
19
|
fluiAuthz: '0.5.0',
|
|
20
20
|
},
|