@git.zone/cli 3.2.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.smartconfig.json +0 -1
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/classes.gitzoneconfig.js +1 -1
- package/dist_ts/classes.project.js +1 -1
- package/dist_ts/gitzone.cli.js +1 -1
- package/dist_ts/gitzone.logging.js +1 -1
- package/dist_ts/helpers.changelog.js +1 -1
- package/dist_ts/helpers.climode.js +1 -1
- package/dist_ts/helpers.smartconfig.js +61 -11
- package/dist_ts/helpers.smartconfigmigrations.js +1 -1
- package/dist_ts/helpers.tsdocker.js +1 -1
- package/dist_ts/helpers.workflow.d.ts +0 -2
- package/dist_ts/helpers.workflow.js +23 -12
- package/dist_ts/index.js +1 -1
- package/dist_ts/mod_audit/index.js +1 -1
- package/dist_ts/mod_commit/index.js +1 -1
- package/dist_ts/mod_commit/mod.helpers.d.ts +3 -3
- package/dist_ts/mod_commit/mod.helpers.js +10 -10
- package/dist_ts/mod_commit/mod.plugins.js +1 -1
- package/dist_ts/mod_commit/mod.ui.js +1 -1
- package/dist_ts/mod_config/classes.commitconfig.js +1 -1
- package/dist_ts/mod_config/classes.releaseconfig.js +1 -1
- package/dist_ts/mod_config/index.js +23 -24
- package/dist_ts/mod_config/mod.plugins.js +1 -1
- package/dist_ts/mod_deprecate/index.js +1 -1
- package/dist_ts/mod_deprecate/mod.plugins.js +1 -1
- package/dist_ts/mod_docker/classes.dockerpruner.js +7 -1
- package/dist_ts/mod_docker/index.js +1 -1
- package/dist_ts/mod_docker/mod.plugins.js +1 -1
- package/dist_ts/mod_format/classes.baseformatter.js +1 -1
- package/dist_ts/mod_format/classes.diffreporter.js +1 -1
- package/dist_ts/mod_format/classes.formatcontext.js +1 -1
- package/dist_ts/mod_format/classes.formatplanner.js +1 -1
- package/dist_ts/mod_format/classes.formatstats.js +1 -1
- package/dist_ts/mod_format/formatters/assets.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/cleanup.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/copy.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/gitignore.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/license.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/packagejson.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/prettier.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/readme.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/smartconfig.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/templates.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/tsconfig.formatter.js +1 -1
- package/dist_ts/mod_format/index.js +1 -1
- package/dist_ts/mod_format/interfaces.format.js +1 -1
- package/dist_ts/mod_format/mod.plugins.js +1 -1
- package/dist_ts/mod_helpers/index.js +1 -1
- package/dist_ts/mod_helpers/mod.plugins.js +1 -1
- package/dist_ts/mod_meta/index.js +1 -1
- package/dist_ts/mod_meta/meta.classes.meta.js +1 -1
- package/dist_ts/mod_meta/meta.interfaces.js +1 -1
- package/dist_ts/mod_meta/meta.plugins.js +1 -1
- package/dist_ts/mod_open/index.js +1 -1
- package/dist_ts/mod_open/mod.plugins.js +1 -1
- package/dist_ts/mod_release/helpers.releasebranch.d.ts +46 -0
- package/dist_ts/mod_release/helpers.releasebranch.js +579 -0
- package/dist_ts/mod_release/index.d.ts +10 -0
- package/dist_ts/mod_release/index.js +300 -92
- package/dist_ts/mod_release/mod.plugins.js +1 -1
- package/dist_ts/mod_services/classes.dockercontainer.d.ts +50 -3
- package/dist_ts/mod_services/classes.dockercontainer.js +646 -84
- package/dist_ts/mod_services/classes.globalregistry.d.ts +10 -24
- package/dist_ts/mod_services/classes.globalregistry.js +210 -62
- package/dist_ts/mod_services/classes.globalregistrystore.d.ts +48 -0
- package/dist_ts/mod_services/classes.globalregistrystore.js +156 -0
- package/dist_ts/mod_services/classes.interprocesslock.d.ts +21 -0
- package/dist_ts/mod_services/classes.interprocesslock.js +141 -0
- package/dist_ts/mod_services/classes.serviceconfiguration.d.ts +15 -12
- package/dist_ts/mod_services/classes.serviceconfiguration.js +188 -91
- package/dist_ts/mod_services/classes.servicedatamarker.d.ts +32 -7
- package/dist_ts/mod_services/classes.servicedatamarker.js +145 -49
- package/dist_ts/mod_services/classes.servicemanager.d.ts +70 -28
- package/dist_ts/mod_services/classes.servicemanager.js +995 -421
- package/dist_ts/mod_services/classes.servicenames.d.ts +8 -0
- package/dist_ts/mod_services/classes.servicenames.js +54 -0
- package/dist_ts/mod_services/classes.serviceoptions.js +1 -1
- package/dist_ts/mod_services/classes.servicepruner.d.ts +3 -0
- package/dist_ts/mod_services/classes.servicepruner.js +208 -46
- package/dist_ts/mod_services/helpers.js +1 -1
- package/dist_ts/mod_services/index.d.ts +2 -0
- package/dist_ts/mod_services/index.js +81 -58
- package/dist_ts/mod_services/mod.plugins.js +1 -1
- package/dist_ts/mod_standard/index.js +2 -2
- package/dist_ts/mod_standard/mod.plugins.js +1 -1
- package/dist_ts/mod_start/index.js +1 -1
- package/dist_ts/mod_start/mod.plugins.js +1 -1
- package/dist_ts/mod_template/index.js +1 -1
- package/dist_ts/mod_template/mod.plugins.js +1 -1
- package/dist_ts/mod_tools/classes.packagemanager.js +1 -1
- package/dist_ts/mod_tools/index.js +1 -1
- package/dist_ts/mod_tools/mod.plugins.js +1 -1
- package/dist_ts/paths.js +1 -1
- package/dist_ts/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +4 -2
- package/dist_ts_migration/001.service-selection.d.ts +9 -0
- package/dist_ts_migration/001.service-selection.js +51 -0
- package/dist_ts_migration/002.runtime-config.d.ts +19 -0
- package/dist_ts_migration/002.runtime-config.js +170 -0
- package/dist_ts_migration/003.service-data-marker.d.ts +9 -0
- package/dist_ts_migration/003.service-data-marker.js +156 -0
- package/dist_ts_migration/004.global-registry.d.ts +13 -0
- package/dist_ts_migration/004.global-registry.js +165 -0
- package/dist_ts_migration/index.d.ts +11 -0
- package/dist_ts_migration/index.js +59 -0
- package/package.json +6 -3
- package/readme.hints.md +102 -25
- package/readme.md +144 -41
- package/readme.plan.md +29 -16
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers.smartconfig.ts +63 -10
- package/ts/helpers.workflow.ts +41 -12
- package/ts/mod_commit/mod.helpers.ts +12 -8
- package/ts/mod_config/index.ts +22 -23
- package/ts/mod_docker/classes.dockerpruner.ts +6 -0
- package/ts/mod_release/helpers.releasebranch.ts +1282 -0
- package/ts/mod_release/index.ts +608 -129
- package/ts/mod_services/classes.dockercontainer.ts +912 -96
- package/ts/mod_services/classes.globalregistry.ts +290 -89
- package/ts/mod_services/classes.globalregistrystore.ts +279 -0
- package/ts/mod_services/classes.interprocesslock.ts +189 -0
- package/ts/mod_services/classes.serviceconfiguration.ts +215 -108
- package/ts/mod_services/classes.servicedatamarker.ts +210 -52
- package/ts/mod_services/classes.servicemanager.ts +1332 -512
- package/ts/mod_services/classes.servicenames.ts +76 -0
- package/ts/mod_services/classes.servicepruner.ts +310 -52
- package/ts/mod_services/index.ts +89 -62
- package/ts/mod_standard/index.ts +1 -1
- package/ts/plugins.ts +4 -0
- package/ts_migration/001.service-selection.ts +77 -0
- package/ts_migration/002.runtime-config.ts +218 -0
- package/ts_migration/003.service-data-marker.ts +216 -0
- package/ts_migration/004.global-registry.ts +272 -0
- package/ts_migration/index.ts +83 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
writeMongodbAuthOption,
|
|
10
10
|
type TServiceOptionSource,
|
|
11
11
|
} from './classes.serviceoptions.js';
|
|
12
|
+
import { withInterProcessLock } from './classes.interprocesslock.js';
|
|
12
13
|
|
|
13
14
|
/** Hosts that are considered local for the purposes of the no-auth guard. */
|
|
14
15
|
const localMongoHosts = ['localhost', '127.0.0.1', '::1'];
|
|
@@ -99,22 +100,6 @@ export const deriveS3BucketName = (projectNameArg: string): string => {
|
|
|
99
100
|
return canonical;
|
|
100
101
|
};
|
|
101
102
|
|
|
102
|
-
export type TLegacyS3BucketRepair =
|
|
103
|
-
| { bucket: unknown; repaired: false }
|
|
104
|
-
| { bucket: string; repaired: true };
|
|
105
|
-
|
|
106
|
-
/** Repair only the exact invalid value emitted by older GitZone releases. */
|
|
107
|
-
export const repairLegacyGeneratedS3Bucket = (
|
|
108
|
-
projectNameArg: string,
|
|
109
|
-
bucketArg: unknown,
|
|
110
|
-
): TLegacyS3BucketRepair => {
|
|
111
|
-
const legacyGeneratedBucket = `${projectNameArg}-documents`;
|
|
112
|
-
if (bucketArg !== legacyGeneratedBucket || isValidS3BucketName(bucketArg)) {
|
|
113
|
-
return { bucket: bucketArg, repaired: false };
|
|
114
|
-
}
|
|
115
|
-
return { bucket: deriveS3BucketName(projectNameArg), repaired: true };
|
|
116
|
-
};
|
|
117
|
-
|
|
118
103
|
export interface IServiceConfig {
|
|
119
104
|
PROJECT_NAME: string;
|
|
120
105
|
MONGODB_HOST: string;
|
|
@@ -131,9 +116,11 @@ export interface IServiceConfig {
|
|
|
131
116
|
MONGODB_AUTH_ENABLED: boolean;
|
|
132
117
|
S3_HOST: string;
|
|
133
118
|
S3_PORT: string;
|
|
134
|
-
|
|
119
|
+
S3_UI_PORT: string;
|
|
135
120
|
S3_ACCESSKEY: string;
|
|
136
121
|
S3_SECRETKEY: string;
|
|
122
|
+
S3_ADMIN_PASSWORD: string;
|
|
123
|
+
S3_REGION: string;
|
|
137
124
|
S3_BUCKET: string;
|
|
138
125
|
S3_ENDPOINT: string;
|
|
139
126
|
S3_USESSL: boolean;
|
|
@@ -144,9 +131,75 @@ export interface IServiceConfig {
|
|
|
144
131
|
ELASTICSEARCH_URL: string;
|
|
145
132
|
}
|
|
146
133
|
|
|
134
|
+
const runtimeStringFields: Array<keyof IServiceConfig> = [
|
|
135
|
+
'PROJECT_NAME',
|
|
136
|
+
'MONGODB_HOST',
|
|
137
|
+
'MONGODB_NAME',
|
|
138
|
+
'MONGODB_PORT',
|
|
139
|
+
'MONGODB_USER',
|
|
140
|
+
'MONGODB_PASS',
|
|
141
|
+
'MONGODB_URL',
|
|
142
|
+
'S3_HOST',
|
|
143
|
+
'S3_PORT',
|
|
144
|
+
'S3_UI_PORT',
|
|
145
|
+
'S3_ACCESSKEY',
|
|
146
|
+
'S3_SECRETKEY',
|
|
147
|
+
'S3_ADMIN_PASSWORD',
|
|
148
|
+
'S3_REGION',
|
|
149
|
+
'S3_BUCKET',
|
|
150
|
+
'S3_ENDPOINT',
|
|
151
|
+
'ELASTICSEARCH_HOST',
|
|
152
|
+
'ELASTICSEARCH_PORT',
|
|
153
|
+
'ELASTICSEARCH_USER',
|
|
154
|
+
'ELASTICSEARCH_PASS',
|
|
155
|
+
'ELASTICSEARCH_URL',
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const runtimePortFields: Array<keyof IServiceConfig> = [
|
|
159
|
+
'MONGODB_PORT',
|
|
160
|
+
'S3_PORT',
|
|
161
|
+
'S3_UI_PORT',
|
|
162
|
+
'ELASTICSEARCH_PORT',
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
/** Validate the complete canonical env.json shape before runtime use or persistence. */
|
|
166
|
+
export const assertCanonicalServiceRuntimeConfig: (
|
|
167
|
+
configArg: unknown,
|
|
168
|
+
) => asserts configArg is IServiceConfig = (configArg) => {
|
|
169
|
+
if (typeof configArg !== 'object' || configArg === null || Array.isArray(configArg)) {
|
|
170
|
+
throw new Error('.nogit/env.json must contain a JSON object at its root.');
|
|
171
|
+
}
|
|
172
|
+
const config = configArg as Record<string, unknown>;
|
|
173
|
+
for (const field of runtimeStringFields) {
|
|
174
|
+
if (typeof config[field] !== 'string' || !(config[field] as string).trim()) {
|
|
175
|
+
throw new Error(`.nogit/env.json field ${field} must be a non-empty string.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (typeof config.MONGODB_AUTH_ENABLED !== 'boolean') {
|
|
179
|
+
throw new Error('.nogit/env.json field MONGODB_AUTH_ENABLED must be a boolean.');
|
|
180
|
+
}
|
|
181
|
+
if (typeof config.S3_USESSL !== 'boolean') {
|
|
182
|
+
throw new Error('.nogit/env.json field S3_USESSL must be a boolean.');
|
|
183
|
+
}
|
|
184
|
+
for (const field of runtimePortFields) {
|
|
185
|
+
const rawPort = config[field];
|
|
186
|
+
if (typeof rawPort !== 'string' || !/^\d{1,5}$/u.test(rawPort)) {
|
|
187
|
+
throw new Error(`.nogit/env.json field ${field} must be a decimal port string.`);
|
|
188
|
+
}
|
|
189
|
+
const port = Number(rawPort);
|
|
190
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
|
|
191
|
+
throw new Error(`.nogit/env.json field ${field} is outside the TCP port range.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (config.S3_PORT === config.S3_UI_PORT) {
|
|
195
|
+
throw new Error('.nogit/env.json S3 API and UI ports must differ.');
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
147
199
|
export class ServiceConfiguration {
|
|
148
200
|
private configPath: string;
|
|
149
201
|
private config!: IServiceConfig;
|
|
202
|
+
private persistedConfigSnapshot?: string;
|
|
150
203
|
private docker: DockerContainer;
|
|
151
204
|
/** Where the effective MongoDB auth mode came from. */
|
|
152
205
|
private mongoAuthSource: TServiceOptionSource = 'default';
|
|
@@ -173,12 +226,20 @@ export class ServiceConfiguration {
|
|
|
173
226
|
// value, so a fresh checkout reproduces the declared setup.
|
|
174
227
|
await this.applyDeclaredServiceOptions();
|
|
175
228
|
|
|
176
|
-
//
|
|
229
|
+
// Only non-S3 services retain legacy port synchronization. ObjectStorage
|
|
230
|
+
// configuration is authoritative and is reconciled on its guarded start path.
|
|
177
231
|
await this.syncPortsFromDocker();
|
|
178
232
|
|
|
179
233
|
return this.config;
|
|
180
234
|
}
|
|
181
235
|
|
|
236
|
+
/** Reload and validate persisted runtime state without deriving or writing fields. */
|
|
237
|
+
public async reloadFromDisk(): Promise<IServiceConfig> {
|
|
238
|
+
await this.loadConfig();
|
|
239
|
+
assertCanonicalServiceRuntimeConfig(this.config);
|
|
240
|
+
return this.config;
|
|
241
|
+
}
|
|
242
|
+
|
|
182
243
|
/**
|
|
183
244
|
* Fold `@git.zone/cli.serviceOptions` into the runtime configuration.
|
|
184
245
|
*/
|
|
@@ -237,7 +298,6 @@ export class ServiceConfiguration {
|
|
|
237
298
|
*/
|
|
238
299
|
private updateDerivedFields(): void {
|
|
239
300
|
this.config.MONGODB_URL = this.buildMongoUrl();
|
|
240
|
-
this.config.S3_ENDPOINT = this.config.S3_HOST;
|
|
241
301
|
this.config.ELASTICSEARCH_URL = `http://${this.config.ELASTICSEARCH_USER}:${this.config.ELASTICSEARCH_PASS}@${this.config.ELASTICSEARCH_HOST}:${this.config.ELASTICSEARCH_PORT}`;
|
|
242
302
|
}
|
|
243
303
|
|
|
@@ -261,6 +321,38 @@ export class ServiceConfiguration {
|
|
|
261
321
|
}
|
|
262
322
|
}
|
|
263
323
|
|
|
324
|
+
/** Reject ObjectStorage's prohibited defaults and credential reuse before Docker mutation. */
|
|
325
|
+
public assertObjectStorageCredentialsAreSafe(): void {
|
|
326
|
+
const config = this.config;
|
|
327
|
+
for (const [name, value] of [
|
|
328
|
+
['S3_ACCESSKEY', config.S3_ACCESSKEY],
|
|
329
|
+
['S3_SECRETKEY', config.S3_SECRETKEY],
|
|
330
|
+
['S3_ADMIN_PASSWORD', config.S3_ADMIN_PASSWORD],
|
|
331
|
+
] as const) {
|
|
332
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
333
|
+
throw new Error(`${name} must be a non-empty string in .nogit/env.json.`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (config.S3_ACCESSKEY === 'admin' && config.S3_SECRETKEY === 'admin') {
|
|
337
|
+
throw new Error(
|
|
338
|
+
'ObjectStorage refuses the default S3 credentials. Set distinct non-default S3_ACCESSKEY and S3_SECRETKEY values in .nogit/env.json.',
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
if (config.S3_ADMIN_PASSWORD === 'admin') {
|
|
342
|
+
throw new Error(
|
|
343
|
+
'ObjectStorage refuses the default admin password. Set a non-default S3_ADMIN_PASSWORD in .nogit/env.json.',
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
if (
|
|
347
|
+
config.S3_ADMIN_PASSWORD === config.S3_ACCESSKEY ||
|
|
348
|
+
config.S3_ADMIN_PASSWORD === config.S3_SECRETKEY
|
|
349
|
+
) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
'S3_ADMIN_PASSWORD must be separate from S3_ACCESSKEY and S3_SECRETKEY in .nogit/env.json.',
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
264
356
|
/**
|
|
265
357
|
* Persist the MongoDB auth mode.
|
|
266
358
|
*
|
|
@@ -281,10 +373,45 @@ export class ServiceConfiguration {
|
|
|
281
373
|
* Save the configuration to file
|
|
282
374
|
*/
|
|
283
375
|
public async saveConfig(): Promise<void> {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
376
|
+
assertCanonicalServiceRuntimeConfig(this.config);
|
|
377
|
+
await withInterProcessLock(
|
|
378
|
+
{
|
|
379
|
+
lockPath: `${this.configPath}.lock`,
|
|
380
|
+
description: 'Service runtime configuration',
|
|
381
|
+
},
|
|
382
|
+
async () => {
|
|
383
|
+
let currentSnapshot: string | undefined;
|
|
384
|
+
try {
|
|
385
|
+
currentSnapshot = await plugins.fs.readFile(this.configPath, 'utf8');
|
|
386
|
+
} catch (error) {
|
|
387
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (currentSnapshot !== this.persistedConfigSnapshot) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
'Refusing to overwrite .nogit/env.json because it changed after loading.',
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const content = `${JSON.stringify(this.config, null, 2)}\n`;
|
|
398
|
+
const tempPath = `${this.configPath}.tmp-${process.pid}-${plugins.crypto
|
|
399
|
+
.randomBytes(12)
|
|
400
|
+
.toString('hex')}`;
|
|
401
|
+
try {
|
|
402
|
+
await plugins.fs.writeFile(tempPath, content, {
|
|
403
|
+
encoding: 'utf8',
|
|
404
|
+
flag: 'wx',
|
|
405
|
+
mode: 0o600,
|
|
406
|
+
});
|
|
407
|
+
await plugins.fs.rename(tempPath, this.configPath);
|
|
408
|
+
this.persistedConfigSnapshot = content;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
await plugins.fs.rm(tempPath, { force: true }).catch(() => {});
|
|
411
|
+
throw new Error('Could not persist .nogit/env.json atomically.', { cause: error });
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
);
|
|
288
415
|
}
|
|
289
416
|
|
|
290
417
|
/**
|
|
@@ -310,7 +437,12 @@ export class ServiceConfiguration {
|
|
|
310
437
|
.file(this.configPath)
|
|
311
438
|
.encoding('utf8')
|
|
312
439
|
.read()) as string;
|
|
313
|
-
|
|
440
|
+
const parsed = JSON.parse(configContent);
|
|
441
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
442
|
+
throw new Error('.nogit/env.json must contain a JSON object at its root.');
|
|
443
|
+
}
|
|
444
|
+
this.config = parsed as IServiceConfig;
|
|
445
|
+
this.persistedConfigSnapshot = configContent;
|
|
314
446
|
}
|
|
315
447
|
|
|
316
448
|
/**
|
|
@@ -320,11 +452,11 @@ export class ServiceConfiguration {
|
|
|
320
452
|
const projectName = await helpers.getProjectName();
|
|
321
453
|
const mongoPort = await helpers.getRandomAvailablePort();
|
|
322
454
|
const s3Port = await helpers.getRandomAvailablePort();
|
|
323
|
-
let
|
|
455
|
+
let s3UiPort = s3Port + 1;
|
|
324
456
|
|
|
325
|
-
// Ensure
|
|
326
|
-
while (!(await helpers.isPortAvailable(
|
|
327
|
-
|
|
457
|
+
// Ensure management UI port is also available
|
|
458
|
+
while (!(await helpers.isPortAvailable(s3UiPort))) {
|
|
459
|
+
s3UiPort++;
|
|
328
460
|
}
|
|
329
461
|
|
|
330
462
|
const mongoUser = 'defaultadmin';
|
|
@@ -350,9 +482,11 @@ export class ServiceConfiguration {
|
|
|
350
482
|
MONGODB_AUTH_ENABLED: true,
|
|
351
483
|
S3_HOST: s3Host,
|
|
352
484
|
S3_PORT: s3PortStr,
|
|
353
|
-
|
|
485
|
+
S3_UI_PORT: s3UiPort.toString(),
|
|
354
486
|
S3_ACCESSKEY: 'defaultadmin',
|
|
355
487
|
S3_SECRETKEY: 'defaultpass',
|
|
488
|
+
S3_ADMIN_PASSWORD: plugins.crypto.randomBytes(24).toString('base64url'),
|
|
489
|
+
S3_REGION: 'us-east-1',
|
|
356
490
|
S3_BUCKET: deriveS3BucketName(projectName),
|
|
357
491
|
S3_ENDPOINT: s3Host,
|
|
358
492
|
S3_USESSL: false,
|
|
@@ -363,12 +497,13 @@ export class ServiceConfiguration {
|
|
|
363
497
|
ELASTICSEARCH_URL: `http://${esUser}:${esPass}@${esHost}:${esPort}`
|
|
364
498
|
};
|
|
365
499
|
|
|
500
|
+
assertCanonicalServiceRuntimeConfig(this.config);
|
|
366
501
|
await this.saveConfig();
|
|
367
502
|
|
|
368
503
|
logger.log('ok', '✅ Created .nogit/env.json with project defaults');
|
|
369
504
|
logger.log('info', `📍 MongoDB port: ${mongoPort}`);
|
|
370
505
|
logger.log('info', `📍 S3 API port: ${s3Port}`);
|
|
371
|
-
logger.log('info', `📍 S3
|
|
506
|
+
logger.log('info', `📍 S3 management UI port: ${s3UiPort}`);
|
|
372
507
|
logger.log('info', `📍 Elasticsearch port: ${esPort}`);
|
|
373
508
|
}
|
|
374
509
|
|
|
@@ -445,16 +580,16 @@ export class ServiceConfiguration {
|
|
|
445
580
|
updated = true;
|
|
446
581
|
}
|
|
447
582
|
|
|
448
|
-
if (!this.config.
|
|
583
|
+
if (!this.config.S3_UI_PORT) {
|
|
449
584
|
const s3Port = parseInt(this.config.S3_PORT);
|
|
450
|
-
let
|
|
585
|
+
let uiPort = s3Port + 1;
|
|
451
586
|
|
|
452
|
-
while (!(await helpers.isPortAvailable(
|
|
453
|
-
|
|
587
|
+
while (!(await helpers.isPortAvailable(uiPort))) {
|
|
588
|
+
uiPort++;
|
|
454
589
|
}
|
|
455
590
|
|
|
456
|
-
this.config.
|
|
457
|
-
fieldsAdded.push(`
|
|
591
|
+
this.config.S3_UI_PORT = uiPort.toString();
|
|
592
|
+
fieldsAdded.push(`S3_UI_PORT(${uiPort})`);
|
|
458
593
|
updated = true;
|
|
459
594
|
}
|
|
460
595
|
|
|
@@ -469,21 +604,11 @@ export class ServiceConfiguration {
|
|
|
469
604
|
fieldsAdded.push('S3_SECRETKEY');
|
|
470
605
|
updated = true;
|
|
471
606
|
}
|
|
472
|
-
|
|
607
|
+
|
|
473
608
|
if (!this.config.S3_BUCKET) {
|
|
474
609
|
this.config.S3_BUCKET = deriveS3BucketName(this.config.PROJECT_NAME);
|
|
475
610
|
fieldsAdded.push('S3_BUCKET');
|
|
476
611
|
updated = true;
|
|
477
|
-
} else {
|
|
478
|
-
const repair = repairLegacyGeneratedS3Bucket(
|
|
479
|
-
this.config.PROJECT_NAME,
|
|
480
|
-
this.config.S3_BUCKET,
|
|
481
|
-
);
|
|
482
|
-
if (repair.repaired) {
|
|
483
|
-
this.config.S3_BUCKET = repair.bucket;
|
|
484
|
-
fieldsAdded.push('S3_BUCKET(repaired legacy generated name)');
|
|
485
|
-
updated = true;
|
|
486
|
-
}
|
|
487
612
|
}
|
|
488
613
|
|
|
489
614
|
// `undefined`, not falsy: a stored `false` is a valid value, and treating it
|
|
@@ -494,10 +619,8 @@ export class ServiceConfiguration {
|
|
|
494
619
|
updated = true;
|
|
495
620
|
}
|
|
496
621
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
this.config.S3_ENDPOINT = this.config.S3_HOST;
|
|
500
|
-
if (oldEndpoint !== this.config.S3_ENDPOINT) {
|
|
622
|
+
if (!this.config.S3_ENDPOINT) {
|
|
623
|
+
this.config.S3_ENDPOINT = this.config.S3_HOST;
|
|
501
624
|
fieldsAdded.push('S3_ENDPOINT');
|
|
502
625
|
updated = true;
|
|
503
626
|
}
|
|
@@ -534,6 +657,7 @@ export class ServiceConfiguration {
|
|
|
534
657
|
updated = true;
|
|
535
658
|
}
|
|
536
659
|
|
|
660
|
+
assertCanonicalServiceRuntimeConfig(this.config);
|
|
537
661
|
if (updated) {
|
|
538
662
|
await this.saveConfig();
|
|
539
663
|
logger.log('ok', `✅ Added missing fields: ${fieldsAdded.join(', ')}`);
|
|
@@ -560,7 +684,8 @@ export class ServiceConfiguration {
|
|
|
560
684
|
public getContainerNames() {
|
|
561
685
|
return {
|
|
562
686
|
mongo: `${this.config.PROJECT_NAME}-mongodb`,
|
|
563
|
-
|
|
687
|
+
objectstorage: `${this.config.PROJECT_NAME}-objectstorage`,
|
|
688
|
+
legacyMinio: `${this.config.PROJECT_NAME}-minio`,
|
|
564
689
|
elasticsearch: `${this.config.PROJECT_NAME}-elasticsearch`
|
|
565
690
|
};
|
|
566
691
|
}
|
|
@@ -574,7 +699,7 @@ export class ServiceConfiguration {
|
|
|
574
699
|
public getDataDirectories() {
|
|
575
700
|
return {
|
|
576
701
|
mongo: getServiceDataDirectory(process.cwd(), 'mongodb'),
|
|
577
|
-
|
|
702
|
+
objectstorage: getServiceDataDirectory(process.cwd(), 'objectstorage'),
|
|
578
703
|
elasticsearch: getServiceDataDirectory(process.cwd(), 'elasticsearch')
|
|
579
704
|
};
|
|
580
705
|
}
|
|
@@ -602,30 +727,6 @@ export class ServiceConfiguration {
|
|
|
602
727
|
}
|
|
603
728
|
}
|
|
604
729
|
|
|
605
|
-
// Check MinIO container
|
|
606
|
-
const minioStatus = await this.docker.getStatus(containers.minio);
|
|
607
|
-
if (minioStatus !== 'not_exists') {
|
|
608
|
-
const portMappings = await this.docker.getPortMappings(containers.minio);
|
|
609
|
-
if (portMappings) {
|
|
610
|
-
if (portMappings['9000']) {
|
|
611
|
-
const dockerPort = portMappings['9000'];
|
|
612
|
-
if (this.config.S3_PORT !== dockerPort) {
|
|
613
|
-
logger.log('note', `📍 Syncing S3 API port from Docker: ${dockerPort}`);
|
|
614
|
-
this.config.S3_PORT = dockerPort;
|
|
615
|
-
updated = true;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
if (portMappings['9001']) {
|
|
619
|
-
const dockerPort = portMappings['9001'];
|
|
620
|
-
if (this.config.S3_CONSOLE_PORT !== dockerPort) {
|
|
621
|
-
logger.log('note', `📍 Syncing S3 Console port from Docker: ${dockerPort}`);
|
|
622
|
-
this.config.S3_CONSOLE_PORT = dockerPort;
|
|
623
|
-
updated = true;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
730
|
// Check Elasticsearch container
|
|
630
731
|
const esStatus = await this.docker.getStatus(containers.elasticsearch);
|
|
631
732
|
if (esStatus !== 'not_exists') {
|
|
@@ -656,7 +757,6 @@ export class ServiceConfiguration {
|
|
|
656
757
|
|
|
657
758
|
// Check if containers exist - if they do, ports are fine
|
|
658
759
|
const mongoExists = await this.docker.exists(containers.mongo);
|
|
659
|
-
const minioExists = await this.docker.exists(containers.minio);
|
|
660
760
|
const esExists = await this.docker.exists(containers.elasticsearch);
|
|
661
761
|
|
|
662
762
|
// Only check port availability if containers don't exist
|
|
@@ -671,30 +771,6 @@ export class ServiceConfiguration {
|
|
|
671
771
|
}
|
|
672
772
|
}
|
|
673
773
|
|
|
674
|
-
if (!minioExists) {
|
|
675
|
-
const s3Port = parseInt(this.config.S3_PORT);
|
|
676
|
-
const s3ConsolePort = parseInt(this.config.S3_CONSOLE_PORT);
|
|
677
|
-
|
|
678
|
-
if (!(await helpers.isPortAvailable(s3Port))) {
|
|
679
|
-
logger.log('note', `⚠️ S3 API port ${s3Port} is in use, finding new port...`);
|
|
680
|
-
const newPort = await helpers.getRandomAvailablePort();
|
|
681
|
-
this.config.S3_PORT = newPort.toString();
|
|
682
|
-
logger.log('ok', `✅ New S3 API port: ${newPort}`);
|
|
683
|
-
updated = true;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (!(await helpers.isPortAvailable(s3ConsolePort))) {
|
|
687
|
-
logger.log('note', `⚠️ S3 Console port ${s3ConsolePort} is in use, finding new port...`);
|
|
688
|
-
let newPort = parseInt(this.config.S3_PORT) + 1;
|
|
689
|
-
while (!(await helpers.isPortAvailable(newPort))) {
|
|
690
|
-
newPort++;
|
|
691
|
-
}
|
|
692
|
-
this.config.S3_CONSOLE_PORT = newPort.toString();
|
|
693
|
-
logger.log('ok', `✅ New S3 Console port: ${newPort}`);
|
|
694
|
-
updated = true;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
774
|
if (!esExists) {
|
|
699
775
|
const esPort = parseInt(this.config.ELASTICSEARCH_PORT);
|
|
700
776
|
if (!(await helpers.isPortAvailable(esPort))) {
|
|
@@ -713,6 +789,37 @@ export class ServiceConfiguration {
|
|
|
713
789
|
|
|
714
790
|
return updated;
|
|
715
791
|
}
|
|
792
|
+
|
|
793
|
+
/** Validate ObjectStorage host ports only after legacy/conflict checks pass. */
|
|
794
|
+
public async validateObjectStoragePorts(): Promise<boolean> {
|
|
795
|
+
let updated = false;
|
|
796
|
+
let s3Port = parseInt(this.config.S3_PORT);
|
|
797
|
+
if (!(await helpers.isPortAvailable(s3Port))) {
|
|
798
|
+
logger.log('note', `⚠️ S3 API port ${s3Port} is in use, finding new port...`);
|
|
799
|
+
s3Port = await helpers.getRandomAvailablePort();
|
|
800
|
+
this.config.S3_PORT = s3Port.toString();
|
|
801
|
+
logger.log('ok', `✅ New S3 API port: ${s3Port}`);
|
|
802
|
+
updated = true;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
let uiPort = parseInt(this.config.S3_UI_PORT);
|
|
806
|
+
if (uiPort === s3Port || !(await helpers.isPortAvailable(uiPort))) {
|
|
807
|
+
logger.log('note', `⚠️ S3 management UI port ${uiPort} is in use, finding new port...`);
|
|
808
|
+
uiPort = s3Port + 1;
|
|
809
|
+
while (!(await helpers.isPortAvailable(uiPort))) {
|
|
810
|
+
uiPort++;
|
|
811
|
+
}
|
|
812
|
+
this.config.S3_UI_PORT = uiPort.toString();
|
|
813
|
+
logger.log('ok', `✅ New S3 management UI port: ${uiPort}`);
|
|
814
|
+
updated = true;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (updated) {
|
|
818
|
+
this.updateDerivedFields();
|
|
819
|
+
await this.saveConfig();
|
|
820
|
+
}
|
|
821
|
+
return updated;
|
|
822
|
+
}
|
|
716
823
|
|
|
717
824
|
/**
|
|
718
825
|
* Force reconfigure all ports with new available ones
|
|
@@ -722,11 +829,11 @@ export class ServiceConfiguration {
|
|
|
722
829
|
|
|
723
830
|
const mongoPort = await helpers.getRandomAvailablePort();
|
|
724
831
|
const s3Port = await helpers.getRandomAvailablePort();
|
|
725
|
-
let
|
|
832
|
+
let s3UiPort = s3Port + 1;
|
|
726
833
|
|
|
727
|
-
// Ensure
|
|
728
|
-
while (!(await helpers.isPortAvailable(
|
|
729
|
-
|
|
834
|
+
// Ensure management UI port is also available
|
|
835
|
+
while (!(await helpers.isPortAvailable(s3UiPort))) {
|
|
836
|
+
s3UiPort++;
|
|
730
837
|
}
|
|
731
838
|
|
|
732
839
|
// Elasticsearch uses standard port 9200
|
|
@@ -734,7 +841,7 @@ export class ServiceConfiguration {
|
|
|
734
841
|
|
|
735
842
|
this.config.MONGODB_PORT = mongoPort.toString();
|
|
736
843
|
this.config.S3_PORT = s3Port.toString();
|
|
737
|
-
this.config.
|
|
844
|
+
this.config.S3_UI_PORT = s3UiPort.toString();
|
|
738
845
|
this.config.ELASTICSEARCH_PORT = esPort;
|
|
739
846
|
|
|
740
847
|
this.updateDerivedFields();
|
|
@@ -743,7 +850,7 @@ export class ServiceConfiguration {
|
|
|
743
850
|
logger.log('ok', '✅ New port configuration:');
|
|
744
851
|
logger.log('info', ` 📍 MongoDB: ${mongoPort}`);
|
|
745
852
|
logger.log('info', ` 📍 S3 API: ${s3Port}`);
|
|
746
|
-
logger.log('info', ` 📍 S3
|
|
853
|
+
logger.log('info', ` 📍 S3 management UI: ${s3UiPort}`);
|
|
747
854
|
logger.log('info', ` 📍 Elasticsearch: ${esPort}`);
|
|
748
855
|
}
|
|
749
856
|
}
|