underpost 3.1.2 → 3.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/.env.example +0 -2
- package/.github/workflows/ghpkg.ci.yml +4 -4
- package/.github/workflows/npmpkg.ci.yml +38 -7
- package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -4
- package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
- package/.github/workflows/release.cd.yml +4 -4
- package/CHANGELOG.md +365 -1
- package/CLI-HELP.md +55 -3
- package/README.md +7 -3
- package/bin/build.js +18 -12
- package/bin/deploy.js +205 -225
- package/bin/file.js +3 -0
- package/conf.js +4 -10
- package/jsdoc.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +72 -50
- package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/nodemon.json +1 -1
- package/package.json +21 -14
- package/scripts/ports-ls.sh +2 -0
- package/scripts/rhel-grpc-setup.sh +56 -0
- package/src/api/file/file.ref.json +18 -0
- package/src/api/user/user.service.js +8 -7
- package/src/cli/cluster.js +7 -7
- package/src/cli/db.js +76 -242
- package/src/cli/deploy.js +104 -65
- package/src/cli/env.js +1 -0
- package/src/cli/fs.js +2 -1
- package/src/cli/index.js +50 -1
- package/src/cli/kubectl.js +211 -0
- package/src/cli/release.js +284 -0
- package/src/cli/repository.js +328 -112
- package/src/cli/run.js +283 -69
- package/src/cli/test.js +3 -3
- package/src/client/Default.index.js +3 -4
- package/src/client/components/core/Alert.js +2 -2
- package/src/client/components/core/AppStore.js +69 -0
- package/src/client/components/core/CalendarCore.js +2 -2
- package/src/client/components/core/Docs.js +9 -2
- package/src/client/components/core/DropDown.js +129 -17
- package/src/client/components/core/Keyboard.js +2 -2
- package/src/client/components/core/LogIn.js +2 -2
- package/src/client/components/core/LogOut.js +2 -2
- package/src/client/components/core/Modal.js +0 -1
- package/src/client/components/core/Panel.js +0 -1
- package/src/client/components/core/PanelForm.js +19 -19
- package/src/client/components/core/RichText.js +1 -2
- package/src/client/components/core/SocketIo.js +82 -29
- package/src/client/components/core/SocketIoHandler.js +75 -0
- package/src/client/components/core/Stream.js +143 -95
- package/src/client/components/core/Webhook.js +40 -7
- package/src/client/components/default/AppStoreDefault.js +5 -0
- package/src/client/components/default/LogInDefault.js +3 -3
- package/src/client/components/default/LogOutDefault.js +2 -2
- package/src/client/components/default/MenuDefault.js +5 -5
- package/src/client/components/default/SocketIoDefault.js +3 -51
- package/src/client/services/core/core.service.js +20 -8
- package/src/client/services/user/user.management.js +2 -2
- package/src/client/ssr/body/404.js +15 -11
- package/src/client/ssr/body/500.js +15 -11
- package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
- package/src/client/ssr/offline/NoNetworkConnection.js +11 -10
- package/src/client/ssr/pages/Test.js +11 -10
- package/src/index.js +24 -1
- package/src/runtime/express/Express.js +26 -9
- package/src/runtime/lampp/Dockerfile +9 -2
- package/src/runtime/lampp/Lampp.js +4 -3
- package/src/runtime/wp/Dockerfile +64 -0
- package/src/runtime/wp/Wp.js +497 -0
- package/src/server/auth.js +30 -6
- package/src/server/backup.js +19 -1
- package/src/server/client-build-docs.js +51 -110
- package/src/server/client-build.js +55 -64
- package/src/server/client-formatted.js +109 -57
- package/src/server/conf.js +19 -15
- package/src/server/ipfs-client.js +24 -1
- package/src/server/peer.js +8 -0
- package/src/server/runtime.js +25 -1
- package/src/server/start.js +21 -8
- package/src/ws/IoInterface.js +1 -10
- package/src/ws/IoServer.js +14 -33
- package/src/ws/core/channels/core.ws.chat.js +65 -20
- package/src/ws/core/channels/core.ws.mailer.js +113 -32
- package/src/ws/core/channels/core.ws.stream.js +90 -31
- package/src/ws/core/core.ws.connection.js +12 -33
- package/src/ws/core/core.ws.emit.js +10 -26
- package/src/ws/core/core.ws.server.js +25 -58
- package/src/ws/default/channels/default.ws.main.js +53 -12
- package/src/ws/default/default.ws.connection.js +26 -13
- package/src/ws/default/default.ws.server.js +30 -12
- package/src/client/components/default/ElementsDefault.js +0 -38
- package/src/ws/core/management/core.ws.chat.js +0 -8
- package/src/ws/core/management/core.ws.mailer.js +0 -16
- package/src/ws/core/management/core.ws.stream.js +0 -8
- package/src/ws/default/management/default.ws.main.js +0 -8
package/src/cli/run.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @namespace UnderpostRun
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { daemonProcess, getTerminalPid, shellCd, shellExec } from '../server/process.js';
|
|
7
|
+
import { daemonProcess, getTerminalPid, pbcopy, shellCd, shellExec } from '../server/process.js';
|
|
8
|
+
import crypto from 'crypto';
|
|
8
9
|
import {
|
|
9
10
|
awaitDeployMonitor,
|
|
10
11
|
buildKindPorts,
|
|
@@ -17,12 +18,31 @@ import {
|
|
|
17
18
|
import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
18
19
|
|
|
19
20
|
import fs from 'fs-extra';
|
|
21
|
+
import net from 'net';
|
|
20
22
|
import { range, setPad, timer } from '../client/components/core/CommonJs.js';
|
|
21
23
|
|
|
22
24
|
import os from 'os';
|
|
23
25
|
import Underpost from '../index.js';
|
|
24
26
|
import dotenv from 'dotenv';
|
|
25
27
|
|
|
28
|
+
const waitForPort = (port, host = '127.0.0.1', { maxAttempts = 30, interval = 2000 } = {}) =>
|
|
29
|
+
new Promise((resolve, reject) => {
|
|
30
|
+
let attempts = 0;
|
|
31
|
+
const tryConnect = () => {
|
|
32
|
+
attempts++;
|
|
33
|
+
const socket = net.createConnection({ port, host }, () => {
|
|
34
|
+
socket.destroy();
|
|
35
|
+
resolve();
|
|
36
|
+
});
|
|
37
|
+
socket.on('error', () => {
|
|
38
|
+
socket.destroy();
|
|
39
|
+
if (attempts >= maxAttempts) return reject(new Error(`Port ${port} not ready after ${maxAttempts} attempts`));
|
|
40
|
+
setTimeout(tryConnect, interval);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
tryConnect();
|
|
44
|
+
});
|
|
45
|
+
|
|
26
46
|
const logger = loggerFactory(import.meta);
|
|
27
47
|
|
|
28
48
|
/**
|
|
@@ -87,6 +107,7 @@ const logger = loggerFactory(import.meta);
|
|
|
87
107
|
* @property {boolean} logs - Whether to enable logs.
|
|
88
108
|
* @property {boolean} dryRun - Whether to perform a dry run.
|
|
89
109
|
* @property {boolean} createJobNow - Whether to create the job immediately.
|
|
110
|
+
* @property {number} fromNCommit - Number of commits back to use for message propagation (default: 1, last commit only).
|
|
90
111
|
* @property {string|Array<{ip: string, hostnames: string[]}>} hostAliases - Adds entries to the Pod /etc/hosts via Kubernetes hostAliases.
|
|
91
112
|
* As a string (CLI): semicolon-separated entries of "ip=hostname1,hostname2" (e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote").
|
|
92
113
|
* As an array (programmatic): objects with `ip` and `hostnames` fields (e.g., [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]).
|
|
@@ -151,7 +172,10 @@ const DEFAULT_OPTION = {
|
|
|
151
172
|
logs: false,
|
|
152
173
|
dryRun: false,
|
|
153
174
|
createJobNow: false,
|
|
175
|
+
fromNCommit: 0,
|
|
154
176
|
hostAliases: '',
|
|
177
|
+
gitClean: false,
|
|
178
|
+
copy: false,
|
|
155
179
|
};
|
|
156
180
|
|
|
157
181
|
/**
|
|
@@ -244,8 +268,15 @@ class UnderpostRun {
|
|
|
244
268
|
const ports = '6379,27017';
|
|
245
269
|
shellExec(`node bin run kill '${ports}'`);
|
|
246
270
|
shellExec(`node bin run dev-cluster --dev --expose --namespace ${options.namespace}`, { async: true });
|
|
247
|
-
|
|
248
|
-
|
|
271
|
+
logger.info('Waiting for port-forward services to be ready...');
|
|
272
|
+
try {
|
|
273
|
+
await Promise.all([waitForPort(27017), waitForPort(6379)]);
|
|
274
|
+
logger.info('Port-forward services are ready');
|
|
275
|
+
} catch (err) {
|
|
276
|
+
logger.error('Port-forward services failed to become ready', { error: err.message });
|
|
277
|
+
shellExec(`node bin run kill '${ports}'`);
|
|
278
|
+
throw err;
|
|
279
|
+
}
|
|
249
280
|
shellExec(`node bin metadata --generate ${path}`);
|
|
250
281
|
shellExec(`node bin db --dev --clean-fs-collection dd`);
|
|
251
282
|
shellExec(`node bin run kill '${ports}'`);
|
|
@@ -354,8 +385,10 @@ class UnderpostRun {
|
|
|
354
385
|
},
|
|
355
386
|
/**
|
|
356
387
|
* @method template-deploy
|
|
357
|
-
* @description Pushes `engine-private`, dispatches CI workflow to build `pwa-microservices-template`,
|
|
358
|
-
*
|
|
388
|
+
* @description Pushes `engine-private`, dispatches CI workflow to build `pwa-microservices-template`,
|
|
389
|
+
* and optionally triggers engine-<conf-id> CI with sync/init which in turn dispatches the CD workflow
|
|
390
|
+
* after the build chain completes (template → ghpkg → engine-<conf-id> → CD).
|
|
391
|
+
* @param {string} path - The deployment path identifier (e.g., 'sync-engine-core', 'init-engine-core', or empty for build-only).
|
|
359
392
|
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
360
393
|
* @memberof UnderpostRun
|
|
361
394
|
*/
|
|
@@ -368,7 +401,18 @@ class UnderpostRun {
|
|
|
368
401
|
return;
|
|
369
402
|
}
|
|
370
403
|
shellExec(`${baseCommand} run pull`);
|
|
371
|
-
|
|
404
|
+
|
|
405
|
+
// Capture last N commit messages for propagation.
|
|
406
|
+
// When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
|
|
407
|
+
const fromN =
|
|
408
|
+
options.fromNCommit && parseInt(options.fromNCommit) > 0
|
|
409
|
+
? parseInt(options.fromNCommit)
|
|
410
|
+
: Underpost.repo.getUnpushedCount('.').count;
|
|
411
|
+
const message = shellExec(`node bin cmt --changelog ${fromN} --changelog-no-hash`, {
|
|
412
|
+
silent: true,
|
|
413
|
+
stdout: true,
|
|
414
|
+
}).trim();
|
|
415
|
+
|
|
372
416
|
shellExec(
|
|
373
417
|
`${baseCommand} push ./engine-private ${options.force ? '-f ' : ''}${
|
|
374
418
|
process.env.GITHUB_USERNAME
|
|
@@ -376,54 +420,76 @@ class UnderpostRun {
|
|
|
376
420
|
);
|
|
377
421
|
shellCd('/home/dd/engine');
|
|
378
422
|
|
|
379
|
-
|
|
380
|
-
const deployBoundaryHash = shellExec('git rev-parse HEAD', {
|
|
381
|
-
stdout: true,
|
|
382
|
-
silent: true,
|
|
383
|
-
disableLog: true,
|
|
384
|
-
}).trim();
|
|
385
|
-
|
|
386
|
-
function replaceNthNewline(str, n, replacement = ' ') {
|
|
387
|
-
let count = 0;
|
|
388
|
-
return str.replace(/\r\n?|\n/g, (match) => {
|
|
389
|
-
count++;
|
|
390
|
-
return count === n ? replacement : match;
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
const sanitizedMessage = message
|
|
394
|
-
? replaceNthNewline(message.replaceAll('"', '').replaceAll('`', '').replaceAll('#', '').replaceAll('- ', ''), 2)
|
|
395
|
-
.replace(/\r\n?|\n/g, ' ')
|
|
396
|
-
.trim()
|
|
397
|
-
: '';
|
|
423
|
+
const sanitizedMessage = Underpost.repo.sanitizeChangelogMessage(message);
|
|
398
424
|
|
|
399
425
|
// Push engine repo so workflow YAML changes reach GitHub
|
|
400
426
|
shellExec(`git reset`);
|
|
401
427
|
shellExec(`${baseCommand} push . ${options.force ? '-f ' : ''}${process.env.GITHUB_USERNAME}/engine`);
|
|
402
428
|
|
|
403
|
-
//
|
|
429
|
+
// Determine deploy conf and type from path (sync-engine-core, init-engine-core, etc.)
|
|
430
|
+
let deployConfId = '';
|
|
431
|
+
let deployType = '';
|
|
432
|
+
if (path.startsWith('sync-')) {
|
|
433
|
+
deployConfId = path.replace(/^sync-/, '');
|
|
434
|
+
deployType = 'sync-and-deploy';
|
|
435
|
+
} else if (path.startsWith('init-')) {
|
|
436
|
+
deployConfId = path.replace(/^init-/, '');
|
|
437
|
+
deployType = 'init';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Dispatch npmpkg CI workflow — this builds pwa-microservices-template first.
|
|
441
|
+
// If deployConfId is set, npmpkg.ci.yml will dispatch the engine-<conf-id> CI
|
|
442
|
+
// with sync=true after template build completes. The engine CI then dispatches
|
|
443
|
+
// the CD workflow after the engine repo build finishes — ensuring correct sequence:
|
|
444
|
+
// npmpkg.ci → engine-<id>.ci → engine-<id>.cd
|
|
404
445
|
const repo = `${process.env.GITHUB_USERNAME}/engine`;
|
|
446
|
+
const inputs = {};
|
|
447
|
+
if (sanitizedMessage) inputs.message = sanitizedMessage;
|
|
448
|
+
if (deployConfId) inputs.deploy_conf_id = deployConfId;
|
|
449
|
+
if (deployType) inputs.deploy_type = deployType;
|
|
450
|
+
|
|
405
451
|
Underpost.repo.dispatchWorkflow({
|
|
406
452
|
repo,
|
|
407
453
|
workflowFile: 'npmpkg.ci.yml',
|
|
408
454
|
ref: 'master',
|
|
409
|
-
inputs
|
|
455
|
+
inputs,
|
|
410
456
|
});
|
|
457
|
+
},
|
|
411
458
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
459
|
+
/**
|
|
460
|
+
* @method template-deploy-local
|
|
461
|
+
* @description Similar to `template-deploy` but runs the workflow locally without dispatching GitHub Actions. It pulls the latest changes, pushes to GitHub, builds the template, and optionally triggers a local release with CI push.
|
|
462
|
+
* @param {string} path - The deployment path identifier (e.g., 'sync-engine-core', 'init-engine-core', or empty for build-only).
|
|
463
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
464
|
+
* @memberof UnderpostRun
|
|
465
|
+
*/
|
|
466
|
+
'template-deploy-local': async (path, options = DEFAULT_OPTION) => {
|
|
467
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
468
|
+
shellExec(`npm run security:secrets`);
|
|
469
|
+
const reportPath = './gitleaks-report.json';
|
|
470
|
+
if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
|
|
471
|
+
logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
|
|
472
|
+
return;
|
|
421
473
|
}
|
|
474
|
+
shellExec(`${baseCommand} run pull`);
|
|
422
475
|
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
476
|
+
// Capture last N commit messages from the engine repo.
|
|
477
|
+
// When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
|
|
478
|
+
const fromN =
|
|
479
|
+
options.fromNCommit && parseInt(options.fromNCommit) > 0
|
|
480
|
+
? parseInt(options.fromNCommit)
|
|
481
|
+
: Underpost.repo.getUnpushedCount('.').count;
|
|
482
|
+
const rawMessage = shellExec(`node bin cmt --changelog ${fromN} --changelog-no-hash`, {
|
|
483
|
+
silent: true,
|
|
484
|
+
stdout: true,
|
|
485
|
+
}).trim();
|
|
486
|
+
const sanitizedMessage = Underpost.repo.sanitizeChangelogMessage(rawMessage);
|
|
426
487
|
|
|
488
|
+
const { triggerCmd } = path
|
|
489
|
+
? await Underpost.release.ci(path, sanitizedMessage, options)
|
|
490
|
+
: await Underpost.release.pwa(sanitizedMessage, options);
|
|
491
|
+
pbcopy(triggerCmd + ' && cd /home/dd/engine');
|
|
492
|
+
},
|
|
427
493
|
/**
|
|
428
494
|
* @method template-deploy-image
|
|
429
495
|
* @description Dispatches the Docker image CI workflow for the `engine` repository.
|
|
@@ -439,6 +505,21 @@ class UnderpostRun {
|
|
|
439
505
|
inputs: {},
|
|
440
506
|
});
|
|
441
507
|
},
|
|
508
|
+
/**
|
|
509
|
+
* @method docker-image
|
|
510
|
+
* @description Dispatches the Docker image CI workflow (`docker-image.ci.yml`) for the `engine` repository via `workflow_dispatch`.
|
|
511
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
512
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
513
|
+
* @memberof UnderpostRun
|
|
514
|
+
*/
|
|
515
|
+
'docker-image': (path, options = DEFAULT_OPTION) => {
|
|
516
|
+
Underpost.repo.dispatchWorkflow({
|
|
517
|
+
repo: `${process.env.GITHUB_USERNAME}/engine`,
|
|
518
|
+
workflowFile: 'docker-image.ci.yml',
|
|
519
|
+
ref: 'master',
|
|
520
|
+
inputs: {},
|
|
521
|
+
});
|
|
522
|
+
},
|
|
442
523
|
/**
|
|
443
524
|
* @method clean
|
|
444
525
|
* @description Changes directory to the provided path (defaulting to `/home/dd/engine`) and runs `node bin/deploy clean-core-repo`.
|
|
@@ -606,20 +687,22 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
606
687
|
const cmdString = options.cmd
|
|
607
688
|
? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
|
|
608
689
|
: '';
|
|
690
|
+
const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : ' --kubeadm';
|
|
691
|
+
const gitCleanFlag = options.gitClean ? ' --git-clean' : '';
|
|
609
692
|
|
|
610
693
|
shellExec(
|
|
611
|
-
`${baseCommand} deploy --
|
|
694
|
+
`${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
612
695
|
image ? ` --image ${image}` : ''
|
|
613
696
|
}${versions ? ` --versions ${versions}` : ''}${
|
|
614
697
|
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
615
|
-
}${timeoutFlags}${cmdString} ${deployId} ${env}`,
|
|
698
|
+
}${timeoutFlags}${cmdString}${gitCleanFlag} ${deployId} ${env}`,
|
|
616
699
|
);
|
|
617
700
|
|
|
618
701
|
if (isDeployRunnerContext(path, options)) {
|
|
619
702
|
shellExec(
|
|
620
|
-
`${baseCommand} deploy
|
|
703
|
+
`${baseCommand} deploy${clusterFlag}${cmdString} --replicas ${replicas} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
|
|
621
704
|
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
622
|
-
}${timeoutFlags}`,
|
|
705
|
+
}${timeoutFlags}${gitCleanFlag}`,
|
|
623
706
|
);
|
|
624
707
|
if (!targetTraffic)
|
|
625
708
|
targetTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
@@ -831,6 +914,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
831
914
|
const confInstances = JSON.parse(
|
|
832
915
|
fs.readFileSync(`./engine-private/conf/${deployId}/conf.instances.json`, 'utf8'),
|
|
833
916
|
);
|
|
917
|
+
let promotedTraffic = '';
|
|
834
918
|
for (const instance of confInstances) {
|
|
835
919
|
let {
|
|
836
920
|
id: _id,
|
|
@@ -850,6 +934,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
850
934
|
namespace: options.namespace,
|
|
851
935
|
});
|
|
852
936
|
const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
|
|
937
|
+
promotedTraffic = targetTraffic;
|
|
853
938
|
let proxyYaml =
|
|
854
939
|
Underpost.deploy.baseProxyYamlFactory({ host: _host, env: options.tls ? 'production' : env, options }) +
|
|
855
940
|
Underpost.deploy.deploymentYamlServiceFactory({
|
|
@@ -875,6 +960,18 @@ EOF
|
|
|
875
960
|
{ disableLog: true },
|
|
876
961
|
);
|
|
877
962
|
}
|
|
963
|
+
// Refresh the gRPC service to ensure it points to the parent deploy's current traffic.
|
|
964
|
+
if (promotedTraffic) {
|
|
965
|
+
const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
|
|
966
|
+
const grpcServicePath = Underpost.deploy.buildGrpcServiceManifest({
|
|
967
|
+
deployId,
|
|
968
|
+
env,
|
|
969
|
+
confServer: loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
|
|
970
|
+
namespace: options.namespace,
|
|
971
|
+
traffic: [parentTraffic],
|
|
972
|
+
});
|
|
973
|
+
if (grpcServicePath) shellExec(`kubectl apply -f ${grpcServicePath} -n ${options.namespace}`);
|
|
974
|
+
}
|
|
878
975
|
},
|
|
879
976
|
|
|
880
977
|
/**
|
|
@@ -914,12 +1011,12 @@ EOF
|
|
|
914
1011
|
// `localhost/rockylinux9-underpost:${Underpost.version}`
|
|
915
1012
|
if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
|
|
916
1013
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
}
|
|
1014
|
+
Underpost.image.pullDockerHubImage({
|
|
1015
|
+
dockerhubImage: _image,
|
|
1016
|
+
kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
|
|
1017
|
+
kubeadm: options.nodeName || options.kubeadm,
|
|
1018
|
+
k3s: options.k3s,
|
|
1019
|
+
});
|
|
923
1020
|
|
|
924
1021
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
|
|
925
1022
|
hostTest: _host,
|
|
@@ -928,7 +1025,7 @@ EOF
|
|
|
928
1025
|
|
|
929
1026
|
const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
|
|
930
1027
|
const podId = `${_deployId}-${env}-${targetTraffic}`;
|
|
931
|
-
const ignorePods = Underpost.
|
|
1028
|
+
const ignorePods = Underpost.kubectl.get(podId, 'pods', options.namespace).map((p) => p.NAME);
|
|
932
1029
|
Underpost.deploy.configMap(env, options.namespace);
|
|
933
1030
|
shellExec(`kubectl delete service ${podId}-service --namespace ${options.namespace} --ignore-not-found`);
|
|
934
1031
|
shellExec(`kubectl delete deployment ${podId} --namespace ${options.namespace} --ignore-not-found`);
|
|
@@ -940,7 +1037,30 @@ EOF
|
|
|
940
1037
|
env,
|
|
941
1038
|
version: targetTraffic,
|
|
942
1039
|
nodeName: options.nodeName,
|
|
1040
|
+
clusterContext: options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind',
|
|
1041
|
+
gitClean: options.gitClean || false,
|
|
943
1042
|
});
|
|
1043
|
+
// Regenerate the parent deploy's gRPC ClusterIP service pointing to the
|
|
1044
|
+
// parent's current traffic colour and apply it before the instance pod starts so
|
|
1045
|
+
// DNS is resolvable the moment the pod boots.
|
|
1046
|
+
const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
|
|
1047
|
+
const grpcServicePath = Underpost.deploy.buildGrpcServiceManifest({
|
|
1048
|
+
deployId,
|
|
1049
|
+
env,
|
|
1050
|
+
confServer: loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
|
|
1051
|
+
namespace: options.namespace,
|
|
1052
|
+
traffic: [targetTraffic],
|
|
1053
|
+
host: _host,
|
|
1054
|
+
});
|
|
1055
|
+
if (grpcServicePath) shellExec(`kubectl apply -f ${grpcServicePath} -n ${options.namespace}`);
|
|
1056
|
+
|
|
1057
|
+
const resolvedCmd = _cmd[env].map((c) =>
|
|
1058
|
+
c.replaceAll(
|
|
1059
|
+
'{{grpc-service-dns}}',
|
|
1060
|
+
`${deployId}-grpc-service-${env}-${parentTraffic}.${options.namespace || 'default'}.svc.cluster.local:50051`,
|
|
1061
|
+
),
|
|
1062
|
+
);
|
|
1063
|
+
|
|
944
1064
|
let deploymentYaml = `---
|
|
945
1065
|
${Underpost.deploy
|
|
946
1066
|
.deploymentYamlPartsFactory({
|
|
@@ -952,7 +1072,7 @@ ${Underpost.deploy
|
|
|
952
1072
|
image: _image,
|
|
953
1073
|
namespace: options.namespace,
|
|
954
1074
|
volumes: _volumes,
|
|
955
|
-
cmd:
|
|
1075
|
+
cmd: resolvedCmd,
|
|
956
1076
|
})
|
|
957
1077
|
.replace('{{ports}}', buildKindPorts(_fromPort, _toPort))}
|
|
958
1078
|
`;
|
|
@@ -998,7 +1118,7 @@ EOF
|
|
|
998
1118
|
* @memberof UnderpostRun
|
|
999
1119
|
*/
|
|
1000
1120
|
'ls-deployments': async (path, options = DEFAULT_OPTION) => {
|
|
1001
|
-
console.table(await Underpost.
|
|
1121
|
+
console.table(await Underpost.kubectl.get(path, 'deployments', options.namespace));
|
|
1002
1122
|
},
|
|
1003
1123
|
|
|
1004
1124
|
/**
|
|
@@ -1092,15 +1212,13 @@ EOF
|
|
|
1092
1212
|
'db-client': async (path, options = DEFAULT_OPTION) => {
|
|
1093
1213
|
const { underpostRoot } = options;
|
|
1094
1214
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
}
|
|
1102
|
-
// For kubeadm/k3s, ensure it's available for containerd
|
|
1103
|
-
shellExec(`sudo crictl pull ${image}`);
|
|
1215
|
+
Underpost.image.pullDockerHubImage({
|
|
1216
|
+
dockerhubImage: 'adminer',
|
|
1217
|
+
version: '4.7.6-standalone',
|
|
1218
|
+
kind: options.kind,
|
|
1219
|
+
kubeadm: options.kubeadm,
|
|
1220
|
+
k3s: options.k3s,
|
|
1221
|
+
});
|
|
1104
1222
|
|
|
1105
1223
|
shellExec(`kubectl delete deployment adminer -n ${options.namespace} --ignore-not-found`);
|
|
1106
1224
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/deployment/adminer/. -n ${options.namespace}`);
|
|
@@ -1573,13 +1691,73 @@ EOF
|
|
|
1573
1691
|
},
|
|
1574
1692
|
|
|
1575
1693
|
/**
|
|
1576
|
-
* @method
|
|
1694
|
+
* @method pid-info
|
|
1695
|
+
* @description Displays detailed information about a process by PID, including service details, command line, executable path, working directory, environment variables, and parent process tree.
|
|
1696
|
+
* @param {string} path - The PID of the process to inspect.
|
|
1697
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1698
|
+
* @memberof UnderpostRun
|
|
1699
|
+
*/
|
|
1700
|
+
'pid-info': (path, options = DEFAULT_OPTION) => {
|
|
1701
|
+
const pid = path;
|
|
1702
|
+
if (!pid) {
|
|
1703
|
+
logger.error('PID is required. Usage: underpost run pid-info <pid>');
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// Services
|
|
1708
|
+
logger.info('Process info');
|
|
1709
|
+
shellExec(`sudo ps -p ${pid} -o pid,ppid,user,stime,etime,cmd`);
|
|
1710
|
+
logger.info('Command line');
|
|
1711
|
+
shellExec(`sudo cat /proc/${pid}/cmdline | tr '\\0' ' ' ; echo`);
|
|
1712
|
+
logger.info('Executable path');
|
|
1713
|
+
shellExec(`sudo readlink -f /proc/${pid}/exe`);
|
|
1714
|
+
logger.info('Working directory');
|
|
1715
|
+
shellExec(`sudo readlink -f /proc/${pid}/cwd`);
|
|
1716
|
+
logger.info('Environment variables (first 200)');
|
|
1717
|
+
shellExec(`sudo tr '\\0' '\\n' </proc/${pid}/environ | head -200`);
|
|
1718
|
+
|
|
1719
|
+
// Parent
|
|
1720
|
+
logger.info('Parent process');
|
|
1721
|
+
const parentInfo = shellExec(`sudo ps -o pid,ppid,user,cmd -p ${pid}`, { stdout: true, silent: true });
|
|
1722
|
+
console.log(parentInfo);
|
|
1723
|
+
const ppidMatch = parentInfo.split('\n').find((l) => l.trim().startsWith(pid));
|
|
1724
|
+
if (ppidMatch) {
|
|
1725
|
+
const ppid = ppidMatch.trim().split(/\s+/)[1];
|
|
1726
|
+
logger.info(`Parent PID: ${ppid}`);
|
|
1727
|
+
shellExec(`ps -fp ${ppid}`);
|
|
1728
|
+
}
|
|
1729
|
+
logger.info('Process tree');
|
|
1730
|
+
shellExec(`pstree -s ${pid}`);
|
|
1731
|
+
},
|
|
1732
|
+
|
|
1733
|
+
/**
|
|
1734
|
+
* @method background
|
|
1735
|
+
* @description Runs a custom command in the background using nohup, logging output to `/var/log/<id>.log` and saving the PID to `/var/run/<id>.pid`.
|
|
1736
|
+
* @param {string} path - The command to run in the background (e.g. 'npm run prod:container dd-cyberia-r3').
|
|
1737
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1738
|
+
* @memberof UnderpostRun
|
|
1739
|
+
*/
|
|
1740
|
+
background: (path, options = DEFAULT_OPTION) => {
|
|
1741
|
+
if (!path) {
|
|
1742
|
+
logger.error('Command is required. Usage: underpost run background <command>');
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
const id = path.split(/\s+/).pop();
|
|
1746
|
+
const logFile = `/var/log/${id}.log`;
|
|
1747
|
+
const pidFile = `/var/run/${id}.pid`;
|
|
1748
|
+
logger.info(`Starting background process`, { id, logFile, pidFile });
|
|
1749
|
+
shellExec(`nohup ${path} > ${logFile} 2>&1 & pid=$!; echo $pid > ${pidFile}; disown`);
|
|
1750
|
+
logger.info(`Background process started for '${id}'`);
|
|
1751
|
+
},
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* @method ports
|
|
1577
1755
|
* @description Set on ~/.bashrc alias: ports <port> Command to list listening ports that match the given keyword.
|
|
1578
1756
|
* @param {string} path - The input value, identifier, or path for the operation (used as a keyword to filter listening ports).
|
|
1579
1757
|
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1580
1758
|
* @memberof UnderpostRun
|
|
1581
1759
|
*/
|
|
1582
|
-
|
|
1760
|
+
ports: async (path = '', options = DEFAULT_OPTION) => {
|
|
1583
1761
|
shellExec(`chmod +x ${options.underpostRoot}/scripts/ports-ls.sh`);
|
|
1584
1762
|
shellExec(`${options.underpostRoot}/scripts/ports-ls.sh`);
|
|
1585
1763
|
},
|
|
@@ -1634,7 +1812,7 @@ EOF
|
|
|
1634
1812
|
|
|
1635
1813
|
const { close } = await (async () => {
|
|
1636
1814
|
const checkAwaitPath = '/await';
|
|
1637
|
-
while (!Underpost.
|
|
1815
|
+
while (!Underpost.kubectl.existsFile({ podName, path: checkAwaitPath })) {
|
|
1638
1816
|
logger.info('monitor', checkAwaitPath);
|
|
1639
1817
|
await timer(1000);
|
|
1640
1818
|
}
|
|
@@ -1661,7 +1839,7 @@ EOF
|
|
|
1661
1839
|
logger.info('monitor', checkPath);
|
|
1662
1840
|
{
|
|
1663
1841
|
const checkAwaitPath = `/home/dd/docs${checkPath}`;
|
|
1664
|
-
while (!Underpost.
|
|
1842
|
+
while (!Underpost.kubectl.existsFile({ podName, path: checkAwaitPath })) {
|
|
1665
1843
|
logger.info('waiting for', checkAwaitPath);
|
|
1666
1844
|
await timer(1000);
|
|
1667
1845
|
}
|
|
@@ -1727,7 +1905,8 @@ EOF
|
|
|
1727
1905
|
|
|
1728
1906
|
shellCd(dir);
|
|
1729
1907
|
|
|
1730
|
-
|
|
1908
|
+
Underpost.repo.initLocalRepo({ path: dir });
|
|
1909
|
+
shellExec(`git add . && git commit -m "Base implementation"`);
|
|
1731
1910
|
shellExec(`chmod +x ./replace_params.sh`);
|
|
1732
1911
|
shellExec(`chmod +x ./build.sh`);
|
|
1733
1912
|
|
|
@@ -1772,11 +1951,46 @@ EOF
|
|
|
1772
1951
|
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1773
1952
|
* @memberof UnderpostRun
|
|
1774
1953
|
*/
|
|
1954
|
+
/**
|
|
1955
|
+
* @method generate-pass
|
|
1956
|
+
* @description Generates a cryptographically secure random password that satisfies all validatePassword
|
|
1957
|
+
* constraints (lowercase, uppercase, digit, special char, min 8 chars). Logs the plain password
|
|
1958
|
+
* to the console or, when `--copy` is set, copies it to the clipboard via pbcopy.
|
|
1959
|
+
* @param {string} path - Optional password length (default: 16).
|
|
1960
|
+
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
1961
|
+
* @param {boolean} options.copy - When true, copies to clipboard instead of logging.
|
|
1962
|
+
* @memberof UnderpostRun
|
|
1963
|
+
*/
|
|
1964
|
+
'generate-pass': (path, options = DEFAULT_OPTION) => {
|
|
1965
|
+
const length = path && parseInt(path) > 0 ? parseInt(path) : 16;
|
|
1966
|
+
const lower = 'abcdefghijklmnopqrstuvwxyz';
|
|
1967
|
+
const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
1968
|
+
const digits = '0123456789';
|
|
1969
|
+
const special = '@#$%^&*()_+';
|
|
1970
|
+
const all = lower + upper + digits + special;
|
|
1971
|
+
const buf = crypto.randomBytes(length + 4);
|
|
1972
|
+
// Guarantee at least one character from each required class
|
|
1973
|
+
const chars = [
|
|
1974
|
+
lower[buf[0] % lower.length],
|
|
1975
|
+
upper[buf[1] % upper.length],
|
|
1976
|
+
digits[buf[2] % digits.length],
|
|
1977
|
+
special[buf[3] % special.length],
|
|
1978
|
+
];
|
|
1979
|
+
for (let i = 4; i < length; i++) chars.push(all[buf[i] % all.length]);
|
|
1980
|
+
// Fisher-Yates shuffle using an independent random buffer
|
|
1981
|
+
const shuf = crypto.randomBytes(length);
|
|
1982
|
+
for (let i = chars.length - 1; i > 0; i--) {
|
|
1983
|
+
const j = shuf[i % shuf.length] % (i + 1);
|
|
1984
|
+
[chars[i], chars[j]] = [chars[j], chars[i]];
|
|
1985
|
+
}
|
|
1986
|
+
const password = chars.join('');
|
|
1987
|
+
if (options.copy) pbcopy(password);
|
|
1988
|
+
else console.log(password);
|
|
1989
|
+
},
|
|
1990
|
+
|
|
1775
1991
|
secret: (path, options = DEFAULT_OPTION) => {
|
|
1776
1992
|
const secretPath = path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`;
|
|
1777
|
-
const command =
|
|
1778
|
-
? `node bin secret underpost --create-from-file ${secretPath}`
|
|
1779
|
-
: `underpost secret underpost --create-from-file ${secretPath}`;
|
|
1993
|
+
const command = `node bin secret underpost --create-from-file ${secretPath}`;
|
|
1780
1994
|
shellExec(command);
|
|
1781
1995
|
},
|
|
1782
1996
|
/**
|
package/src/cli/test.js
CHANGED
|
@@ -81,7 +81,7 @@ class UnderpostTest {
|
|
|
81
81
|
return await Underpost.test.statusMonitor(options.podName, options.podStatus, options.kindType);
|
|
82
82
|
|
|
83
83
|
if (options.sh === true || options.logs === true) {
|
|
84
|
-
const [pod] = Underpost.
|
|
84
|
+
const [pod] = Underpost.kubectl.get(deployList);
|
|
85
85
|
if (pod) {
|
|
86
86
|
if (options.sh) return pbcopy(`sudo kubectl exec -it ${pod.NAME} -- sh`);
|
|
87
87
|
if (options.logs) return shellExec(`sudo kubectl logs -f ${pod.NAME}`);
|
|
@@ -115,7 +115,7 @@ class UnderpostTest {
|
|
|
115
115
|
break;
|
|
116
116
|
}
|
|
117
117
|
else {
|
|
118
|
-
const pods = Underpost.
|
|
118
|
+
const pods = Underpost.kubectl.get(deployId);
|
|
119
119
|
if (pods.length > 0)
|
|
120
120
|
for (const deployData of pods) {
|
|
121
121
|
const { NAME } = deployData;
|
|
@@ -145,7 +145,7 @@ class UnderpostTest {
|
|
|
145
145
|
logger.info(`Loading instance`, { podName, status, kindType, deltaMs, maxAttempts });
|
|
146
146
|
const _monitor = async () => {
|
|
147
147
|
await timer(deltaMs);
|
|
148
|
-
const pods = Underpost.
|
|
148
|
+
const pods = Underpost.kubectl.get(podName, kindType);
|
|
149
149
|
let result = pods.find((p) => p.STATUS === status || (status === 'Running' && p.STATUS === 'Completed'));
|
|
150
150
|
logger.info(
|
|
151
151
|
`Testing pod ${podName}... ${result ? 1 : 0}/1 - elapsed time ${deltaMs * (index + 1)}s - attempt ${
|
|
@@ -11,10 +11,9 @@ import { RouterDefault } from './components/default/RoutesDefault.js';
|
|
|
11
11
|
import { TranslateDefault } from './components/default/TranslateDefault.js';
|
|
12
12
|
import { Worker } from './components/core/Worker.js';
|
|
13
13
|
import { Keyboard } from './components/core/Keyboard.js';
|
|
14
|
-
import { DefaultParams } from './components/default/CommonDefault.js';
|
|
15
14
|
import { SocketIo } from './components/core/SocketIo.js';
|
|
16
15
|
import { SocketIoDefault } from './components/default/SocketIoDefault.js';
|
|
17
|
-
import {
|
|
16
|
+
import { AppStoreDefault } from './components/default/AppStoreDefault.js';
|
|
18
17
|
import { CssDefaultDark, CssDefaultLight } from './components/default/CssDefault.js';
|
|
19
18
|
import { EventsUI } from './components/core/EventsUI.js';
|
|
20
19
|
import { Modal } from './components/core/Modal.js';
|
|
@@ -71,14 +70,14 @@ window.onload = () =>
|
|
|
71
70
|
await Responsive.Init();
|
|
72
71
|
await MenuDefault.Render({ htmlMainBody });
|
|
73
72
|
await SocketIo.Init({
|
|
74
|
-
channels:
|
|
73
|
+
channels: AppStoreDefault.Data,
|
|
75
74
|
path: `/`,
|
|
76
75
|
});
|
|
77
76
|
await SocketIoDefault.Init();
|
|
78
77
|
await LogInDefault();
|
|
79
78
|
await LogOutDefault();
|
|
80
79
|
await SignUpDefault();
|
|
81
|
-
await Keyboard.Init(
|
|
80
|
+
await Keyboard.Init();
|
|
82
81
|
await Modal.RenderSeoSanitizer();
|
|
83
82
|
},
|
|
84
83
|
});
|
|
@@ -49,7 +49,7 @@ const e404 = async () => {
|
|
|
49
49
|
<br />
|
|
50
50
|
<br />${Translate.Render('page-not-found')} <br />
|
|
51
51
|
<br />
|
|
52
|
-
<a href="${location.origin}">${Translate.Render('back')}</a>
|
|
52
|
+
<a target="_top" href="${location.origin}">${Translate.Render('back')}</a>
|
|
53
53
|
</div>`;
|
|
54
54
|
};
|
|
55
55
|
|
|
@@ -68,7 +68,7 @@ const e500 = async () => {
|
|
|
68
68
|
<br />
|
|
69
69
|
<br />${Translate.Render('page-broken')} <br />
|
|
70
70
|
<br />
|
|
71
|
-
<a href="${location.origin}">${Translate.Render('back')}</a>
|
|
71
|
+
<a target="_top" href="${location.origin}">${Translate.Render('back')}</a>
|
|
72
72
|
</div>`;
|
|
73
73
|
};
|
|
74
74
|
|