cyberia 3.2.9 → 3.2.22
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/.github/workflows/engine-cyberia.cd.yml +7 -0
- package/.github/workflows/engine-cyberia.ci.yml +14 -2
- package/.github/workflows/ghpkg.ci.yml +1 -0
- package/.github/workflows/npmpkg.ci.yml +10 -5
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -0
- package/.vscode/extensions.json +9 -9
- package/.vscode/settings.json +20 -4
- package/CHANGELOG.md +363 -1
- package/CLI-HELP.md +975 -1061
- package/README.md +190 -348
- package/bin/build.js +102 -125
- package/bin/build.template.js +33 -0
- package/bin/cyberia.js +238 -56
- package/bin/deploy.js +16 -3
- package/bin/index.js +238 -56
- package/bump.config.js +26 -0
- package/conf.js +131 -24
- package/deployment.yaml +76 -2
- package/hardhat/package-lock.json +113 -144
- package/hardhat/package.json +4 -3
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +76 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/kind-config-dev.yaml +8 -0
- package/manifests/lxd/lxd-admin-profile.yaml +12 -3
- package/manifests/mongodb/pv-pvc.yaml +44 -8
- package/manifests/mongodb/statefulset.yaml +55 -68
- package/manifests/mongodb-4.4/headless-service.yaml +10 -0
- package/manifests/mongodb-4.4/kustomization.yaml +3 -1
- package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
- package/manifests/mongodb-4.4/statefulset.yaml +79 -0
- package/manifests/mongodb-4.4/storage-class.yaml +9 -0
- package/manifests/valkey/statefulset.yaml +1 -1
- package/manifests/valkey/valkey-nodeport.yaml +17 -0
- package/package.json +31 -19
- package/scripts/ipxe-setup.sh +52 -49
- package/scripts/k3s-node-setup.sh +81 -46
- package/scripts/link-local-underpost-cli.sh +6 -0
- package/scripts/lxd-vm-setup.sh +193 -8
- package/scripts/maas-nat-firewalld.sh +145 -0
- package/scripts/test-monitor.sh +250 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
- package/src/api/core/core.router.js +19 -14
- package/src/api/core/core.service.js +5 -5
- package/src/api/crypto/crypto.router.js +18 -12
- package/src/api/crypto/crypto.service.js +3 -3
- package/src/api/cyberia-action/cyberia-action.model.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +22 -18
- package/src/api/cyberia-action/cyberia-action.service.js +5 -5
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
- package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
- package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
- package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
- package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
- package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
- package/src/api/cyberia-map/cyberia-map.router.js +35 -30
- package/src/api/cyberia-map/cyberia-map.service.js +7 -7
- package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +458 -0
- package/src/api/default/default.router.js +22 -18
- package/src/api/default/default.service.js +5 -5
- package/src/api/document/document.router.js +28 -23
- package/src/api/document/document.service.js +100 -23
- package/src/api/file/file.router.js +19 -13
- package/src/api/file/file.service.js +9 -7
- package/src/api/instance/instance.router.js +29 -24
- package/src/api/instance/instance.service.js +6 -6
- package/src/api/ipfs/ipfs.router.js +21 -16
- package/src/api/ipfs/ipfs.service.js +8 -8
- package/src/api/object-layer/object-layer.router.js +512 -507
- package/src/api/object-layer/object-layer.service.js +17 -14
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
- package/src/api/test/test.router.js +17 -12
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +5 -4
- package/src/api/user/user.router.js +297 -288
- package/src/api/user/user.service.js +100 -35
- package/src/cli/baremetal.js +132 -101
- package/src/cli/cluster.js +700 -232
- package/src/cli/db.js +59 -60
- package/src/cli/deploy.js +291 -294
- package/src/cli/env.js +1 -4
- package/src/cli/fs.js +13 -3
- package/src/cli/image.js +58 -4
- package/src/cli/index.js +127 -15
- package/src/cli/ipfs.js +4 -6
- package/src/cli/kubectl.js +4 -1
- package/src/cli/lxd.js +1099 -223
- package/src/cli/monitor.js +396 -9
- package/src/cli/release.js +355 -146
- package/src/cli/repository.js +169 -30
- package/src/cli/run.js +347 -117
- package/src/cli/secrets.js +11 -2
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +5 -0
- package/src/client/components/core/ClientEvents.js +76 -0
- package/src/client/components/core/EventBus.js +4 -0
- package/src/client/components/core/Modal.js +82 -41
- package/src/client/components/core/PanelForm.js +14 -10
- package/src/client/components/core/Worker.js +162 -363
- package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +55 -1
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +223 -361
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +212 -185
- package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
- package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +23 -1
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
- package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
- package/src/client/sw/core.sw.js +174 -112
- package/src/db/DataBaseProvider.js +115 -15
- package/src/db/mariadb/MariaDB.js +2 -1
- package/src/db/mongo/MongoBootstrap.js +657 -0
- package/src/db/mongo/MongooseDB.js +130 -21
- package/src/grpc/cyberia/grpc-server.js +25 -57
- package/src/index.js +1 -1
- package/src/runtime/cyberia-client/Dockerfile +10 -7
- package/src/runtime/cyberia-client/Dockerfile.dev +67 -0
- package/src/runtime/cyberia-server/Dockerfile +11 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +47 -0
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Dockerfile +3 -3
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/catalog-underpost.js +61 -0
- package/src/server/catalog.js +77 -0
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +496 -135
- package/src/server/ipfs-client.js +5 -3
- package/src/server/process.js +180 -19
- package/src/server/proxy.js +9 -2
- package/src/server/runtime-status.js +235 -0
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +44 -11
- package/src/server/valkey.js +2 -0
- package/src/ws/IoInterface.js +16 -16
- package/src/ws/core/channels/core.ws.chat.js +11 -11
- package/src/ws/core/channels/core.ws.mailer.js +29 -29
- package/src/ws/core/channels/core.ws.stream.js +19 -19
- package/src/ws/core/core.ws.connection.js +8 -8
- package/src/ws/core/core.ws.server.js +6 -5
- package/src/ws/default/channels/default.ws.main.js +10 -10
- package/src/ws/default/default.ws.connection.js +4 -4
- package/src/ws/default/default.ws.server.js +4 -3
- package/test/deploy-monitor.test.js +251 -0
- package/bin/file.js +0 -202
- package/bin/vs.js +0 -74
- package/bin/zed.js +0 -84
- package/manifests/deployment/dd-test-development/deployment.yaml +0 -254
- package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
- package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
- package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
- package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
- package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
- /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
- /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
- /package/src/client/ssr/{pages → views}/Test.js +0 -0
package/src/cli/run.js
CHANGED
|
@@ -10,9 +10,12 @@ import {
|
|
|
10
10
|
awaitDeployMonitor,
|
|
11
11
|
buildKindPorts,
|
|
12
12
|
Config,
|
|
13
|
+
cronDeployIdResolve,
|
|
14
|
+
etcHostFactory,
|
|
13
15
|
getNpmRootPath,
|
|
14
16
|
isDeployRunnerContext,
|
|
15
17
|
loadConfServerJson,
|
|
18
|
+
loadReplicas,
|
|
16
19
|
writeEnv,
|
|
17
20
|
} from '../server/conf.js';
|
|
18
21
|
import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
@@ -24,6 +27,7 @@ import { range, setPad, timer } from '../client/components/core/CommonJs.js';
|
|
|
24
27
|
import os from 'os';
|
|
25
28
|
import Underpost from '../index.js';
|
|
26
29
|
import dotenv from 'dotenv';
|
|
30
|
+
import { MongoBootstrap } from '../db/mongo/MongoBootstrap.js';
|
|
27
31
|
|
|
28
32
|
const waitForPort = (port, host = '127.0.0.1', { maxAttempts = 30, interval = 2000 } = {}) =>
|
|
29
33
|
new Promise((resolve, reject) => {
|
|
@@ -97,6 +101,7 @@ const logger = loggerFactory(import.meta);
|
|
|
97
101
|
* @property {string} deployId - The deployment ID.
|
|
98
102
|
* @property {string} instanceId - The instance ID.
|
|
99
103
|
* @property {string} user - The user to run as.
|
|
104
|
+
* @property {string} group - The group to use.
|
|
100
105
|
* @property {string} pid - The process ID.
|
|
101
106
|
* @property {boolean} disablePrivateConfUpdate - Whether to disable private configuration updates.
|
|
102
107
|
* @property {string} monitorStatus - The monitor status option.
|
|
@@ -114,6 +119,8 @@ const logger = loggerFactory(import.meta);
|
|
|
114
119
|
* @property {boolean} copy - Whether to copy the command to the clipboard instead of executing it.
|
|
115
120
|
* @property {boolean} skipFullBuild - Whether to skip the full client bundle build during deployment (supported by: sync, template-deploy).
|
|
116
121
|
* @property {boolean} pullBundle - Whether to pull the bundle before running. Use together with --skip-full-build to skip the local build entirely (supported by: sync, template-deploy).
|
|
122
|
+
* @property {boolean} remove - Whether to remove/teardown resources instead of creating them (e.g. delete-expose for k3s proxy devices in dev-cluster).
|
|
123
|
+
* @property {boolean} test - Whether to enable test/generic-purpose mode (e.g. use self-signed TLS instead of cert-manager).
|
|
117
124
|
* @memberof UnderpostRun
|
|
118
125
|
*/
|
|
119
126
|
const DEFAULT_OPTION = {
|
|
@@ -165,6 +172,7 @@ const DEFAULT_OPTION = {
|
|
|
165
172
|
deployId: '',
|
|
166
173
|
instanceId: '',
|
|
167
174
|
user: '',
|
|
175
|
+
group: '',
|
|
168
176
|
pid: '',
|
|
169
177
|
disablePrivateConfUpdate: false,
|
|
170
178
|
monitorStatus: '',
|
|
@@ -180,6 +188,8 @@ const DEFAULT_OPTION = {
|
|
|
180
188
|
copy: false,
|
|
181
189
|
skipFullBuild: false,
|
|
182
190
|
pullBundle: false,
|
|
191
|
+
remove: false,
|
|
192
|
+
test: false,
|
|
183
193
|
};
|
|
184
194
|
|
|
185
195
|
/**
|
|
@@ -209,28 +219,39 @@ class UnderpostRun {
|
|
|
209
219
|
'dev-cluster': (path, options = DEFAULT_OPTION) => {
|
|
210
220
|
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
211
221
|
const mongoHosts = ['mongodb-0.mongodb-service'];
|
|
222
|
+
let primaryMongoHost = 'mongodb-0.mongodb-service';
|
|
212
223
|
if (!options.expose) {
|
|
213
224
|
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --reset`);
|
|
214
225
|
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''}`);
|
|
215
226
|
|
|
216
227
|
shellExec(
|
|
217
|
-
`${baseCommand} cluster${options.dev ? ' --dev' : ''} --mongodb --
|
|
228
|
+
`${baseCommand} cluster${options.dev ? ' --dev' : ''} --mongodb --service-host ${mongoHosts.join(
|
|
218
229
|
',',
|
|
219
230
|
)} --pull-image`,
|
|
220
231
|
);
|
|
221
232
|
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --valkey --pull-image`);
|
|
222
233
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
234
|
+
if (options.k3s) {
|
|
235
|
+
if (options.remove) {
|
|
236
|
+
shellExec(`${baseCommand} lxd --delete-expose k3s-control:27017`);
|
|
237
|
+
shellExec(`${baseCommand} lxd --delete-expose k3s-control:6379`);
|
|
238
|
+
} else {
|
|
239
|
+
shellExec(`${baseCommand} lxd --expose k3s-control:27017 --node-port 32017`);
|
|
240
|
+
shellExec(`${baseCommand} lxd --expose k3s-control:6379 --node-port 32079`);
|
|
241
|
+
}
|
|
242
|
+
shellExec(`lxc config device show k3s-control`);
|
|
243
|
+
} else {
|
|
227
244
|
try {
|
|
228
|
-
const primaryPodName =
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
245
|
+
const primaryPodName =
|
|
246
|
+
MongoBootstrap.getPrimaryPodName({
|
|
247
|
+
namespace: options.namespace,
|
|
248
|
+
podName: 'mongodb-0',
|
|
249
|
+
disableAuth: options.dev,
|
|
250
|
+
}) || 'mongodb-0';
|
|
251
|
+
shellExec(
|
|
252
|
+
`${baseCommand} deploy --expose --namespace ${options.namespace} --disable-update-underpost-config mongo`,
|
|
253
|
+
{ async: true },
|
|
254
|
+
);
|
|
234
255
|
shellExec(
|
|
235
256
|
`${baseCommand} deploy --expose --namespace ${options.namespace} --disable-update-underpost-config valkey`,
|
|
236
257
|
{ async: true },
|
|
@@ -241,10 +262,19 @@ class UnderpostRun {
|
|
|
241
262
|
default: primaryMongoHost,
|
|
242
263
|
});
|
|
243
264
|
}
|
|
244
|
-
|
|
245
|
-
const hostListenResult = Underpost.deploy.etcHostFactory([primaryMongoHost]);
|
|
246
|
-
logger.info(hostListenResult.renderHosts);
|
|
247
265
|
}
|
|
266
|
+
const hostListenResult = etcHostFactory([primaryMongoHost]);
|
|
267
|
+
logger.info(hostListenResult.renderHosts);
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @method etc-hosts
|
|
272
|
+
* @description Modifies the `/etc/hosts` file to add entries for local access to services,
|
|
273
|
+
* based on the provided path input.
|
|
274
|
+
* @param {string} path - The input value, identifier, or path for the operation (used to specify the entries to add to /etc/hosts).
|
|
275
|
+
*/
|
|
276
|
+
'etc-hosts': (path = '', options = DEFAULT_OPTION) => {
|
|
277
|
+
etcHostFactory(path.split(','));
|
|
248
278
|
},
|
|
249
279
|
|
|
250
280
|
/**
|
|
@@ -405,6 +435,7 @@ class UnderpostRun {
|
|
|
405
435
|
return;
|
|
406
436
|
}
|
|
407
437
|
shellExec(`${baseCommand} run pull`);
|
|
438
|
+
shellExec(`${baseCommand} run shared-dir`);
|
|
408
439
|
|
|
409
440
|
// Capture last N commit messages for propagation.
|
|
410
441
|
// When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
|
|
@@ -485,6 +516,7 @@ class UnderpostRun {
|
|
|
485
516
|
return;
|
|
486
517
|
}
|
|
487
518
|
shellExec(`${baseCommand} run pull`);
|
|
519
|
+
shellExec(`${baseCommand} run shared-dir`);
|
|
488
520
|
|
|
489
521
|
// Capture last N commit messages from the engine repo.
|
|
490
522
|
// When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
|
|
@@ -505,14 +537,16 @@ class UnderpostRun {
|
|
|
505
537
|
},
|
|
506
538
|
/**
|
|
507
539
|
* @method docker-image
|
|
508
|
-
* @description Dispatches the Docker image CI workflow (`docker-image.ci.yml`)
|
|
509
|
-
*
|
|
540
|
+
* @description Dispatches the Docker image CI workflow (`docker-image[.<runtime>].ci.yml`) via `workflow_dispatch`.
|
|
541
|
+
* Repository resolution is delegated to `Underpost.repo.resolveInstanceRepo(path)`.
|
|
542
|
+
* @param {string} path - Optional runtime / workflow suffix (e.g. `cyberia-server`, `cyberia-client`).
|
|
510
543
|
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
511
544
|
* @memberof UnderpostRun
|
|
512
545
|
*/
|
|
513
546
|
'docker-image': (path, options = DEFAULT_OPTION) => {
|
|
547
|
+
const repo = Underpost.repo.resolveInstanceRepo(path);
|
|
514
548
|
Underpost.repo.dispatchWorkflow({
|
|
515
|
-
repo
|
|
549
|
+
repo,
|
|
516
550
|
workflowFile: `docker-image${path ? `.${path}` : ''}.ci.yml`,
|
|
517
551
|
ref: 'master',
|
|
518
552
|
inputs: {},
|
|
@@ -527,6 +561,7 @@ class UnderpostRun {
|
|
|
527
561
|
*/
|
|
528
562
|
clean: (path = '', options = DEFAULT_OPTION) => {
|
|
529
563
|
Underpost.repo.clean({ paths: path ? path.split(',') : ['/home/dd/engine', '/home/dd/engine/engine-private'] });
|
|
564
|
+
if (options.dev) shellExec(`node bin run shared-dir ${path ? path : '/home/dd/engine'}`);
|
|
530
565
|
},
|
|
531
566
|
/**
|
|
532
567
|
* @method pull
|
|
@@ -536,16 +571,14 @@ class UnderpostRun {
|
|
|
536
571
|
* @memberof UnderpostRun
|
|
537
572
|
*/
|
|
538
573
|
pull: (path, options = DEFAULT_OPTION) => {
|
|
574
|
+
// shellExec is fail-fast by default — any non-zero exit throws and
|
|
575
|
+
// propagates up to the workflow step. No per-call flag required.
|
|
539
576
|
if (!fs.existsSync(`/home/dd`) || !fs.existsSync(`/home/dd/engine`)) {
|
|
540
577
|
fs.mkdirSync(`/home/dd`, { recursive: true });
|
|
541
|
-
shellExec(`cd /home/dd && underpost clone ${process.env.GITHUB_USERNAME}/engine`, {
|
|
542
|
-
silent: true,
|
|
543
|
-
});
|
|
578
|
+
shellExec(`cd /home/dd && underpost clone ${process.env.GITHUB_USERNAME}/engine`, { silent: true });
|
|
544
579
|
} else {
|
|
545
580
|
shellExec(`underpost run clean`);
|
|
546
|
-
shellExec(`cd /home/dd/engine && underpost pull . ${process.env.GITHUB_USERNAME}/engine`, {
|
|
547
|
-
silent: true,
|
|
548
|
-
});
|
|
581
|
+
shellExec(`cd /home/dd/engine && underpost pull . ${process.env.GITHUB_USERNAME}/engine`, { silent: true });
|
|
549
582
|
}
|
|
550
583
|
if (!fs.existsSync(`/home/dd/engine/engine-private`))
|
|
551
584
|
shellExec(`cd /home/dd/engine && underpost clone ${process.env.GITHUB_USERNAME}/engine-private`, {
|
|
@@ -554,9 +587,7 @@ class UnderpostRun {
|
|
|
554
587
|
else
|
|
555
588
|
shellExec(
|
|
556
589
|
`cd /home/dd/engine/engine-private && underpost pull . ${process.env.GITHUB_USERNAME}/engine-private`,
|
|
557
|
-
{
|
|
558
|
-
silent: true,
|
|
559
|
-
},
|
|
590
|
+
{ silent: true },
|
|
560
591
|
);
|
|
561
592
|
},
|
|
562
593
|
/**
|
|
@@ -643,6 +674,11 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
643
674
|
/**
|
|
644
675
|
* @method sync
|
|
645
676
|
* @description Cleans up, and then runs a deployment synchronization command (`underpost deploy --kubeadm --build-manifest --sync...`) using parameters parsed from `path` (deployId, replicas, versions, image, node).
|
|
677
|
+
*
|
|
678
|
+
* Forwards `--image-pull-policy <policy>` to the underlying `deploy --build-manifest` invocation when `options.imagePullPolicy` is set,
|
|
679
|
+
* which then plumbs through `buildManifest` and `deploymentYamlPartsFactory` to override the container `imagePullPolicy` in the generated
|
|
680
|
+
* `deployment.yaml`. Useful when you want to force `Always` so the kubelet re-pulls a mutable tag on every rollout. Example:
|
|
681
|
+
* `node bin run sync dd-core --kubeadm --image-pull-policy Always`
|
|
646
682
|
* @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string containing deploy parameters).
|
|
647
683
|
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
648
684
|
* @memberof UnderpostRun
|
|
@@ -692,6 +728,11 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
692
728
|
let targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'green';
|
|
693
729
|
if (targetTraffic) versions = versions ? versions : targetTraffic;
|
|
694
730
|
|
|
731
|
+
const ignorePods =
|
|
732
|
+
isDeployRunnerContext(path, options) && targetTraffic
|
|
733
|
+
? Underpost.kubectl.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace).map((p) => p.NAME)
|
|
734
|
+
: [];
|
|
735
|
+
|
|
695
736
|
const timeoutFlags = Underpost.deploy.timeoutFlagsFactory(options);
|
|
696
737
|
const cmdString = options.cmd
|
|
697
738
|
? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
|
|
@@ -700,13 +741,14 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
700
741
|
|
|
701
742
|
const skipFullBuildFlag = options.skipFullBuild ? ' --skip-full-build' : '';
|
|
702
743
|
const pullBundleFlag = options.pullBundle ? ' --pull-bundle' : '';
|
|
744
|
+
const imagePullPolicyFlag = options.imagePullPolicy ? ` --image-pull-policy ${options.imagePullPolicy}` : '';
|
|
703
745
|
|
|
704
746
|
shellExec(
|
|
705
747
|
`${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
706
748
|
image ? ` --image ${image}` : ''
|
|
707
749
|
}${versions ? ` --versions ${versions}` : ''}${
|
|
708
750
|
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
709
|
-
}${timeoutFlags}${cmdString}${gitCleanFlag}${skipFullBuildFlag}${pullBundleFlag} ${deployId} ${env}`,
|
|
751
|
+
}${timeoutFlags}${cmdString}${gitCleanFlag}${skipFullBuildFlag}${pullBundleFlag}${imagePullPolicyFlag} ${deployId} ${env}`,
|
|
710
752
|
);
|
|
711
753
|
|
|
712
754
|
if (isDeployRunnerContext(path, options)) {
|
|
@@ -717,11 +759,11 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
717
759
|
shellExec(
|
|
718
760
|
`${baseCommand} deploy${clusterFlag}${cmdString} --replicas ${replicas} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
|
|
719
761
|
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
720
|
-
}${timeoutFlags}${gitCleanFlag}`,
|
|
762
|
+
}${timeoutFlags}${gitCleanFlag}${imagePullPolicyFlag}`,
|
|
721
763
|
);
|
|
722
764
|
if (!targetTraffic)
|
|
723
765
|
targetTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
724
|
-
await Underpost.
|
|
766
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods, options.namespace);
|
|
725
767
|
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, replicas, options.namespace, options);
|
|
726
768
|
} else
|
|
727
769
|
logger.info('current traffic', Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }));
|
|
@@ -967,8 +1009,19 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
967
1009
|
// pathRewritePolicy,
|
|
968
1010
|
});
|
|
969
1011
|
if (options.tls) {
|
|
970
|
-
|
|
971
|
-
|
|
1012
|
+
if (options.test) {
|
|
1013
|
+
const sslDir = `./engine-private/ssl/${_host}`;
|
|
1014
|
+
const nameSafe = _host.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
1015
|
+
fs.mkdirpSync(sslDir);
|
|
1016
|
+
shellExec(`bash ./scripts/ssl.sh "${sslDir}" "${_host}"`);
|
|
1017
|
+
shellExec(`kubectl delete secret ${_host} -n ${options.namespace} --ignore-not-found`);
|
|
1018
|
+
shellExec(
|
|
1019
|
+
`kubectl create secret tls ${_host} --cert="${sslDir}/${nameSafe}.pem" --key="${sslDir}/${nameSafe}-key.pem" -n ${options.namespace}`,
|
|
1020
|
+
);
|
|
1021
|
+
} else {
|
|
1022
|
+
shellExec(`sudo kubectl delete Certificate ${_host} -n ${options.namespace} --ignore-not-found`);
|
|
1023
|
+
proxyYaml += Underpost.deploy.buildCertManagerCertificate({ ...options, host: _host });
|
|
1024
|
+
}
|
|
972
1025
|
}
|
|
973
1026
|
// console.log(proxyYaml);
|
|
974
1027
|
shellExec(`kubectl delete HTTPProxy ${_host} --namespace ${options.namespace} --ignore-not-found`);
|
|
@@ -1023,6 +1076,9 @@ EOF
|
|
|
1023
1076
|
cmd: _cmd,
|
|
1024
1077
|
volumes: _volumes,
|
|
1025
1078
|
metadata: _metadata,
|
|
1079
|
+
lifecycle: _lifecycle,
|
|
1080
|
+
readinessProbe: _readinessProbe,
|
|
1081
|
+
livenessProbe: _livenessProbe,
|
|
1026
1082
|
} = instance;
|
|
1027
1083
|
if (id !== _id) continue;
|
|
1028
1084
|
const _deployId = `${deployId}-${_id}`;
|
|
@@ -1034,6 +1090,7 @@ EOF
|
|
|
1034
1090
|
// Examples images:
|
|
1035
1091
|
// `underpost/underpost-engine:${Underpost.version}`
|
|
1036
1092
|
// `localhost/rockylinux9-underpost:${Underpost.version}`
|
|
1093
|
+
if (options.imageName) _image = options.imageName;
|
|
1037
1094
|
if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
|
|
1038
1095
|
|
|
1039
1096
|
if (_image && !_image.startsWith('localhost'))
|
|
@@ -1087,6 +1144,20 @@ EOF
|
|
|
1087
1144
|
),
|
|
1088
1145
|
);
|
|
1089
1146
|
|
|
1147
|
+
// Resolve env-scoped lifecycle/probe blocks: each can be either
|
|
1148
|
+
// { ...envObj } // shared shape
|
|
1149
|
+
// { development: {...}, production: {...} } // env-specific
|
|
1150
|
+
const pickEnv = (v) => (v && (v.development || v.production) ? v[env] : v);
|
|
1151
|
+
|
|
1152
|
+
// Convention: an instance config may place `imagePullPolicy` inside
|
|
1153
|
+
// the env-scoped lifecycle block (alongside postStart/preStop).
|
|
1154
|
+
// Extract it onto the container spec (where K8S expects it) and
|
|
1155
|
+
// strip it from the lifecycle hash so the rendered YAML stays valid.
|
|
1156
|
+
// CLI override (`--image-pull-policy`) wins over the conf value.
|
|
1157
|
+
const { lifecycle: lifecycleForManifest, imagePullPolicy: lifecycleImagePullPolicy } =
|
|
1158
|
+
Underpost.deploy.extractInstanceImagePullPolicy(pickEnv(_lifecycle));
|
|
1159
|
+
const instanceImagePullPolicy = options.imagePullPolicy || lifecycleImagePullPolicy;
|
|
1160
|
+
|
|
1090
1161
|
let deploymentYaml = `---
|
|
1091
1162
|
${Underpost.deploy
|
|
1092
1163
|
.deploymentYamlPartsFactory({
|
|
@@ -1099,6 +1170,11 @@ ${Underpost.deploy
|
|
|
1099
1170
|
namespace: options.namespace,
|
|
1100
1171
|
volumes: _volumes,
|
|
1101
1172
|
cmd: resolvedCmd,
|
|
1173
|
+
lifecycle: lifecycleForManifest,
|
|
1174
|
+
readinessProbe: pickEnv(_readinessProbe),
|
|
1175
|
+
livenessProbe: pickEnv(_livenessProbe),
|
|
1176
|
+
containerPort: _toPort,
|
|
1177
|
+
imagePullPolicy: instanceImagePullPolicy,
|
|
1102
1178
|
})
|
|
1103
1179
|
.replace('{{ports}}', buildKindPorts(_fromPort, _toPort))}
|
|
1104
1180
|
`;
|
|
@@ -1110,12 +1186,16 @@ EOF
|
|
|
1110
1186
|
`,
|
|
1111
1187
|
{ disableLog: true },
|
|
1112
1188
|
);
|
|
1113
|
-
|
|
1189
|
+
// Custom instances run a bare binary (no `underpost start` / internal
|
|
1190
|
+
// HTTP endpoint): Kubernetes readiness is the running signal and
|
|
1191
|
+
// container-status is read via exec. See `Deploy custom instance to K8S.md`.
|
|
1192
|
+
const { ready, readyPods } = await Underpost.monitor.monitorReadyRunner(
|
|
1114
1193
|
_deployId,
|
|
1115
1194
|
env,
|
|
1116
1195
|
targetTraffic,
|
|
1117
1196
|
ignorePods,
|
|
1118
1197
|
options.namespace,
|
|
1198
|
+
{ readyGate: 'kubernetes', statusTransport: 'exec' },
|
|
1119
1199
|
);
|
|
1120
1200
|
|
|
1121
1201
|
if (!ready) {
|
|
@@ -1125,12 +1205,12 @@ EOF
|
|
|
1125
1205
|
shellExec(
|
|
1126
1206
|
`${baseCommand} run${baseClusterCommand} --namespace ${options.namespace}` +
|
|
1127
1207
|
`${options.nodeName ? ` --node-name ${options.nodeName}` : ''}` +
|
|
1128
|
-
`${options.tls ? ` --tls` : ''}` +
|
|
1208
|
+
`${options.tls ? ` --tls ${options.test ? '--test' : ''}` : ''}` +
|
|
1129
1209
|
` instance-promote '${path}'`,
|
|
1130
1210
|
);
|
|
1131
1211
|
}
|
|
1132
1212
|
if (options.etcHosts) {
|
|
1133
|
-
const hostListenResult =
|
|
1213
|
+
const hostListenResult = etcHostFactory(etcHosts);
|
|
1134
1214
|
logger.info(hostListenResult.renderHosts);
|
|
1135
1215
|
}
|
|
1136
1216
|
},
|
|
@@ -1187,14 +1267,34 @@ EOF
|
|
|
1187
1267
|
volumes: _volumes,
|
|
1188
1268
|
metadata: _metadata,
|
|
1189
1269
|
runtime: _runtime,
|
|
1270
|
+
lifecycle: _lifecycle,
|
|
1271
|
+
readinessProbe: _readinessProbe,
|
|
1272
|
+
livenessProbe: _livenessProbe,
|
|
1190
1273
|
} = instance;
|
|
1191
1274
|
|
|
1192
|
-
// Resolve Dockerfile source
|
|
1193
|
-
|
|
1194
|
-
|
|
1275
|
+
// Resolve Dockerfile source. Dev/prod variant rules:
|
|
1276
|
+
// - When the instance defines a `runtime`, look under
|
|
1277
|
+
// `src/runtime/<runtime>/`. In `--dev` mode prefer `Dockerfile.dev`
|
|
1278
|
+
// when it exists, falling back to `Dockerfile`.
|
|
1279
|
+
// - When `runtime` is not set, look in the project root with the
|
|
1280
|
+
// same `.dev` → no-suffix precedence.
|
|
1281
|
+
// Dockerfile.dev is a full Dockerfile (not an overlay) — each runtime
|
|
1282
|
+
// owns the contract between its dev image and its prod image (debug
|
|
1283
|
+
// build flags, extra tooling, default ports, etc.).
|
|
1284
|
+
const dockerfileBase = _runtime ? `src/runtime/${_runtime}` : rootPath;
|
|
1285
|
+
const dockerfileCandidates = options.dev
|
|
1286
|
+
? [`${dockerfileBase}/Dockerfile.dev`, `${dockerfileBase}/Dockerfile`]
|
|
1287
|
+
: [`${dockerfileBase}/Dockerfile`];
|
|
1288
|
+
const dockerfileSourcePath = dockerfileCandidates.find((p) => fs.existsSync(p));
|
|
1289
|
+
if (dockerfileSourcePath) {
|
|
1290
|
+
if (options.dev && !dockerfileSourcePath.endsWith('.dev')) {
|
|
1291
|
+
logger.warn(
|
|
1292
|
+
`[instance-build-manifest] --dev requested but no Dockerfile.dev present; falling back to ${dockerfileSourcePath}`,
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1195
1295
|
fs.copyFileSync(dockerfileSourcePath, dockerfileManifestPath);
|
|
1196
1296
|
} else {
|
|
1197
|
-
logger.warn(`[instance-build-manifest] Dockerfile not found
|
|
1297
|
+
logger.warn(`[instance-build-manifest] Dockerfile not found; tried: ${dockerfileCandidates.join(', ')}`);
|
|
1198
1298
|
}
|
|
1199
1299
|
|
|
1200
1300
|
const _deployId = `${deployId}-${_id}`;
|
|
@@ -1239,6 +1339,17 @@ EOF
|
|
|
1239
1339
|
),
|
|
1240
1340
|
);
|
|
1241
1341
|
|
|
1342
|
+
// Env-aware lifecycle / probe selection. Each block may either be
|
|
1343
|
+
// a single object (shared across envs) or `{ development, production }`.
|
|
1344
|
+
const pickEnv = (v) => (v && (v.development || v.production) ? v[env] : v);
|
|
1345
|
+
|
|
1346
|
+
// Convention: an instance config may place `imagePullPolicy` inside
|
|
1347
|
+
// the env-scoped lifecycle block (alongside postStart/preStop).
|
|
1348
|
+
// Extract it onto the container spec and strip it from the lifecycle hash.
|
|
1349
|
+
const { lifecycle: lifecycleForManifest, imagePullPolicy: lifecycleImagePullPolicy } =
|
|
1350
|
+
Underpost.deploy.extractInstanceImagePullPolicy(pickEnv(_lifecycle));
|
|
1351
|
+
const instanceImagePullPolicy = options.imagePullPolicy || lifecycleImagePullPolicy;
|
|
1352
|
+
|
|
1242
1353
|
const deploymentYaml =
|
|
1243
1354
|
`---\n` +
|
|
1244
1355
|
Underpost.deploy
|
|
@@ -1252,6 +1363,11 @@ EOF
|
|
|
1252
1363
|
namespace: options.namespace,
|
|
1253
1364
|
volumes: _volumes,
|
|
1254
1365
|
cmd: resolvedCmd,
|
|
1366
|
+
lifecycle: lifecycleForManifest,
|
|
1367
|
+
readinessProbe: pickEnv(_readinessProbe),
|
|
1368
|
+
livenessProbe: pickEnv(_livenessProbe),
|
|
1369
|
+
containerPort: _toPort,
|
|
1370
|
+
imagePullPolicy: instanceImagePullPolicy,
|
|
1255
1371
|
})
|
|
1256
1372
|
.replace('{{ports}}', buildKindPorts(_fromPort, _toPort));
|
|
1257
1373
|
|
|
@@ -1330,9 +1446,9 @@ EOF`);
|
|
|
1330
1446
|
// crictl is in the kubernetes repo but excluded by default — install it explicitly
|
|
1331
1447
|
shellExec(`sudo yum install -y cri-tools --disableexcludes=kubernetes`);
|
|
1332
1448
|
// Ensure CRI-O uses systemd cgroup driver (matches kubelet default)
|
|
1333
|
-
shellExec(
|
|
1334
|
-
|
|
1335
|
-
);
|
|
1449
|
+
shellExec(`sudo sed -i 's/^#\?cgroup_manager =.*/cgroup_manager = "systemd"/' /etc/crio/crio.conf`, {
|
|
1450
|
+
silentOnError: true,
|
|
1451
|
+
});
|
|
1336
1452
|
shellExec(`sudo systemctl enable --now crio`);
|
|
1337
1453
|
logger.info('CRI-O installed and started.');
|
|
1338
1454
|
// Write crictl config so all crictl calls default to the CRI-O socket
|
|
@@ -1356,8 +1472,8 @@ EOF`);
|
|
|
1356
1472
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1357
1473
|
const currentImage = options.imageName
|
|
1358
1474
|
? options.imageName
|
|
1359
|
-
: Underpost.
|
|
1360
|
-
.
|
|
1475
|
+
: Underpost.image
|
|
1476
|
+
.getCurrentLoaded(options.nodeName ? options.nodeName : 'kind-worker', false)
|
|
1361
1477
|
.find((o) => o.IMAGE.match('underpost'));
|
|
1362
1478
|
const podName = options.podName || `underpost-dev-container`;
|
|
1363
1479
|
const volumeHostPath = options.claimName || '/home/dd';
|
|
@@ -1466,7 +1582,8 @@ EOF`);
|
|
|
1466
1582
|
`git config user.name '${username}' && ` +
|
|
1467
1583
|
`git config user.email '${email}' && ` +
|
|
1468
1584
|
`git config credential.interactive always &&` +
|
|
1469
|
-
`git config pull.rebase false
|
|
1585
|
+
`git config pull.rebase false && ` +
|
|
1586
|
+
`git config core.filemode false`,
|
|
1470
1587
|
{
|
|
1471
1588
|
disableLog: true,
|
|
1472
1589
|
silent: true,
|
|
@@ -1633,20 +1750,13 @@ EOF`);
|
|
|
1633
1750
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
1634
1751
|
const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
|
|
1635
1752
|
const env = options.dev ? 'development' : 'production';
|
|
1636
|
-
const ignorePods = Underpost.
|
|
1753
|
+
const ignorePods = Underpost.kubectl
|
|
1637
1754
|
.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace)
|
|
1638
1755
|
.map((p) => p.NAME);
|
|
1639
1756
|
|
|
1640
1757
|
shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${targetTraffic} -n ${options.namespace}`);
|
|
1641
1758
|
|
|
1642
|
-
await Underpost.
|
|
1643
|
-
deployId,
|
|
1644
|
-
env,
|
|
1645
|
-
targetTraffic,
|
|
1646
|
-
ignorePods,
|
|
1647
|
-
options.namespace,
|
|
1648
|
-
'underpost',
|
|
1649
|
-
);
|
|
1759
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods, options.namespace);
|
|
1650
1760
|
|
|
1651
1761
|
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, options.replicas, options.namespace, options);
|
|
1652
1762
|
},
|
|
@@ -1738,7 +1848,7 @@ EOF`);
|
|
|
1738
1848
|
}`;
|
|
1739
1849
|
shellExec(cmd, { async: true });
|
|
1740
1850
|
}
|
|
1741
|
-
await awaitDeployMonitor(true);
|
|
1851
|
+
if ((await awaitDeployMonitor()) !== true) return;
|
|
1742
1852
|
{
|
|
1743
1853
|
const cmd = `npm run dev:client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
|
|
1744
1854
|
|
|
@@ -1746,7 +1856,7 @@ EOF`);
|
|
|
1746
1856
|
async: true,
|
|
1747
1857
|
});
|
|
1748
1858
|
}
|
|
1749
|
-
await awaitDeployMonitor(true);
|
|
1859
|
+
if ((await awaitDeployMonitor()) !== true) return;
|
|
1750
1860
|
shellExec(
|
|
1751
1861
|
`NODE_ENV=development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`,
|
|
1752
1862
|
);
|
|
@@ -1843,7 +1953,7 @@ EOF`);
|
|
|
1843
1953
|
);
|
|
1844
1954
|
} else logger.error(`Service pod ${podToMonitor} failed to start in time.`);
|
|
1845
1955
|
if (options.etcHosts === true) {
|
|
1846
|
-
const hostListenResult =
|
|
1956
|
+
const hostListenResult = etcHostFactory([host]);
|
|
1847
1957
|
logger.info(hostListenResult.renderHosts);
|
|
1848
1958
|
}
|
|
1849
1959
|
},
|
|
@@ -1861,7 +1971,7 @@ EOF`);
|
|
|
1861
1971
|
const confServer = loadConfServerJson(`./engine-private/conf/${options.deployId}/conf.server.json`);
|
|
1862
1972
|
hosts.push(...Object.keys(confServer));
|
|
1863
1973
|
}
|
|
1864
|
-
const hostListenResult =
|
|
1974
|
+
const hostListenResult = etcHostFactory(hosts);
|
|
1865
1975
|
logger.info(hostListenResult.renderHosts);
|
|
1866
1976
|
},
|
|
1867
1977
|
|
|
@@ -2180,15 +2290,26 @@ EOF`);
|
|
|
2180
2290
|
* @memberof UnderpostRun
|
|
2181
2291
|
*/
|
|
2182
2292
|
kill: (path = '', options = DEFAULT_OPTION) => {
|
|
2183
|
-
if (options.pid)
|
|
2293
|
+
if (options.pid)
|
|
2294
|
+
return shellExec(`sudo kill -9 ${options.pid}`, {
|
|
2295
|
+
silentOnError: true,
|
|
2296
|
+
});
|
|
2184
2297
|
for (const _path of path.split(',')) {
|
|
2185
2298
|
if (_path.split('+')[1]) {
|
|
2186
2299
|
let [port, sumPortOffSet] = _path.split('+');
|
|
2187
2300
|
port = parseInt(port);
|
|
2188
2301
|
sumPortOffSet = parseInt(sumPortOffSet);
|
|
2189
2302
|
for (const sumPort of range(0, sumPortOffSet))
|
|
2190
|
-
shellExec(
|
|
2191
|
-
|
|
2303
|
+
shellExec(
|
|
2304
|
+
`PIDS=$(lsof -t -i:${parseInt(port) + parseInt(sumPort)}); [ -n "$PIDS" ] && sudo kill -9 $PIDS || true`,
|
|
2305
|
+
{
|
|
2306
|
+
silentOnError: true,
|
|
2307
|
+
},
|
|
2308
|
+
);
|
|
2309
|
+
} else
|
|
2310
|
+
shellExec(`PIDS=$(lsof -t -i:${_path}); [ -n "$PIDS" ] && sudo kill -9 $PIDS || true`, {
|
|
2311
|
+
silentOnError: true,
|
|
2312
|
+
});
|
|
2192
2313
|
}
|
|
2193
2314
|
},
|
|
2194
2315
|
/**
|
|
@@ -2235,9 +2356,10 @@ EOF`);
|
|
|
2235
2356
|
* @memberof UnderpostRun
|
|
2236
2357
|
*/
|
|
2237
2358
|
secret: (path, options = DEFAULT_OPTION) => {
|
|
2238
|
-
const
|
|
2239
|
-
|
|
2240
|
-
|
|
2359
|
+
const cronDeployId = cronDeployIdResolve() || 'dd-cron';
|
|
2360
|
+
Underpost.secret.underpost.createFromEnvFile(
|
|
2361
|
+
`/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${options.dev ? 'development' : 'production'}`,
|
|
2362
|
+
);
|
|
2241
2363
|
},
|
|
2242
2364
|
/**
|
|
2243
2365
|
* @method underpost-config
|
|
@@ -2411,7 +2533,8 @@ EOF`;
|
|
|
2411
2533
|
/**
|
|
2412
2534
|
* @method push-bundle
|
|
2413
2535
|
* @description Builds the client zip for the specified deployment, splits it into parts, and uploads to file storage.
|
|
2414
|
-
* Steps: set env, build+split zip,
|
|
2536
|
+
* Steps: set env, build+split zip, upload only the zip parts belonging to the deploy-id's hosts (from conf.server.json).
|
|
2537
|
+
* Only files matching `<host>-<route>.zip.part*` or `<host>-<route>.zip` for each non-skipped route are uploaded.
|
|
2415
2538
|
* @param {string} path - Optional `fsPath.splitOption` string.
|
|
2416
2539
|
* Examples: `build` (default split 8), `build.16` (split 16 MB), `build.none-split` (no split flag).
|
|
2417
2540
|
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
@@ -2420,7 +2543,7 @@ EOF`;
|
|
|
2420
2543
|
* @memberof UnderpostRun
|
|
2421
2544
|
*/
|
|
2422
2545
|
'push-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
2423
|
-
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2546
|
+
const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
|
|
2424
2547
|
const env = options.dev ? 'development' : 'production';
|
|
2425
2548
|
const deployId = options.deployId || 'dd-default';
|
|
2426
2549
|
const pathParts = (path || '').split('.');
|
|
@@ -2444,11 +2567,54 @@ EOF`;
|
|
|
2444
2567
|
}
|
|
2445
2568
|
}
|
|
2446
2569
|
|
|
2570
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
2571
|
+
const confServer = fs.existsSync(confServerPath)
|
|
2572
|
+
? loadReplicas(deployId, loadConfServerJson(confServerPath))
|
|
2573
|
+
: {};
|
|
2574
|
+
const storageFilePath = `engine-private/conf/${deployId}/storage.bundle.json`;
|
|
2575
|
+
|
|
2447
2576
|
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
2448
2577
|
shellExec(`${baseCommand} client ${deployId} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2578
|
+
|
|
2579
|
+
const pushBundleFiles = (host, routePath) => {
|
|
2580
|
+
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
2581
|
+
const buildDir = `./${fsPath}`;
|
|
2582
|
+
if (!fs.existsSync(buildDir)) return;
|
|
2583
|
+
const partFiles = fs
|
|
2584
|
+
.readdirSync(buildDir)
|
|
2585
|
+
.filter(
|
|
2586
|
+
(name) =>
|
|
2587
|
+
name.startsWith(`${buildId}.zip.part`) ||
|
|
2588
|
+
name.startsWith(`${buildId}.zip-part`) ||
|
|
2589
|
+
name === `${buildId}.zip`,
|
|
2590
|
+
)
|
|
2591
|
+
.map((name) => `${fsPath}/${name}`);
|
|
2592
|
+
if (partFiles.length === 0) {
|
|
2593
|
+
logger.warn(`push-bundle: no bundle files found for '${host}${routePath}'`, { buildId });
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
for (const partFile of partFiles) {
|
|
2597
|
+
shellExec(
|
|
2598
|
+
`${baseCommand} fs ${partFile} --deploy-id ${deployId} --storage-file-path ${storageFilePath} --force`,
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2601
|
+
};
|
|
2602
|
+
|
|
2603
|
+
for (const host of Object.keys(confServer)) {
|
|
2604
|
+
for (const routePath of Object.keys(confServer[host])) {
|
|
2605
|
+
const routeConf = confServer[host][routePath] || {};
|
|
2606
|
+
if (routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
2607
|
+
if (routeConf.singleReplica) {
|
|
2608
|
+
if (routeConf.replicas) {
|
|
2609
|
+
for (const replica of routeConf.replicas) {
|
|
2610
|
+
pushBundleFiles(host, replica);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
pushBundleFiles(host, routePath);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2452
2618
|
},
|
|
2453
2619
|
|
|
2454
2620
|
/**
|
|
@@ -2465,11 +2631,13 @@ EOF`;
|
|
|
2465
2631
|
* @memberof UnderpostRun
|
|
2466
2632
|
*/
|
|
2467
2633
|
'pull-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
2468
|
-
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2634
|
+
const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
|
|
2469
2635
|
const env = options.dev ? 'development' : 'production';
|
|
2470
2636
|
const deployId = options.deployId || 'dd-default';
|
|
2471
2637
|
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
2472
|
-
const confServer = fs.existsSync(confServerPath)
|
|
2638
|
+
const confServer = fs.existsSync(confServerPath)
|
|
2639
|
+
? loadReplicas(deployId, loadConfServerJson(confServerPath))
|
|
2640
|
+
: {};
|
|
2473
2641
|
const hostsArg = path
|
|
2474
2642
|
? path
|
|
2475
2643
|
.split(',')
|
|
@@ -2488,62 +2656,124 @@ EOF`;
|
|
|
2488
2656
|
`${baseCommand} fs build --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --pull --omit-unzip`,
|
|
2489
2657
|
);
|
|
2490
2658
|
|
|
2659
|
+
const pullBundleRoute = (host, routePath) => {
|
|
2660
|
+
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
2661
|
+
const zipPath = `build/${buildId}.zip`;
|
|
2662
|
+
const buildDir = './build';
|
|
2663
|
+
const hasZip = fs.existsSync(zipPath);
|
|
2664
|
+
const hasParts =
|
|
2665
|
+
fs.existsSync(buildDir) &&
|
|
2666
|
+
fs
|
|
2667
|
+
.readdirSync(buildDir)
|
|
2668
|
+
.some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
|
|
2669
|
+
|
|
2670
|
+
if (!hasZip && !hasParts) {
|
|
2671
|
+
logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
if (hasParts) shellExec(`${baseCommand} client --merge-zip ${zipPath}`);
|
|
2676
|
+
shellExec(`${baseCommand} client --unzip ${zipPath}`);
|
|
2677
|
+
shellExec(`sudo rm -rf ${zipPath}`);
|
|
2678
|
+
|
|
2679
|
+
if (fs.existsSync(buildDir)) {
|
|
2680
|
+
fs.readdirSync(buildDir)
|
|
2681
|
+
.filter((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`))
|
|
2682
|
+
.forEach((partFile) => shellExec(`sudo rm -rf ${buildDir}/${partFile}`));
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
const extractedDir = `build/${buildId.replace(/-$/, '')}`;
|
|
2686
|
+
if (!fs.existsSync(extractedDir)) {
|
|
2687
|
+
logger.warn(`Extracted build dir not found: ${extractedDir}. Skipping move for '${host}${routePath}'.`);
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
const publicDestPath = routePath === '/' ? `public/${host}` : `public/${host}${routePath}`;
|
|
2692
|
+
if (fs.existsSync(publicDestPath)) shellExec(`sudo rm -rf ${publicDestPath}`);
|
|
2693
|
+
if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
|
|
2694
|
+
fs.copySync(`${extractedDir}`, `${publicDestPath}`);
|
|
2695
|
+
};
|
|
2696
|
+
|
|
2491
2697
|
for (const host of hostsArg) {
|
|
2492
|
-
// Gather all routes for this host; fall back to root '/' when host is not in confServer
|
|
2493
|
-
// (e.g. when hosts were provided explicitly via the path argument).
|
|
2494
2698
|
const routePaths = confServer[host] ? Object.keys(confServer[host]) : ['/'];
|
|
2495
2699
|
|
|
2496
2700
|
for (const routePath of routePaths) {
|
|
2497
2701
|
const routeConf = confServer[host] ? confServer[host][routePath] || {} : {};
|
|
2498
|
-
|
|
2499
|
-
if (routeConf.singleReplica
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
const zipPath = `build/${buildId}.zip`;
|
|
2506
|
-
const buildDir = './build';
|
|
2507
|
-
const hasZip = fs.existsSync(zipPath);
|
|
2508
|
-
const hasParts =
|
|
2509
|
-
fs.existsSync(buildDir) &&
|
|
2510
|
-
fs
|
|
2511
|
-
.readdirSync(buildDir)
|
|
2512
|
-
.some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
|
|
2513
|
-
|
|
2514
|
-
if (!hasZip && !hasParts) {
|
|
2515
|
-
logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
|
|
2702
|
+
if (routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
2703
|
+
if (routeConf.singleReplica) {
|
|
2704
|
+
if (routeConf.replicas) {
|
|
2705
|
+
for (const replica of routeConf.replicas) {
|
|
2706
|
+
pullBundleRoute(host, replica);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2516
2709
|
continue;
|
|
2517
2710
|
}
|
|
2711
|
+
pullBundleRoute(host, routePath);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
},
|
|
2518
2715
|
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2716
|
+
/**
|
|
2717
|
+
* @method build-cluster-deployment-manifests
|
|
2718
|
+
* @description Builds deployment manifests for both production and development environments using `node bin deploy --build-manifest`, syncing them, and setting replicas to 1 for the `dd` deployment.
|
|
2719
|
+
* @param {string} path - Unused.
|
|
2720
|
+
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
2721
|
+
* @memberof UnderpostRun
|
|
2722
|
+
*/
|
|
2723
|
+
'build-cluster-deployment-manifests': (path = '', options = DEFAULT_OPTION) => {
|
|
2724
|
+
shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd development`);
|
|
2725
|
+
shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd production --cert`);
|
|
2726
|
+
},
|
|
2522
2727
|
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2728
|
+
/**
|
|
2729
|
+
* @method monitor-ui
|
|
2730
|
+
* @description Installs and enables the Cockpit KVM Dashboard (cockpit, cockpit-machines, libvirt)
|
|
2731
|
+
* and opens the cockpit firewall service. With `--remove`, closes the firewall service instead.
|
|
2732
|
+
* @param {string} path - Unused.
|
|
2733
|
+
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
2734
|
+
* `options.remove` — when true, removes the cockpit firewall rule instead of adding it.
|
|
2735
|
+
* @memberof UnderpostRun
|
|
2736
|
+
*/
|
|
2737
|
+
'monitor-ui': (path, options = DEFAULT_OPTION) => {
|
|
2738
|
+
if (options.remove) {
|
|
2739
|
+
shellExec(`sudo firewall-cmd --zone=public --remove-service=cockpit --permanent`);
|
|
2740
|
+
shellExec(`sudo firewall-cmd --reload`);
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
shellExec(`sudo dnf install -y cockpit cockpit-machines libvirt`);
|
|
2744
|
+
shellExec(`sudo systemctl enable --now cockpit.socket libvirtd`);
|
|
2745
|
+
shellExec(`sudo firewall-cmd --permanent --add-service=cockpit`);
|
|
2746
|
+
shellExec(`sudo firewall-cmd --reload`);
|
|
2747
|
+
},
|
|
2529
2748
|
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2749
|
+
/**
|
|
2750
|
+
* @method shared-dir
|
|
2751
|
+
* @description Run once for initial shared-directory setup. Creates the group, adds the user,
|
|
2752
|
+
* creates the directory, sets ownership, applies the SGID bit, and configures default ACLs so
|
|
2753
|
+
* all future files inside the directory automatically inherit group write permissions.
|
|
2754
|
+
* Use `reload-shared-dir` for subsequent permission repairs without recreating the group.
|
|
2755
|
+
* @param {string} path - Target directory to set up (defaults to `/home/dd/engine`).
|
|
2756
|
+
* Customise via the `path` argument or leave empty to use the default.
|
|
2757
|
+
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
2758
|
+
* Key fields: `options.user` (default `'admin'`), `options.group` (default `'engine-dev'`).
|
|
2759
|
+
* @memberof UnderpostRun
|
|
2760
|
+
*/
|
|
2761
|
+
'shared-dir': (path = '/home/dd/engine', options = DEFAULT_OPTION) => {
|
|
2762
|
+
const dir = path || '/home/dd/engine';
|
|
2763
|
+
const user = options.user || 'admin';
|
|
2764
|
+
const group = options.group || 'engine-dev';
|
|
2538
2765
|
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
}
|
|
2766
|
+
logger.info(`[setup-shared-dir] dir=${dir} user=${user} group=${group}`);
|
|
2767
|
+
|
|
2768
|
+
shellExec(`sudo groupadd ${group} 2>/dev/null || true`);
|
|
2769
|
+
shellExec(`sudo usermod -aG ${group} ${user}`);
|
|
2770
|
+
shellExec(`sudo mkdir -p ${dir}`);
|
|
2771
|
+
shellExec(`sudo chown -R ${user}:${group} ${dir}`);
|
|
2772
|
+
shellExec(`sudo chmod -R 2775 ${dir}`);
|
|
2773
|
+
shellExec(`sudo setfacl -d -m g:${group}:rwx ${dir}`);
|
|
2774
|
+
shellExec(`sudo setfacl -m g:${group}:rwx ${dir}`);
|
|
2775
|
+
|
|
2776
|
+
logger.info(`[setup-shared-dir] Shared directory setup complete: ${dir}`);
|
|
2547
2777
|
},
|
|
2548
2778
|
};
|
|
2549
2779
|
|
|
@@ -2601,14 +2831,14 @@ EOF`;
|
|
|
2601
2831
|
if (options.replicas === '' || options.replicas === null || options.replicas === undefined)
|
|
2602
2832
|
options.replicas = 1;
|
|
2603
2833
|
options.npmRoot = npmRoot;
|
|
2604
|
-
logger.info(
|
|
2834
|
+
logger.info(`Executing runner`, { runner, namespace: options.namespace });
|
|
2605
2835
|
if (!Underpost.run.RUNNERS.includes(runner)) throw new Error(`Runner not found: ${runner}`);
|
|
2606
2836
|
const result = await Underpost.run.CALL(runner, path, options);
|
|
2607
2837
|
return result;
|
|
2608
2838
|
} catch (error) {
|
|
2609
2839
|
console.log(error);
|
|
2610
2840
|
logger.error(error);
|
|
2611
|
-
|
|
2841
|
+
process.exit(1);
|
|
2612
2842
|
}
|
|
2613
2843
|
},
|
|
2614
2844
|
};
|