underpost 2.92.0 → 2.95.1

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.
Files changed (35) hide show
  1. package/.github/workflows/pwa-microservices-template-page.cd.yml +5 -4
  2. package/README.md +4 -5
  3. package/bin/build.js +6 -1
  4. package/bin/deploy.js +2 -69
  5. package/cli.md +99 -92
  6. package/manifests/deployment/dd-default-development/deployment.yaml +4 -4
  7. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  8. package/package.json +1 -1
  9. package/scripts/disk-clean.sh +216 -0
  10. package/scripts/ssh-cluster-info.sh +4 -3
  11. package/src/cli/cluster.js +1 -1
  12. package/src/cli/db.js +71 -80
  13. package/src/cli/deploy.js +77 -13
  14. package/src/cli/image.js +198 -133
  15. package/src/cli/index.js +59 -81
  16. package/src/cli/lxd.js +73 -74
  17. package/src/cli/monitor.js +20 -9
  18. package/src/cli/repository.js +86 -3
  19. package/src/cli/run.js +167 -63
  20. package/src/cli/ssh.js +351 -134
  21. package/src/index.js +1 -1
  22. package/src/monitor.js +11 -1
  23. package/src/server/backup.js +1 -1
  24. package/src/server/conf.js +1 -1
  25. package/src/server/dns.js +88 -1
  26. package/src/server/process.js +6 -1
  27. package/scripts/snap-clean.sh +0 -26
  28. package/src/client/public/default/plantuml/client-conf.svg +0 -1
  29. package/src/client/public/default/plantuml/client-schema.svg +0 -1
  30. package/src/client/public/default/plantuml/cron-conf.svg +0 -1
  31. package/src/client/public/default/plantuml/cron-schema.svg +0 -1
  32. package/src/client/public/default/plantuml/server-conf.svg +0 -1
  33. package/src/client/public/default/plantuml/server-schema.svg +0 -1
  34. package/src/client/public/default/plantuml/ssr-conf.svg +0 -1
  35. 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
@@ -35,6 +34,8 @@ program
35
34
  .option('--sync-conf', 'Sync configuration to private repositories (requires --deploy-id)')
36
35
  .option('--purge', 'Remove deploy ID conf and all related repositories (requires --deploy-id)')
37
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')
38
39
  .description('Initializes a new Underpost project, service, or configuration.')
39
40
  .action(Underpost.repo.new);
40
41
 
@@ -256,57 +257,7 @@ program
256
257
  .option('--ban-both-add', 'Adds IP addresses to both banned ingress and egress lists.')
257
258
  .option('--ban-both-remove', 'Removes IP addresses from both banned ingress and egress lists.')
258
259
  .description('Displays the current public machine IP addresses.')
259
- .action(async (ips = '', options) => {
260
- const ipList = ips
261
- ? ips
262
- .split(',')
263
- .map((i) => i.trim())
264
- .filter(Boolean)
265
- : [];
266
-
267
- if (options.banIngressAdd) {
268
- return ipList.forEach((ip) => Dns.banIngress(ip));
269
- }
270
- if (options.banIngressRemove) {
271
- return ipList.forEach((ip) => Dns.unbanIngress(ip));
272
- }
273
- if (options.banIngressList) {
274
- return Dns.listBannedIngress();
275
- }
276
- if (options.banIngressClear) {
277
- return Dns.clearBannedIngress();
278
- }
279
-
280
- if (options.banEgressAdd) {
281
- return ipList.forEach((ip) => Dns.banEgress(ip));
282
- }
283
- if (options.banEgressRemove) {
284
- return ipList.forEach((ip) => Dns.unbanEgress(ip));
285
- }
286
- if (options.banEgressList) {
287
- return Dns.listBannedEgress();
288
- }
289
- if (options.banEgressClear) {
290
- return Dns.clearBannedEgress();
291
- }
292
-
293
- if (options.banBothAdd) {
294
- return ipList.forEach((ip) => {
295
- Dns.banIngress(ip);
296
- Dns.banEgress(ip);
297
- });
298
- }
299
- if (options.banBothRemove) {
300
- return ipList.forEach((ip) => {
301
- Dns.unbanIngress(ip);
302
- Dns.unbanEgress(ip);
303
- });
304
- }
305
-
306
- const ip = await Dns.getPublicIp();
307
- if (options.copy) return pbcopy(ip);
308
- return console.log(ip);
309
- });
260
+ .action(Dns.ipDispatcher);
310
261
 
