@underpostnet/underpost 2.90.4 → 2.95.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/.github/workflows/pwa-microservices-template-page.cd.yml +5 -4
- package/.github/workflows/release.cd.yml +7 -7
- package/README.md +7 -8
- package/bin/build.js +6 -1
- package/bin/deploy.js +2 -196
- package/cli.md +154 -80
- package/manifests/deployment/dd-default-development/deployment.yaml +4 -4
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/scripts/disk-clean.sh +216 -0
- package/scripts/rocky-setup.sh +1 -0
- package/scripts/ssh-cluster-info.sh +4 -3
- package/src/cli/cluster.js +1 -1
- package/src/cli/db.js +1143 -201
- package/src/cli/deploy.js +93 -24
- package/src/cli/env.js +2 -2
- package/src/cli/image.js +198 -133
- package/src/cli/index.js +111 -44
- package/src/cli/lxd.js +73 -74
- package/src/cli/monitor.js +20 -9
- package/src/cli/repository.js +212 -5
- package/src/cli/run.js +207 -74
- package/src/cli/ssh.js +642 -14
- package/src/client/components/core/CommonJs.js +0 -1
- package/src/db/mongo/MongooseDB.js +5 -1
- package/src/index.js +1 -1
- package/src/monitor.js +11 -1
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +1 -1
- package/src/server/dns.js +242 -1
- package/src/server/process.js +6 -1
- package/src/server/start.js +2 -0
- package/scripts/snap-clean.sh +0 -26
- package/src/client/public/default/plantuml/client-conf.svg +0 -1
- package/src/client/public/default/plantuml/client-schema.svg +0 -1
- package/src/client/public/default/plantuml/cron-conf.svg +0 -1
- package/src/client/public/default/plantuml/cron-schema.svg +0 -1
- package/src/client/public/default/plantuml/server-conf.svg +0 -1
- package/src/client/public/default/plantuml/server-schema.svg +0 -1
- package/src/client/public/default/plantuml/ssr-conf.svg +0 -1
- package/src/client/public/default/plantuml/ssr-schema.svg +0 -1
package/src/cli/index.js
CHANGED
|
@@ -8,7 +8,6 @@ import UnderpostLxd from './lxd.js';
|
|
|
8
8
|
import UnderpostBaremetal from './baremetal.js';
|
|
9
9
|
import UnderpostRun from './run.js';
|
|
10
10
|
import Dns from '../server/dns.js';
|
|
11
|
-
import { pbcopy } from '../server/process.js';
|
|
12
11
|
import UnderpostStatic from './static.js';
|
|
13
12
|
|
|
14
13
|
// Load environment variables from .env file
|
|
@@ -29,7 +28,14 @@ program
|
|
|
29
28
|
.option('--deploy-id <deploy-id>', 'Crete deploy ID conf env files')
|
|
30
29
|
.option('--sub-conf <sub-conf>', 'Create sub conf env files')
|
|
31
30
|
.option('--cluster', 'Create deploy ID cluster files and sync to current cluster')
|
|
31
|
+
.option('--build-repos', 'Create deploy ID repositories')
|
|
32
|
+
.option('--build', 'Build the deployment to pwa-microservices-template (requires --deploy-id)')
|
|
33
|
+
.option('--clean-template', 'Clean the build directory (pwa-microservices-template)')
|
|
34
|
+
.option('--sync-conf', 'Sync configuration to private repositories (requires --deploy-id)')
|
|
35
|
+
.option('--purge', 'Remove deploy ID conf and all related repositories (requires --deploy-id)')
|
|
32
36
|
.option('--dev', 'Sets the development cli context')
|
|
37
|
+
.option('--default-conf', 'Create default deploy ID conf env files')
|
|
38
|
+
.option('--conf-workflow-id <workflow-id>', 'Set custom configuration workflow ID for conf generation')
|
|
33
39
|
.description('Initializes a new Underpost project, service, or configuration.')
|
|
34
40
|
.action(Underpost.repo.new);
|
|
35
41
|
|
|
@@ -238,13 +244,20 @@ program
|
|
|
238
244
|
|
|
239
245
|
program
|
|
240
246
|
.command('ip')
|
|
247
|
+
.argument('[ips]', 'Optional args comma-separated list of IP to process.')
|
|
241
248
|
.option('--copy', 'Copies the IP addresses to the clipboard.')
|
|
249
|
+
.option('--ban-ingress-add', 'Adds IP addresses to banned ingress list.')
|
|
250
|
+
.option('--ban-ingress-remove', 'Removes IP addresses from banned ingress list.')
|
|
251
|
+
.option('--ban-ingress-list', 'Lists all banned ingress IP addresses.')
|
|
252
|
+
.option('--ban-ingress-clear', 'Clears all banned ingress IP addresses.')
|
|
253
|
+
.option('--ban-egress-add', 'Adds IP addresses to banned egress list.')
|
|
254
|
+
.option('--ban-egress-remove', 'Removes IP addresses from banned egress list.')
|
|
255
|
+
.option('--ban-egress-list', 'Lists all banned egress IP addresses.')
|
|
256
|
+
.option('--ban-egress-clear', 'Clears all banned egress IP addresses.')
|
|
257
|
+
.option('--ban-both-add', 'Adds IP addresses to both banned ingress and egress lists.')
|
|
258
|
+
.option('--ban-both-remove', 'Removes IP addresses from both banned ingress and egress lists.')
|
|
242
259
|
.description('Displays the current public machine IP addresses.')
|
|
243
|
-
.action(
|
|
244
|
-
const ip = await Dns.getPublicIp();
|
|
245
|
-
if (options.copy) return pbcopy(ip);
|
|
246
|
-
return console.log(ip);
|
|
247
|
-
});
|
|
260
|
+
.action(Dns.ipDispatcher);
|
|
248
261
|
|
|
249
262
|
// 'cluster' command: Manage Kubernetes clusters
|
|
250
263
|
program
|
|
@@ -331,6 +344,8 @@ program
|
|
|
331
344
|
.option('--restore-hosts', 'Restores default `/etc/hosts` entries.')
|
|
332
345
|
.option('--disable-update-underpost-config', 'Disables updates to Underpost configuration during deployment.')
|
|
333
346
|
.option('--namespace <namespace>', 'Kubernetes namespace for deployment operations (defaults to "default").')
|
|
347
|
+
.option('--kind-type <kind-type>', 'Specifies the Kind cluster type for deployment operations.')
|
|
348
|
+
.option('--port <port>', 'Sets up port forwarding from local to remote ports.')
|
|
334
349
|
.description('Manages application deployments, defaulting to deploying development pods.')
|
|
335
350
|
.action(Underpost.deploy.callback);
|
|
336
351
|
|
|
@@ -348,37 +363,41 @@ program
|
|
|
348
363
|
if (args[1].init) return Underpost.secret[args[0]].init();
|
|
349
364
|
});
|
|
350
365
|
|
|
351
|
-
// '
|
|
366
|
+
// 'image'
|
|
352
367
|
program
|
|
353
|
-
.command('
|
|
368
|
+
.command('image')
|
|
369
|
+
.option(
|
|
370
|
+
'--build',
|
|
371
|
+
'Builds a Docker image using Podman, optionally saves it as a tar archive, and loads it into a specified Kubernetes cluster (Kind, Kubeadm, or K3s).',
|
|
372
|
+
)
|
|
373
|
+
.option('--ls', 'Lists all available Underpost Dockerfile images.')
|
|
374
|
+
.option('--rm <image-id>', 'Removes specified Underpost Dockerfile images.')
|
|
354
375
|
.option('--path [path]', 'The path to the Dockerfile directory.')
|
|
355
376
|
.option('--image-name [image-name]', 'Sets a custom name for the Docker image.')
|
|
356
377
|
.option('--image-path [image-path]', 'Sets the output path for the tar image archive.')
|
|
357
378
|
.option('--dockerfile-name [dockerfile-name]', 'Sets a custom name for the Dockerfile.')
|
|
358
379
|
.option('--podman-save', 'Exports the built image as a tar file using Podman.')
|
|
359
|
-
.option('--
|
|
360
|
-
.option('--
|
|
380
|
+
.option('--pull-base', 'Pulls base images and builds a "rockylinux9-underpost" image.')
|
|
381
|
+
.option('--spec', 'Get current cached list of container images used by all pods')
|
|
382
|
+
.option('--namespace <namespace>', 'Kubernetes namespace for image operations (defaults to "default").')
|
|
383
|
+
.option('--kind', 'Set kind cluster env image context management.')
|
|
384
|
+
.option('--kubeadm', 'Set kubeadm cluster env image context management.')
|
|
385
|
+
.option('--k3s', 'Set k3s cluster env image context management.')
|
|
386
|
+
.option('--node-name', 'Set node name for kubeadm or k3s cluster env image context management.')
|
|
361
387
|
.option('--secrets', 'Includes Dockerfile environment secrets during the build.')
|
|
362
388
|
.option('--secrets-path [secrets-path]', 'Specifies a custom path for Dockerfile environment secrets.')
|
|
363
389
|
.option('--reset', 'Performs a build without using the cache.')
|
|
364
390
|
.option('--dev', 'Use development mode.')
|
|
365
|
-
.option('--
|
|
366
|
-
.description(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
.option('--kind-load', 'Imports the pulled image into a Kind cluster.')
|
|
376
|
-
.option('--kubeadm-load', 'Imports the pulled image into a Kubeadm cluster.')
|
|
377
|
-
.option('--version', 'Sets a custom version for the base images.')
|
|
378
|
-
.option('--k3s-load', 'Loads the image into a K3s cluster.')
|
|
379
|
-
.option('--dev', 'Use development mode.')
|
|
380
|
-
.description('Pulls required Underpost Dockerfile base images and optionally loads them into clusters.')
|
|
381
|
-
.action(Underpost.image.dockerfile.pullBaseImages);
|
|
391
|
+
.option('--pull-dockerhub <dockerhub-image>', 'Sets a custom Docker Hub image for base image pulls.')
|
|
392
|
+
.description('Manages Docker images, including building, saving, and loading into Kubernetes clusters.')
|
|
393
|
+
.action(async (options) => {
|
|
394
|
+
if (options.rm) Underpost.image.rm({ ...options, imageName: options.rm });
|
|
395
|
+
if (options.ls) Underpost.image.list({ ...options, log: true });
|
|
396
|
+
if (options.pullBase) Underpost.image.pullBaseImages(options);
|
|
397
|
+
if (options.build) Underpost.image.build(options);
|
|
398
|
+
if (options.pullDockerhub)
|
|
399
|
+
Underpost.image.pullDockerHubImage({ ...options, dockerhubImage: options.pullDockerhub });
|
|
400
|
+
});
|
|
382
401
|
|
|
383
402
|
// 'install' command: Fast import npm dependencies
|
|
384
403
|
program
|
|
@@ -394,16 +413,30 @@ program
|
|
|
394
413
|
.argument('<deploy-list>', 'A comma-separated list of deployment IDs (e.g., "default-a,default-b").')
|
|
395
414
|
.option('--import', 'Imports container backups from specified repositories.')
|
|
396
415
|
.option('--export', 'Exports container backups to specified repositories.')
|
|
397
|
-
.option(
|
|
398
|
-
|
|
416
|
+
.option(
|
|
417
|
+
'--pod-name <pod-name>',
|
|
418
|
+
'Comma-separated list of pod names or patterns (supports wildcards like "mariadb-*").',
|
|
419
|
+
)
|
|
420
|
+
.option('--node-name <node-name>', 'Comma-separated list of node names to filter pods by their node placement.')
|
|
421
|
+
.option('--label-selector <selector>', 'Kubernetes label selector for filtering pods (e.g., "app=mariadb").')
|
|
422
|
+
.option('--all-pods', 'Target all matching pods instead of just the first one.')
|
|
423
|
+
.option('--primary-pod', 'Automatically detect and use MongoDB primary pod (MongoDB only).')
|
|
424
|
+
.option('--stats', 'Display database statistics (collection/table names with document/row counts).')
|
|
425
|
+
.option('--collections <collections>', 'Comma-separated list of database collections to operate on.')
|
|
399
426
|
.option('--out-path <out-path>', 'Specifies a custom output path for backups.')
|
|
400
|
-
.option('--drop', 'Drops the specified databases or collections.')
|
|
401
|
-
.option('--preserveUUID', 'Preserves UUIDs during database operations.')
|
|
402
|
-
.option('--git', '
|
|
403
|
-
.option('--hosts <hosts>', '
|
|
404
|
-
.option('--paths <paths>', '
|
|
405
|
-
.option('--ns <ns-name>', '
|
|
406
|
-
.
|
|
427
|
+
.option('--drop', 'Drops the specified databases or collections before importing.')
|
|
428
|
+
.option('--preserveUUID', 'Preserves UUIDs during database import operations.')
|
|
429
|
+
.option('--git', 'Enables Git integration for backup version control (clone, pull, commit, push to GitHub).')
|
|
430
|
+
.option('--hosts <hosts>', 'Comma-separated list of database hosts to filter operations.')
|
|
431
|
+
.option('--paths <paths>', 'Comma-separated list of paths to filter database operations.')
|
|
432
|
+
.option('--ns <ns-name>', 'Kubernetes namespace context for database operations (defaults to "default").')
|
|
433
|
+
.option(
|
|
434
|
+
'--macro-rollback-export <n-commits-reset>',
|
|
435
|
+
'Exports a macro rollback script that reverts the last n commits (Git integration required).',
|
|
436
|
+
)
|
|
437
|
+
.description(
|
|
438
|
+
'Manages database operations with support for MariaDB and MongoDB, including import/export, multi-pod targeting, and Git integration.',
|
|
439
|
+
)
|
|
407
440
|
.action(Underpost.db.callback);
|
|
408
441
|
|
|
409
442
|
program
|
|
@@ -489,24 +522,44 @@ program
|
|
|
489
522
|
.option('--ms-interval <ms-interval>', 'Sets a custom millisecond interval for monitoring checks.')
|
|
490
523
|
.option('--now', 'Executes the monitor script immediately.')
|
|
491
524
|
.option('--single', 'Disables recurrence, running the monitor script only once.')
|
|
492
|
-
.option('--replicas <replicas>', 'Sets a custom number of replicas for monitoring.')
|
|
525
|
+
.option('--replicas <replicas>', 'Sets a custom number of replicas for monitoring. Defaults to 1.')
|
|
493
526
|
.option('--type <type>', 'Sets a custom monitor type.')
|
|
494
527
|
.option('--sync', 'Synchronizes with current proxy deployments and traffic configurations.')
|
|
528
|
+
.option('--namespace <namespace>', 'Sets the Kubernetes namespace for the deployment. Defaults to "default".')
|
|
495
529
|
.description('Manages health server monitoring for specified deployments.')
|
|
496
530
|
.action(Underpost.monitor.callback);
|
|
497
531
|
|
|
498
532
|
// 'ssh' command: SSH management
|
|
499
533
|
program
|
|
500
534
|
.command('ssh')
|
|
535
|
+
.option('--deploy-id <deploy-id>', 'Sets deploy id context for ssh operations.')
|
|
501
536
|
.option('--generate', 'Generates new ssh credential and stores it in current private keys file storage.')
|
|
502
|
-
.
|
|
537
|
+
.option('--user <user>', 'Sets custom ssh user')
|
|
538
|
+
.option('--password <password>', 'Sets custom ssh password')
|
|
539
|
+
.option('--host <host>', 'Sets custom ssh host')
|
|
540
|
+
.option('--port <port>', 'Sets custom ssh port')
|
|
541
|
+
.option('--filter <filter>', 'Filters ssh user credentials from current private keys file storage.')
|
|
542
|
+
.option('--groups <groups>', 'Sets comma-separated ssh user groups for the ssh user credential.')
|
|
543
|
+
.option('--user-add', 'Adds a new ssh user credential to current private keys file storage.')
|
|
544
|
+
.option('--user-remove', 'Removes an existing ssh user credential from current private keys file storage.')
|
|
545
|
+
.option('--user-ls', 'Lists all ssh user credentials from current private keys file storage.')
|
|
546
|
+
.option('--start', 'Starts an SSH session with the specified credentials.')
|
|
547
|
+
.option('--reset', 'Resets ssh configuration and deletes all stored credentials.')
|
|
548
|
+
.option('--keys-list', 'Lists all ssh keys from current private keys file storage.')
|
|
549
|
+
.option('--hosts-list', 'Lists all ssh hosts from current private keys file storage.')
|
|
550
|
+
.option('--disable-password', 'Disables password authentication for the SSH session.')
|
|
551
|
+
.option('--key-test', 'Tests the SSH key using ssh-keygen.')
|
|
552
|
+
.option('--stop', 'Stops the SSH service.')
|
|
553
|
+
.option('--status', 'Checks the status of the SSH service.')
|
|
554
|
+
.option('--connect-uri', 'Displays the connection URI.')
|
|
555
|
+
.option('--copy', 'Copies the connection URI to clipboard.')
|
|
503
556
|
.action(Underpost.ssh.callback);
|
|
504
557
|
|
|
505
558
|
// 'run' command: Run a script
|
|
506
559
|
program
|
|
507
560
|
.command('run')
|
|
508
561
|
.argument('<runner-id>', `The runner ID to run. Options: ${Object.keys(UnderpostRun.RUNNERS).join(', ')}.`)
|
|
509
|
-
.argument('[path]', 'The
|
|
562
|
+
.argument('[path]', 'The input value, identifier, or path for the operation.')
|
|
510
563
|
.option('--command <command-array>', 'Array of commands to run.')
|
|
511
564
|
.option('--args <args-array>', 'Array of arguments to pass to the command.')
|
|
512
565
|
.option('--dev', 'Sets the development context environment for the script.')
|
|
@@ -533,9 +586,10 @@ program
|
|
|
533
586
|
'Optional: Specifies a comma-separated list of key-value pairs for labels (e.g., "app=my-app,env=prod").',
|
|
534
587
|
)
|
|
535
588
|
.option('--claim-name <name>', 'Optional: Specifies the claim name for volume mounting in deploy-job.')
|
|
536
|
-
.option(
|
|
537
|
-
|
|
538
|
-
|
|
589
|
+
.option(
|
|
590
|
+
'--kind-type <kind-type>',
|
|
591
|
+
'Specifies the kind of Kubernetes resource (e.g., Job, Deployment) for deploy-job.',
|
|
592
|
+
)
|
|
539
593
|
.option('--force', 'Forces operation, overriding any warnings or conflicts.')
|
|
540
594
|
.option('--tls', 'Enables TLS for the runner execution.')
|
|
541
595
|
.option('--reset', 'Resets the runner state before execution.')
|
|
@@ -553,7 +607,17 @@ program
|
|
|
553
607
|
.option('--expose', 'Enables service exposure for the runner execution.')
|
|
554
608
|
.option('--conf-server-path <conf-server-path>', 'Sets a custom configuration server path.')
|
|
555
609
|
.option('--underpost-root <underpost-root>', 'Sets a custom Underpost root path.')
|
|
556
|
-
.
|
|
610
|
+
.option('--cron-jobs <jobs>', 'Comma-separated list of cron jobs to run before executing the script.')
|
|
611
|
+
.option('--timezone <timezone>', 'Sets the timezone for the runner execution.')
|
|
612
|
+
.option('--kubeadm', 'Sets the kubeadm cluster context for the runner execution.')
|
|
613
|
+
.option('--k3s', 'Sets the k3s cluster context for the runner execution.')
|
|
614
|
+
.option('--kind', 'Sets the kind cluster context for the runner execution.')
|
|
615
|
+
.option('--log-type <log-type>', 'Sets the log type for the runner execution.')
|
|
616
|
+
.option('--deploy-id <deploy-id>', 'Sets deploy id context for the runner execution.')
|
|
617
|
+
.option('--user <user>', 'Sets user context for the runner execution.')
|
|
618
|
+
.option('--hosts <hosts>', 'Comma-separated list of hosts for the runner execution.')
|
|
619
|
+
.option('--instance-id <instance-id>', 'Sets instance id context for the runner execution.')
|
|
620
|
+
.description('Runs specified scripts using various runners.')
|
|
557
621
|
.action(UnderpostRun.API.callback);
|
|
558
622
|
|
|
559
623
|
// 'lxd' command: LXD management
|
|
@@ -585,7 +649,10 @@ program
|
|
|
585
649
|
'--delete-expose <vm-name-ports>',
|
|
586
650
|
'Removes exposed ports on a VM (e.g., "k8s-control:80,443"). Multiple VM-port pairs can be comma-separated.',
|
|
587
651
|
)
|
|
588
|
-
.option('--
|
|
652
|
+
.option('--workflow-id <workflow-id>', 'Sets the workflow ID context for LXD operations.')
|
|
653
|
+
.option('--vm-id <vm-id>', 'Sets the VM ID context for LXD operations.')
|
|
654
|
+
.option('--deploy-id <deploy-id>', 'Sets the deployment ID context for LXD operations.')
|
|
655
|
+
.option('--namespace <namespace>', 'Kubernetes namespace for LXD operations (defaults to "default").')
|
|
589
656
|
.description('Manages LXD containers and virtual machines.')
|
|
590
657
|
.action(UnderpostLxd.API.callback);
|
|
591
658
|
|
package/src/cli/lxd.js
CHANGED
|
@@ -8,6 +8,9 @@ import { getNpmRootPath } from '../server/conf.js';
|
|
|
8
8
|
import { getLocalIPv4Address } from '../server/dns.js';
|
|
9
9
|
import { pbcopy, shellExec } from '../server/process.js';
|
|
10
10
|
import fs from 'fs-extra';
|
|
11
|
+
import { loggerFactory } from '../server/logger.js';
|
|
12
|
+
|
|
13
|
+
const logger = loggerFactory(import.meta);
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* @class UnderpostLxd
|
|
@@ -37,7 +40,10 @@ class UnderpostLxd {
|
|
|
37
40
|
* @param {string} [options.expose=''] - Expose ports from a VM to the host (format: 'vmName:port1,port2').
|
|
38
41
|
* @param {string} [options.deleteExpose=''] - Delete exposed ports from a VM (format: 'vmName:port1,port2').
|
|
39
42
|
* @param {string} [options.test=''] - Test health, status and network connectivity for a VM.
|
|
40
|
-
* @param {string} [options.
|
|
43
|
+
* @param {string} [options.workflowId=''] - Workflow identifier for workflow execution.
|
|
44
|
+
* @param {string} [options.vmId=''] - VM identifier for workflow execution.
|
|
45
|
+
* @param {string} [options.deployId=''] - Deployment identifier for workflow execution.
|
|
46
|
+
* @param {string} [options.namespace=''] - Namespace for workflow execution.
|
|
41
47
|
* @memberof UnderpostLxd
|
|
42
48
|
*/
|
|
43
49
|
async callback(
|
|
@@ -59,7 +65,10 @@ class UnderpostLxd {
|
|
|
59
65
|
expose: '',
|
|
60
66
|
deleteExpose: '',
|
|
61
67
|
test: '',
|
|
62
|
-
|
|
68
|
+
workflowId: '',
|
|
69
|
+
vmId: '',
|
|
70
|
+
deployId: '',
|
|
71
|
+
namespace: '',
|
|
63
72
|
},
|
|
64
73
|
) {
|
|
65
74
|
const npmRoot = getNpmRootPath();
|
|
@@ -86,9 +95,21 @@ ipv4.dhcp=true \
|
|
|
86
95
|
ipv6.address=none`);
|
|
87
96
|
}
|
|
88
97
|
if (options.createAdminProfile === true) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
const existingProfiles = await new Promise((resolve) => {
|
|
99
|
+
shellExec(`lxc profile show admin-profile`, {
|
|
100
|
+
silent: true,
|
|
101
|
+
callback: (...args) => {
|
|
102
|
+
return resolve(JSON.stringify(args));
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
if (existingProfiles.toLowerCase().match('error')) {
|
|
107
|
+
logger.warn('Profile does not exist. Using following command to create admin-profile:');
|
|
108
|
+
pbcopy(`lxc profile create admin-profile`);
|
|
109
|
+
} else {
|
|
110
|
+
shellExec(`cat ${underpostRoot}/manifests/lxd/lxd-admin-profile.yaml | lxc profile edit admin-profile`);
|
|
111
|
+
shellExec(`lxc profile show admin-profile`);
|
|
112
|
+
}
|
|
92
113
|
}
|
|
93
114
|
if (options.createVm && typeof options.createVm === 'string') {
|
|
94
115
|
pbcopy(
|
|
@@ -108,9 +129,10 @@ ipv6.address=none`);
|
|
|
108
129
|
// Default to kubeadm if not K3s
|
|
109
130
|
flag = ' -s -- --kubeadm';
|
|
110
131
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
await UnderpostLxd.API.runWorkflow({
|
|
133
|
+
workflowId: 'engine',
|
|
134
|
+
vmName: options.initVm,
|
|
135
|
+
});
|
|
114
136
|
} else if (options.worker == true) {
|
|
115
137
|
if (options.k3s === true) {
|
|
116
138
|
flag = ' -s -- --worker --k3s';
|
|
@@ -123,74 +145,16 @@ ipv6.address=none`);
|
|
|
123
145
|
shellExec(`cat ${underpostRoot}/manifests/lxd/underpost-setup.sh | lxc exec ${options.initVm} -- bash${flag}`);
|
|
124
146
|
console.log(`underpost-setup.sh execution completed on VM: ${options.initVm}`);
|
|
125
147
|
}
|
|
126
|
-
// --- Automatic Kubernetes Port Exposure ---
|
|
127
|
-
if (options.autoExposeK8sPorts && typeof options.autoExposeK8sPorts === 'string') {
|
|
128
|
-
console.log(`Automatically exposing Kubernetes ports for VM: ${options.autoExposeK8sPorts}`);
|
|
129
|
-
const vmName = options.autoExposeK8sPorts;
|
|
130
|
-
const hostIp = getLocalIPv4Address();
|
|
131
|
-
let vmIp = '';
|
|
132
|
-
let retries = 0;
|
|
133
|
-
const maxRetries = 10;
|
|
134
|
-
const delayMs = 5000; // 5 seconds
|
|
135
148
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
).trim();
|
|
144
|
-
if (vmIp) {
|
|
145
|
-
console.log(`IPv4 address found for ${vmName}: ${vmIp}`);
|
|
146
|
-
} else {
|
|
147
|
-
console.log(`IPv4 address not yet available for ${vmName}. Retrying in ${delayMs / 1000} seconds...`);
|
|
148
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
149
|
-
}
|
|
150
|
-
} catch (error) {
|
|
151
|
-
console.error(`Error getting IPv4 address for exposure: ${error.message}`);
|
|
152
|
-
console.log(`Retrying in ${delayMs / 1000} seconds...`);
|
|
153
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
154
|
-
}
|
|
155
|
-
retries++;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!vmIp) {
|
|
159
|
-
console.error(`Failed to get VM IP for ${vmName} after ${maxRetries} attempts. Cannot expose ports.`);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
let portsToExpose = [];
|
|
164
|
-
if (options.control === true) {
|
|
165
|
-
// Kubernetes API Server (Kubeadm and K3s both use 6443 by default)
|
|
166
|
-
portsToExpose.push('6443');
|
|
167
|
-
// Standard HTTP/HTTPS for Ingress if deployed
|
|
168
|
-
portsToExpose.push('80');
|
|
169
|
-
portsToExpose.push('443');
|
|
170
|
-
}
|
|
171
|
-
// Add common NodePorts if needed, or rely on explicit 'expose'
|
|
172
|
-
portsToExpose.push('30000'); // Example NodePort
|
|
173
|
-
portsToExpose.push('30001'); // Example NodePort
|
|
174
|
-
portsToExpose.push('30002'); // Example NodePort
|
|
175
|
-
|
|
176
|
-
const protocols = ['tcp']; // Most K8s services are TCP, UDP for some like DNS
|
|
177
|
-
|
|
178
|
-
for (const port of portsToExpose) {
|
|
179
|
-
for (const protocol of protocols) {
|
|
180
|
-
const deviceName = `${vmName}-${protocol}-port-${port}`;
|
|
181
|
-
try {
|
|
182
|
-
// Remove existing device first to avoid conflicts if re-running
|
|
183
|
-
shellExec(`lxc config device remove ${vmName} ${deviceName} || true`);
|
|
184
|
-
shellExec(
|
|
185
|
-
`lxc config device add ${vmName} ${deviceName} proxy listen=${protocol}:${hostIp}:${port} connect=${protocol}:${vmIp}:${port} nat=true`,
|
|
186
|
-
);
|
|
187
|
-
console.log(`Exposed ${protocol}:${hostIp}:${port} -> ${vmIp}:${port} for ${vmName}`);
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error(`Failed to expose port ${port} for ${vmName}: ${error.message}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
149
|
+
if (options.workflowId) {
|
|
150
|
+
await UnderpostLxd.API.runWorkflow({
|
|
151
|
+
workflowId: options.workflowId,
|
|
152
|
+
vmName: options.vmId,
|
|
153
|
+
deployId: options.deployId,
|
|
154
|
+
dev: options.dev,
|
|
155
|
+
});
|
|
193
156
|
}
|
|
157
|
+
|
|
194
158
|
if (options.joinNode && typeof options.joinNode === 'string') {
|
|
195
159
|
const [workerNode, controlNode] = options.joinNode.split(',');
|
|
196
160
|
// Determine if it's a Kubeadm or K3s join
|
|
@@ -396,6 +360,41 @@ ipv6.address=none`);
|
|
|
396
360
|
console.log(`\nComprehensive test for VM: ${vmName} completed.`);
|
|
397
361
|
}
|
|
398
362
|
},
|
|
363
|
+
/**
|
|
364
|
+
* @method runWorkflow
|
|
365
|
+
* @description Executes predefined workflows on LXD VMs.
|
|
366
|
+
* @param {object} params - Parameters for the workflow.
|
|
367
|
+
* @param {string} params.workflowId - The workflow id to execute (e.g., 'init').
|
|
368
|
+
* @param {string} params.vmName - The name of the VM to run the workflow on.
|
|
369
|
+
* @param {string} [params.deployId] - Optional deployment identifier.
|
|
370
|
+
* @param {boolean} [params.dev=false] - Run in development mode (adjusts paths).
|
|
371
|
+
* @memberof UnderpostLxd
|
|
372
|
+
*/
|
|
373
|
+
async runWorkflow({ workflowId, vmName, deployId, dev }) {
|
|
374
|
+
switch (workflowId) {
|
|
375
|
+
case 'engine': {
|
|
376
|
+
const basePath = `/home/dd`;
|
|
377
|
+
const subDir = 'engine';
|
|
378
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'rm ${basePath} && mkdir -p ${basePath}/${subDir}'`);
|
|
379
|
+
shellExec(`lxc file push ${basePath}/${subDir}/package.json ${vmName}${basePath}/${subDir}/package.json`);
|
|
380
|
+
shellExec(`lxc file push ${basePath}/${subDir}/src ${vmName}${basePath}/${subDir} --recursive`);
|
|
381
|
+
shellExec(`lxc file push ${basePath}/${subDir}/${subDir}-private ${vmName}${basePath}/${subDir} --recursive`);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case 'setup-underpost-engine': {
|
|
385
|
+
const basePath = `/home/dd/engine`;
|
|
386
|
+
shellExec(`lxc exec ${vmName} -- bash -lc 'nvm use $(node --version) && cd ${basePath} && npm install'`);
|
|
387
|
+
shellExec(`lxc exec ${vmName} -- bash -lc 'underpost run secret'`);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
case 'k3s-setup': {
|
|
391
|
+
shellExec(
|
|
392
|
+
`lxc exec ${vmName} -- bash -lc 'cd /home/dd/engine && node bin cluster --dev --reset && node bin cluster --dev --k3s'`,
|
|
393
|
+
);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
399
398
|
};
|
|
400
399
|
}
|
|
401
400
|
|
package/src/cli/monitor.js
CHANGED
|
@@ -37,9 +37,9 @@ class UnderpostMonitor {
|
|
|
37
37
|
* @param {boolean} [options.single=false] - Perform a single health check and exit.
|
|
38
38
|
* @param {string} [options.msInterval=''] - Interval in milliseconds for periodic health checks.
|
|
39
39
|
* @param {string} [options.type=''] - Type of deployment (e.g., 'blue-green', 'remote').
|
|
40
|
-
* @param {string} [options.replicas=''] - Number of replicas for the deployment.
|
|
40
|
+
* @param {string} [options.replicas='1'] - Number of replicas for the deployment. Defaults to 1.
|
|
41
41
|
* @param {boolean} [options.sync=false] - Synchronize traffic switching with the deployment.
|
|
42
|
-
* @param {string} [options.namespace=''] - Kubernetes namespace for the deployment.
|
|
42
|
+
* @param {string} [options.namespace='default'] - Kubernetes namespace for the deployment. Defaults to 'default'.
|
|
43
43
|
* @param {object} [commanderOptions] - Options passed from the command line interface.
|
|
44
44
|
* @param {object} [auxRouter] - Optional router configuration for the deployment.
|
|
45
45
|
* @memberof UnderpostMonitor
|
|
@@ -47,11 +47,20 @@ class UnderpostMonitor {
|
|
|
47
47
|
async callback(
|
|
48
48
|
deployId,
|
|
49
49
|
env = 'development',
|
|
50
|
-
options = {
|
|
50
|
+
options = {
|
|
51
|
+
now: false,
|
|
52
|
+
single: false,
|
|
53
|
+
msInterval: '',
|
|
54
|
+
type: '',
|
|
55
|
+
replicas: '1',
|
|
56
|
+
sync: false,
|
|
57
|
+
namespace: 'default',
|
|
58
|
+
},
|
|
51
59
|
commanderOptions,
|
|
52
60
|
auxRouter,
|
|
53
61
|
) {
|
|
54
62
|
if (!options.namespace) options.namespace = 'default';
|
|
63
|
+
if (!options.replicas) options.replicas = '1';
|
|
55
64
|
if (deployId === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`)) {
|
|
56
65
|
for (const _deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(','))
|
|
57
66
|
UnderpostMonitor.API.callback(
|
|
@@ -97,10 +106,10 @@ class UnderpostMonitor {
|
|
|
97
106
|
if (traffic === 'blue') traffic = 'green';
|
|
98
107
|
else traffic = 'blue';
|
|
99
108
|
UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, traffic);
|
|
100
|
-
const namespace = options.namespace
|
|
109
|
+
const namespace = options.namespace;
|
|
101
110
|
shellExec(
|
|
102
111
|
`node bin deploy --info-router --build-manifest --traffic ${traffic} --replicas ${
|
|
103
|
-
options.replicas
|
|
112
|
+
options.replicas
|
|
104
113
|
} --namespace ${namespace} ${deployId} ${env}`,
|
|
105
114
|
);
|
|
106
115
|
shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml -n ${namespace}`);
|
|
@@ -155,7 +164,7 @@ class UnderpostMonitor {
|
|
|
155
164
|
fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'),
|
|
156
165
|
);
|
|
157
166
|
|
|
158
|
-
const namespace = options.namespace
|
|
167
|
+
const namespace = options.namespace;
|
|
159
168
|
UnderpostDeploy.API.configMap(env, namespace);
|
|
160
169
|
|
|
161
170
|
for (const host of Object.keys(confServer)) {
|
|
@@ -208,11 +217,13 @@ class UnderpostMonitor {
|
|
|
208
217
|
monitorTrafficName = undefined;
|
|
209
218
|
monitorPodName = undefined;
|
|
210
219
|
}
|
|
211
|
-
const checkDeploymentReadyStatus = () => {
|
|
212
|
-
const { ready, notReadyPods, readyPods } = UnderpostDeploy.API.checkDeploymentReadyStatus(
|
|
220
|
+
const checkDeploymentReadyStatus = async () => {
|
|
221
|
+
const { ready, notReadyPods, readyPods } = await UnderpostDeploy.API.checkDeploymentReadyStatus(
|
|
213
222
|
deployId,
|
|
214
223
|
env,
|
|
215
224
|
traffic,
|
|
225
|
+
[],
|
|
226
|
+
options.namespace,
|
|
216
227
|
);
|
|
217
228
|
if (ready) {
|
|
218
229
|
monitorPodName = readyPods[0].NAME;
|
|
@@ -220,7 +231,7 @@ class UnderpostMonitor {
|
|
|
220
231
|
}
|
|
221
232
|
};
|
|
222
233
|
if (!monitorPodName) {
|
|
223
|
-
checkDeploymentReadyStatus();
|
|
234
|
+
await checkDeploymentReadyStatus();
|
|
224
235
|
monitorCallBack(resolve, reject);
|
|
225
236
|
return;
|
|
226
237
|
}
|