311
262
  // 'cluster' command: Manage Kubernetes clusters
312
263
  program
@@ -412,37 +363,41 @@ program
412
363
  if (args[1].init) return Underpost.secret[args[0]].init();
413
364
  });
414
365
 
415
- // 'dockerfile-image-build' command: Build Docker images from Dockerfiles
366
+ // 'image'
416
367
  program
417
- .command('dockerfile-image-build')
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.')
418
375
  .option('--path [path]', 'The path to the Dockerfile directory.')
419
376
  .option('--image-name [image-name]', 'Sets a custom name for the Docker image.')
420
377
  .option('--image-path [image-path]', 'Sets the output path for the tar image archive.')
421
378
  .option('--dockerfile-name [dockerfile-name]', 'Sets a custom name for the Dockerfile.')
422
379
  .option('--podman-save', 'Exports the built image as a tar file using Podman.')
423
- .option('--kind-load', 'Imports the tar image into a Kind cluster.')
424
- .option('--kubeadm-load', 'Imports the tar image into a Kubeadm cluster.')
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.')
425
387
  .option('--secrets', 'Includes Dockerfile environment secrets during the build.')
426
388
  .option('--secrets-path [secrets-path]', 'Specifies a custom path for Dockerfile environment secrets.')
427
389
  .option('--reset', 'Performs a build without using the cache.')
428
390
  .option('--dev', 'Use development mode.')
429
- .option('--k3s-load', 'Loads the image into a K3s cluster.')
430
- .description(
431
- 'Builds a Docker image from a specified Dockerfile with various options for naming, saving, and loading.',
432
- )
433
- .action(Underpost.image.dockerfile.build);
434
-
435
- // 'dockerfile-pull-base-images' command: Pull base Dockerfile images
436
- program
437
- .command('dockerfile-pull-base-images')
438
- .option('--path [path]', 'The path to the Dockerfile directory.')
439
- .option('--kind-load', 'Imports the pulled image into a Kind cluster.')
440
- .option('--kubeadm-load', 'Imports the pulled image into a Kubeadm cluster.')
441
- .option('--version', 'Sets a custom version for the base images.')
442
- .option('--k3s-load', 'Loads the image into a K3s cluster.')
443
- .option('--dev', 'Use development mode.')
444
- .description('Pulls required Underpost Dockerfile base images and optionally loads them into clusters.')
445
- .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
+ });
446
401
 
447
402
  // 'install' command: Fast import npm dependencies
448
403
  program
@@ -475,7 +430,10 @@ program
475
430
  .option('--hosts <hosts>', 'Comma-separated list of database hosts to filter operations.')
476
431
  .option('--paths <paths>', 'Comma-separated list of paths to filter database operations.')
477
432
  .option('--ns <ns-name>', 'Kubernetes namespace context for database operations (defaults to "default").')
478
- .option('--dry-run', 'Simulates operations without executing them (useful for testing).')
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
+ )
479
437
  .description(
480
438
  'Manages database operations with support for MariaDB and MongoDB, including import/export, multi-pod targeting, and Git integration.',
481
439
  )
@@ -564,9 +522,10 @@ program
564
522
  .option('--ms-interval <ms-interval>', 'Sets a custom millisecond interval for monitoring checks.')
565
523
  .option('--now', 'Executes the monitor script immediately.')
566
524
  .option('--single', 'Disables recurrence, running the monitor script only once.')
567
- .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.')
568
526
  .option('--type <type>', 'Sets a custom monitor type.')
569
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".')
570
529
  .description('Manages health server monitoring for specified deployments.')
571
530
  .action(Underpost.monitor.callback);
572
531
 
@@ -589,13 +548,18 @@ program
589
548
  .option('--keys-list', 'Lists all ssh keys from current private keys file storage.')
590
549
  .option('--hosts-list', 'Lists all ssh hosts from current private keys file storage.')
591
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.')
592
556
  .action(Underpost.ssh.callback);
593
557
 
594
558
  // 'run' command: Run a script
595
559
  program
596
560
  .command('run')
597
561
  .argument('<runner-id>', `The runner ID to run. Options: ${Object.keys(UnderpostRun.RUNNERS).join(', ')}.`)
598
- .argument('[path]', 'The absolute or relative directory path where the script is located.')
562
+ .argument('[path]', 'The input value, identifier, or path for the operation.')
599
563
  .option('--command <command-array>', 'Array of commands to run.')
600
564
  .option('--args <args-array>', 'Array of arguments to pass to the command.')
601
565
  .option('--dev', 'Sets the development context environment for the script.')
@@ -622,9 +586,10 @@ program
622
586
  'Optional: Specifies a comma-separated list of key-value pairs for labels (e.g., "app=my-app,env=prod").',
623
587
  )
624
588
  .option('--claim-name <name>', 'Optional: Specifies the claim name for volume mounting in deploy-job.')
625
- .option('--kind <kind-type>', 'Specifies the kind of Kubernetes resource (e.g., Job, Deployment) for deploy-job.')
626
- .option('--kubeadm', 'Flag to indicate Kubeadm cluster type context')
627
- .option('--k3s', 'Flag to indicate K3s cluster type context')
589
+ .option(
590
+ '--kind-type <kind-type>',
591
+ 'Specifies the kind of Kubernetes resource (e.g., Job, Deployment) for deploy-job.',
592
+ )
628
593
  .option('--force', 'Forces operation, overriding any warnings or conflicts.')
629
594
  .option('--tls', 'Enables TLS for the runner execution.')
630
595
  .option('--reset', 'Resets the runner state before execution.')
@@ -642,7 +607,17 @@ program
642
607
  .option('--expose', 'Enables service exposure for the runner execution.')
643
608
  .option('--conf-server-path <conf-server-path>', 'Sets a custom configuration server path.')
644
609
  .option('--underpost-root <underpost-root>', 'Sets a custom Underpost root path.')
645
- .description('Runs a script from the specified path.')
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.')
646
621
  .action(UnderpostRun.API.callback);
647
622
 
648
623
  // 'lxd' command: LXD management
@@ -674,7 +649,10 @@ program
674
649
  '--delete-expose <vm-name-ports>',
675
650
  'Removes exposed ports on a VM (e.g., "k8s-control:80,443"). Multiple VM-port pairs can be comma-separated.',
676
651
  )
677
- .option('--auto-expose-k8s-ports <vm-id>', 'Automatically exposes common Kubernetes ports for the specified VM.')
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").')
678
656
  .description('Manages LXD containers and virtual machines.')
679
657
  .action(UnderpostLxd.API.callback);
680
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.autoExposeK8sPorts=''] - Automatically expose common Kubernetes ports for the VM.
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
- autoExposeK8sPorts: '',
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
- pbcopy(`lxc profile create admin-profile`);
90
- shellExec(`cat ${underpostRoot}/manifests/lxd/lxd-admin-profile.yaml | lxc profile edit admin-profile`);
91
- shellExec(`lxc profile show admin-profile`);
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
- shellExec(`lxc exec ${options.initVm} -- bash -c 'mkdir -p /home/dd/engine'`);
112
- shellExec(`lxc file push /home/dd/engine/engine-private ${options.initVm}/home/dd/engine --recursive`);
113
- shellExec(`lxc file push /home/dd/engine/manifests ${options.initVm}/home/dd/engine --recursive`);
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
- // Wait for VM to get an IP address
137
- while (!vmIp && retries < maxRetries) {
138
- try {
139
- console.log(`Attempting to get IPv4 address for ${vmName} (Attempt ${retries + 1}/${maxRetries})...`);
140
- vmIp = shellExec(
141
- `lxc list ${vmName} --format json | jq -r '.[0].state.network.enp5s0.addresses[] | select(.family=="inet") | .address'`,
142
- { stdout: true },
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
 
@@ -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 = { now: false, single: false, msInterval: '', type: '', replicas: '', sync: false, namespace: '' },
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 || 'default';
109
+ const namespace = options.namespace;
101
110
  shellExec(
102
111
  `node bin deploy --info-router --build-manifest --traffic ${traffic} --replicas ${
103
- options.replicas ? options.replicas : 1
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 || 'default';
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
  }
@@ -10,8 +10,8 @@ import { pbcopy, shellCd, shellExec } from '../server/process.js';
10
10
  import { actionInitLog, loggerFactory } from '../server/logger.js';
11
11
  import fs from 'fs-extra';
12
12
  import { getNpmRootPath } from '../server/conf.js';
13
- import UnderpostStartUp from '../server/start.js';
14
13
  import { Config } from '../server/conf.js';
14
+ import { DefaultConf } from '../../conf.js';
15
15
 
16
16
  dotenv.config();
17
17
 
@@ -244,6 +244,8 @@ class UnderpostRepository {
244
244
  * @param {boolean} [options.cleanTemplate=false] - If true, cleans the pwa-microservices-template build directory.
245
245
  * @param {boolean} [options.build=false] - If true, builds the deployment to pwa-microservices-template (requires deployId).
246
246
  * @param {boolean} [options.syncConf=false] - If true, syncs configuration to private repositories (requires deployId).
247
+ * @param {boolean} [options.defaultConf=false] - If true, updates the default configuration file (requires deployId).
248
+ * @param {string} [options.confWorkflowId=''] - If provided, uses this configuration workflow ID.
247
249
  * @returns {Promise<boolean>} A promise that resolves when the initialization is complete.
248
250
  * @memberof UnderpostRepository
249
251
  */
@@ -259,6 +261,8 @@ class UnderpostRepository {
259
261
  cleanTemplate: false,
260
262
  build: false,
261
263
  syncConf: false,
264
+ defaultConf: false,
265
+ confWorkflowId: '',
262
266
  },
263
267
  ) {
264
268
  return new Promise(async (resolve, reject) => {
@@ -277,10 +281,15 @@ class UnderpostRepository {
277
281
  return resolve(true);
278
282
  }
279
283
 
284
+ // Handle defaultConf operation
285
+ if (options.defaultConf) {
286
+ UnderpostRepository.API.updateDefaultConf(options);
287
+ return resolve(true);
288
+ }
289
+
280
290
  if (options.deployId) {
281
291
  let deployId = options.deployId;
282
292
  if (!deployId.startsWith('dd-')) deployId = `dd-${deployId}`;
283
-
284
293
  // Handle purge operation
285
294
  if (options.purge) {
286
295
  logger.info(`Purging deploy ID: ${deployId}`);
@@ -450,7 +459,9 @@ class UnderpostRepository {
450
459
  const privateRepoPath = `../${privateRepoName}`;
451
460
  if (fs.existsSync(privateRepoPath)) fs.removeSync(privateRepoPath);
452
461
  shellExec(`cd .. && underpost clone ${process.env.GITHUB_USERNAME}/${privateRepoName}`);
453
- shellExec(`cd ${privateRepoPath} && underpost pull . ${process.env.GITHUB_USERNAME}/${privateRepoName}`);
462
+ shellExec(`cd ${privateRepoPath} && underpost pull . ${process.env.GITHUB_USERNAME}/${privateRepoName}`, {
463
+ silent: true,
464
+ });
454
465
  shellExec(`underpost run secret`);
455
466
  shellExec(`underpost run underpost-config`);
456
467
  const packageJsonDeploy = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/package.json`, 'utf8'));
@@ -504,6 +515,78 @@ Prevent build private config repo.`,
504
515
  return line;
505
516
  });
506
517
  },
518
+ /**
519
+ * Updates the default configuration file based on the provided options.
520
+ * @param {object} [options={ deployId: '' }] - The options for updating the configuration.
521
+ * @param {string} [options.deployId=''] - The deployment ID to use for configuration.
522
+ * @param {string} [options.confWorkflowId=''] - The configuration workflow ID to use.
523
+ * @memberof UnderpostRepository
524
+ */
525
+ updateDefaultConf(options = { deployId: '', confWorkflowId: '' }) {
526
+ const defaultServer = DefaultConf.server['default.net']['/'];
527
+ let { deployId, confWorkflowId } = options;
528
+ let defaultConf = false;
529
+
530
+ // Custom workflow configurations
531
+ if (confWorkflowId)
532
+ switch (confWorkflowId) {
533
+ case 'dd-github-pages': {
534
+ const host = `${process.env.GITHUB_USERNAME ? process.env.GITHUB_USERNAME : 'underpostnet'}.github.io`;
535
+ const path = '/pwa-microservices-template-ghpkg';
536
+ DefaultConf.server = {
537
+ [host]: { [path]: defaultServer },
538
+ };
539
+ DefaultConf.server[host][path].apiBaseProxyPath = '/';
540
+ DefaultConf.server[host][path].apiBaseHost = 'www.nexodev.org';
541
+ defaultConf = true;
542
+ break;
543
+ }
544
+ case 'template': {
545
+ const host = 'default.net';
546
+ const path = '/';
547
+ DefaultConf.server[host][path].valkey = {
548
+ port: 6379,
549
+ host: 'valkey-service.default.svc.cluster.local',
550
+ };
551
+ // mongodb-0.mongodb-service
552
+ DefaultConf.server[host][path].db.host = 'mongodb://mongodb-service:27017';
553
+ defaultConf = true;
554
+ break;
555
+ }
556
+ default:
557
+ logger.error(`Unknown confWorkflowId: ${confWorkflowId}.`);
558
+ return;
559
+ }
560
+ else if (deployId && fs.existsSync(`./engine-private/conf/${deployId}`)) {
561
+ DefaultConf.client = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.client.json`, 'utf8'));
562
+ DefaultConf.server = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
563
+ DefaultConf.ssr = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.ssr.json`, 'utf8'));
564
+ // DefaultConf.cron = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.cron.json`, 'utf8'));
565
+
566
+ for (const host of Object.keys(DefaultConf.server)) {
567
+ for (const path of Object.keys(DefaultConf.server[host])) {
568
+ DefaultConf.server[host][path].db = defaultServer.db;
569
+ DefaultConf.server[host][path].mailer = defaultServer.mailer;
570
+
571
+ delete DefaultConf.server[host][path]._wp_client;
572
+ delete DefaultConf.server[host][path]._wp_git;
573
+ delete DefaultConf.server[host][path]._wp_directory;
574
+ delete DefaultConf.server[host][path].wp;
575
+ delete DefaultConf.server[host][path].git;
576
+ delete DefaultConf.server[host][path].directory;
577
+ }
578
+ }
579
+ } else
580
+ logger.warn(
581
+ `Deploy ID configuration not found: ./engine-private/conf/${deployId}, using default configuration.`,
582
+ );
583
+ const sepRender = '/**/';
584
+ const confRawPaths = fs.readFileSync('./conf.js', 'utf8').split(sepRender);
585
+ confRawPaths[1] = `${JSON.stringify(DefaultConf)};`;
586
+ const targetConfPath = `./conf${defaultConf ? '' : `.${deployId}`}.js`;
587
+ fs.writeFileSync(targetConfPath, confRawPaths.join(sepRender), 'utf8');
588
+ shellExec(`prettier --write ${targetConfPath}`);
589
+ },
507
590
  };
508
591
  }
509
592