@underpostnet/underpost 2.99.5 → 2.99.6

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.
@@ -122,8 +122,6 @@ ${Underpost.baremetal.stepsRender(
122
122
  `cloud-init modules --mode=config`,
123
123
  `sleep 3`,
124
124
  `cloud-init modules --mode=final`,
125
- `sleep 3`,
126
- `/underpost/enlistment.sh`,
127
125
  ],
128
126
  false,
129
127
  )}`,
@@ -190,45 +188,6 @@ cat /etc/default/keyboard`,
190
188
  logger.info('Build', `${nfsHostToolsPath}/device_scan.sh`);
191
189
  fs.copySync(`${underpostRoot}/scripts/device-scan.sh`, `${nfsHostToolsPath}/device_scan.sh`);
192
190
 
193
- // Build and write the MAAS enlistment script.
194
- logger.info('Build', `${nfsHostToolsPath}/enlistment.sh`);
195
- fs.writeFileSync(
196
- `${nfsHostToolsPath}/enlistment.sh`,
197
- `#!/bin/bash
198
- set -x
199
-
200
- # ------------------------------------------------------------
201
- # Step: Commission a machine in MAAS using OAuth1 authentication
202
- # ------------------------------------------------------------
203
-
204
- MACHINE_ID=$(cat /underpost/system-id)
205
- CONSUMER_KEY=$(cat /underpost/consumer-key)
206
- TOKEN_KEY=$(cat /underpost/token-key)
207
- TOKEN_SECRET=$(cat /underpost/token-secret)
208
-
209
- echo ">>> Starting MAAS machine commissioning for system_id: $MACHINE_ID …"
210
-
211
- curl -X POST \\
212
- --fail --location --verbose --include --raw --trace-ascii /dev/stdout\\
213
- --header "Authorization:\\
214
- OAuth oauth_version=1.0,\\
215
- oauth_signature_method=PLAINTEXT,\\
216
- oauth_consumer_key=$CONSUMER_KEY,\\
217
- oauth_token=$TOKEN_KEY,\\
218
- oauth_signature=&$TOKEN_SECRET,\\
219
- oauth_nonce=$(uuidgen),\\
220
- oauth_timestamp=$(date +%s)"\\
221
- -F "commissioning_scripts=20-maas-01-install-lldpd"\\
222
- -F "enable_ssh=1"\\
223
- -F "skip_bmc_config=1"\\
224
- -F "skip_networking=1"\\
225
- -F "skip_storage=1"\\
226
- -F "testing_scripts=none"\\
227
- http://${callbackMetaData.runnerHost.ip}:5240/MAAS/api/2.0/machines/$MACHINE_ID/op-commission \\
228
- 2>&1 | tee /underpost/enlistment.log || echo "ERROR: MAAS commissioning returned code $?"`,
229
- 'utf8',
230
- );
231
-
232
191
  // Import SSH keys for root user.
233
192
  logger.info('Import ssh keys');
234
193
  shellExec(`sudo rm -rf ${nfsHostPath}/root/.ssh`);
@@ -659,7 +618,30 @@ curl -X POST \\
659
618
  }
660
619
 
661
620
  return yaml.join('\n');
662
- } /**
621
+ },
622
+
623
+ /**
624
+ * @method httpServerStaticFactory
625
+ * @description Writes cloud-init files (user-data, meta-data, vendor-data) to the bootstrap HTTP server path.
626
+ * @param {object} params
627
+ * @param {string} params.bootstrapHttpServerPath
628
+ * @param {string} params.hostname
629
+ * @param {string} params.cloudConfigSrc
630
+ * @param {string} [params.vendorData='']
631
+ * @memberof UnderpostCloudInit
632
+ * @returns {void}
633
+ */
634
+ httpServerStaticFactory({ bootstrapHttpServerPath, hostname, cloudConfigSrc, vendorData = '' }) {
635
+ if (!cloudConfigSrc) return;
636
+ const dir = `${bootstrapHttpServerPath}/${hostname}/cloud-init`;
637
+ shellExec(`mkdir -p ${dir}`);
638
+ fs.writeFileSync(`${dir}/user-data`, cloudConfigSrc, 'utf8');
639
+ fs.writeFileSync(`${dir}/meta-data`, `instance-id: ${hostname}\nlocal-hostname: ${hostname}`, 'utf8');
640
+ fs.writeFileSync(`${dir}/vendor-data`, vendorData, 'utf8');
641
+ logger.info(`Cloud-init files written to ${dir}`);
642
+ },
643
+
644
+ /**
663
645
  * @method kernelParamsFactory
664
646
  * @description Generates the kernel parameters for the target machine's bootloader configuration,
665
647
  * including the necessary parameters to enable cloud-init with a specific configuration URL and logging settings.
@@ -670,7 +652,7 @@ curl -X POST \\
670
652
  * @param {string} [options.machine.system_id] - The unique identifier of the machine, used to fetch the correct cloud-init preseed configuration from MAAS.
671
653
  * @return {array} The modified array of kernel parameters with cloud-init parameters included.
672
654
  * @memberof UnderpostCloudInit
673
- */,
655
+ */
674
656
  kernelParamsFactory(
675
657
  macAddress,
676
658
  cmd = [],
package/src/cli/index.js CHANGED
@@ -150,6 +150,11 @@ program
150
150
  .option('--dir <dir>', 'HTML dir attribute (default: ltr).')
151
151
  .option('--dev', 'Sets the development cli context')
152
152
 
153
+ .option(
154
+ '--run-sv [port]',
155
+ 'Start a standalone Express static server to preview the static build (default port: 5000).',
156
+ )
157
+
153
158
  .description(`Manages static build of page, bundles, and documentation with comprehensive customization options.`)
154
159
  .action(Underpost.static.callback);
155
160
 
@@ -402,10 +407,26 @@ program
402
407
  '[job-list]',
403
408
  `A comma-separated list of job IDs. Options: ${Underpost.cron.getJobsIDs()}. Defaults to all available jobs.`,
404
409
  )
405
- .option('--init-pm2-cronjobs', 'Initializes PM2 cron jobs from configuration for the specified deployment IDs.')
406
- .option('--git', 'Uploads cron job configurations to GitHub.')
407
- .option('--update-package-scripts', 'Updates package.json start scripts for each deploy-id configuration.')
408
- .description('Manages cron jobs, including initialization, execution, and configuration updates.')
410
+ .option('--generate-k8s-cronjobs', 'Generates Kubernetes CronJob YAML manifests from cron configuration.')
411
+ .option('--apply', 'Applies generated K8s CronJob manifests to the cluster via kubectl.')
412
+ .option(
413
+ '--setup-start [deploy-id]',
414
+ 'Updates deploy-id package.json start script and generates+applies its K8s CronJob manifests.',
415
+ )
416
+ .option('--namespace <namespace>', 'Kubernetes namespace for the CronJob resources (default: "default").')
417
+ .option('--image <image>', 'Custom container image for the CronJob pods.')
418
+ .option('--git', 'Pass --git flag to cron job execution.')
419
+ .option('--cmd <cmd>', 'Optional pre-script commands to run before cron execution.')
420
+ .option('--dev', 'Use local ./ base path instead of global underpost installation.')
421
+ .option('--k3s', 'Use k3s cluster context (apply directly on host).')
422
+ .option('--kind', 'Use kind cluster context (apply via kind-worker container).')
423
+ .option('--kubeadm', 'Use kubeadm cluster context (apply directly on host).')
424
+ .option('--dry-run', 'Preview cron jobs without executing them.')
425
+ .option(
426
+ '--create-job-now',
427
+ 'After applying manifests, immediately create a Job from each CronJob (requires --apply).',
428
+ )
429
+ .description('Manages cron jobs: execute jobs directly or generate and apply K8s CronJob manifests.')
409
430
  .action(Underpost.cron.callback);
410
431
 
411
432
  program
@@ -534,7 +555,11 @@ program
534
555
  .option('--expose', 'Enables service exposure for the runner execution.')
535
556
  .option('--conf-server-path <conf-server-path>', 'Sets a custom configuration server path.')
536
557
  .option('--underpost-root <underpost-root>', 'Sets a custom Underpost root path.')
537
- .option('--cron-jobs <jobs>', 'Comma-separated list of cron jobs to run before executing the script.')
558
+ .option('--cmd-cron-jobs <cmd-cron-jobs>', 'Pre-script commands to run before cron job execution.')
559
+ .option(
560
+ '--deploy-id-cron-jobs <deploy-id-cron-jobs>',
561
+ 'Specifies deployment IDs to synchronize cron jobs with during execution.',
562
+ )
538
563
  .option('--timezone <timezone>', 'Sets the timezone for the runner execution.')
539
564
  .option('--kubeadm', 'Sets the kubeadm cluster context for the runner execution.')
540
565
  .option('--k3s', 'Sets the k3s cluster context for the runner execution.')
@@ -567,6 +592,11 @@ program
567
592
  '--monitor-status-max-attempts <attempts>',
568
593
  'Sets the maximum number of status check attempts (default: 600).',
569
594
  )
595
+ .option('--dry-run', 'Preview operations without executing them.')
596
+ .option(
597
+ '--create-job-now',
598
+ 'After applying cron manifests, immediately create a Job from each CronJob (forwarded to cron runner).',
599
+ )
570
600
  .description('Runs specified scripts using various runners.')
571
601
  .action(Underpost.run.callback);
572
602
 
@@ -650,6 +680,10 @@ program
650
680
  .option('--remove-machines <system-ids>', 'Removes baremetal machines by comma-separated system IDs, or use "all"')
651
681
  .option('--clear-discovered', 'Clears all discovered baremetal machines from the database.')
652
682
  .option('--commission', 'Init workflow for commissioning a physical machine.')
683
+ .option(
684
+ '--bootstrap-http-server-run',
685
+ 'Runs a temporary bootstrap HTTP server for generic purposes such as serving iPXE scripts or ISO images during commissioning.',
686
+ )
653
687
  .option(
654
688
  '--bootstrap-http-server-path <path>',
655
689
  'Sets a custom bootstrap HTTP server path for baremetal commissioning.',
@@ -661,6 +695,7 @@ program
661
695
  .option('--iso-url <url>', 'Uses a custom ISO URL for baremetal machine commissioning.')
662
696
  .option('--nfs-build', 'Builds an NFS root filesystem for a workflow id config architecture using QEMU emulation.')
663
697
  .option('--nfs-mount', 'Mounts the NFS root filesystem for a workflow id config architecture.')
698
+ .option('--nfs-reset', 'Resets the NFS server completely, closing all connections before reloading exports.')
664
699
  .option('--nfs-unmount', 'Unmounts the NFS root filesystem for a workflow id config architecture.')
665
700
  .option('--nfs-build-server', 'Builds the NFS server for a workflow id config architecture.')
666
701
  .option('--nfs-sh', 'Copies QEMU emulation root entrypoint shell command to the clipboard.')
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Kickstart configuration generator for Underpost Engine
3
+ * @module src/cli/kickstart.js
4
+ * @namespace UnderpostKickStart
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { loggerFactory } from '../server/logger.js';
11
+
12
+ const logger = loggerFactory(import.meta);
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ class UnderpostKickStart {
17
+ static API = {
18
+ /**
19
+ * @method kickstartHeader
20
+ * @description Generates the kickstart header section with template variables.
21
+ * @param {object} options
22
+ * @param {string} [options.lang='en_US.UTF-8']
23
+ * @param {string} [options.keyboard='us']
24
+ * @param {string} [options.timezone='America/New_York']
25
+ * @param {string} [options.rootPassword]
26
+ * @memberof UnderpostKickStart
27
+ * @returns {string}
28
+ */
29
+ kickstartHeader: ({ lang = 'en_US.UTF-8', keyboard = 'us', timezone = 'America/New_York', rootPassword = '' }) => {
30
+ return [
31
+ '# Rocky Linux 9 Kickstart - Ephemeral Anaconda Live Environment with SSHD',
32
+ 'cmdline',
33
+ 'eula --agreed',
34
+ `keyboard --vckeymap=${keyboard} --xlayouts='${keyboard}'`,
35
+ `lang ${lang}`,
36
+ 'network --bootproto=dhcp --device=link --activate --onboot=yes',
37
+ `timezone ${timezone} --utc`,
38
+ rootPassword ? `rootpw --plaintext ${rootPassword}` : 'rootpw --lock',
39
+ 'firstboot --disable',
40
+ 'skipx',
41
+ ].join('\n');
42
+ },
43
+
44
+ /**
45
+ * @method kickstartPreVariables
46
+ * @description Generates the variable assignments block for the %pre script.
47
+ * @param {object} options
48
+ * @param {string} [options.rootPassword]
49
+ * @param {string} [options.authorizedKeys]
50
+ * @param {string} [options.adminUsername]
51
+ * @memberof UnderpostKickStart
52
+ * @returns {string}
53
+ */
54
+ kickstartPreVariables: ({ rootPassword = '', authorizedKeys = '', adminUsername = '' }) => {
55
+ const sanitizedKeys = (authorizedKeys || '').trim();
56
+ return [
57
+ `ROOT_PASS='${rootPassword || ''}'`,
58
+ `AUTHORIZED_KEYS='${sanitizedKeys}'`,
59
+ `ADMIN_USER='${adminUsername || process.env.MAAS_ADMIN_USERNAME || 'maas'}'`,
60
+ ].join('\n');
61
+ },
62
+
63
+ /**
64
+ * @method kickstartFactory
65
+ * @description Generates a complete kickstart configuration by combining the header,
66
+ * variable assignments, and the rocky-kickstart.sh script body.
67
+ * @param {object} options
68
+ * @param {string} [options.lang='en_US.UTF-8']
69
+ * @param {string} [options.keyboard='us']
70
+ * @param {string} [options.timezone='America/New_York']
71
+ * @param {string} [options.rootPassword]
72
+ * @param {string} [options.authorizedKeys]
73
+ * @memberof UnderpostKickStart
74
+ * @returns {string}
75
+ */
76
+ kickstartFactory: ({
77
+ lang = 'en_US.UTF-8',
78
+ keyboard = 'us',
79
+ timezone = 'America/New_York',
80
+ rootPassword = process.env.MAAS_ADMIN_PASS,
81
+ authorizedKeys = '',
82
+ }) => {
83
+ const adminUsername = process.env.MAAS_ADMIN_USERNAME || 'maas';
84
+ const header = UnderpostKickStart.API.kickstartHeader({ lang, keyboard, timezone, rootPassword });
85
+ const variables = UnderpostKickStart.API.kickstartPreVariables({ rootPassword, authorizedKeys, adminUsername });
86
+
87
+ const scriptPath = path.resolve(__dirname, '../../scripts/rocky-kickstart.sh');
88
+ const scriptBody = fs.readFileSync(scriptPath, 'utf8');
89
+
90
+ return [
91
+ header,
92
+ '',
93
+ '%pre --interpreter=/bin/bash --log=/tmp/ks-pre.log --erroronfail',
94
+ '#!/bin/bash',
95
+ variables,
96
+ '',
97
+ scriptBody,
98
+ '%end',
99
+ ].join('\n');
100
+ },
101
+
102
+ /**
103
+ * @method kernelParamsFactory
104
+ * @description Appends kickstart-specific kernel parameters (inst.ks, inst.repo, inst.text, inst.sshd).
105
+ * @param {string} macAddress - The MAC address of the target machine.
106
+ * @param {array} cmd - The existing array of kernel parameters.
107
+ * @param {object} options - Options for generating kernel parameters.
108
+ * @param {string} options.ipDhcpServer - The IP address of the DHCP server.
109
+ * @param {number} [options.bootstrapHttpServerPort=8888] - Port for the bootstrap HTTP server.
110
+ * @param {string} options.hostname - The hostname of the target machine.
111
+ * @param {string} [options.architecture='amd64'] - The target architecture.
112
+ * @memberof UnderpostKickStart
113
+ * @returns {array}
114
+ */
115
+ kernelParamsFactory(
116
+ macAddress,
117
+ cmd = [],
118
+ options = { ipDhcpServer: '', bootstrapHttpServerPort: 8888, hostname: '', architecture: 'amd64' },
119
+ ) {
120
+ const { ipDhcpServer, bootstrapHttpServerPort, hostname, architecture } = options;
121
+ const repoArch = architecture && architecture.match('arm64') ? 'aarch64' : 'x86_64';
122
+ return cmd.concat([
123
+ `inst.ks=http://${ipDhcpServer}:${bootstrapHttpServerPort}/${hostname}/ks.cfg`,
124
+ `inst.repo=http://dl.rockylinux.org/pub/rocky/9/BaseOS/${repoArch}/os/`,
125
+ `inst.text`,
126
+ `inst.sshd`,
127
+ ]);
128
+ },
129
+
130
+ /**
131
+ * @method httpServerStaticFactory
132
+ * @description Writes kickstart ks.cfg file to the bootstrap HTTP server path.
133
+ * @param {object} params
134
+ * @param {string} params.bootstrapHttpServerPath
135
+ * @param {string} params.hostname
136
+ * @param {string} params.kickstartSrc
137
+ * @memberof UnderpostKickStart
138
+ * @returns {void}
139
+ */
140
+ httpServerStaticFactory({ bootstrapHttpServerPath, hostname, kickstartSrc }) {
141
+ if (!kickstartSrc) return;
142
+ const dest = `${bootstrapHttpServerPath}/${hostname}/ks.cfg`;
143
+ fs.writeFileSync(dest, kickstartSrc, 'utf8');
144
+ logger.info(`Kickstart file written to ${dest}`);
145
+ },
146
+ };
147
+ }
148
+
149
+ export default UnderpostKickStart;
package/src/cli/run.js CHANGED
@@ -68,7 +68,8 @@ const logger = loggerFactory(import.meta);
68
68
  * @property {boolean} etcHosts - Whether to modify /etc/hosts.
69
69
  * @property {string} confServerPath - The configuration server path.
70
70
  * @property {string} underpostRoot - The root path of the Underpost installation.
71
- * @property {string} cronJobs - The cron jobs to run.
71
+ * @property {string} cmdCronJobs - Pre-script commands to run before cron job execution.
72
+ * @property {string} deployIdCronJobs - The deployment ID for cron jobs.
72
73
  * @property {string} timezone - The timezone to set.
73
74
  * @property {boolean} kubeadm - Whether to run in kubeadm mode.
74
75
  * @property {boolean} kind - Whether to run in kind mode.
@@ -84,6 +85,8 @@ const logger = loggerFactory(import.meta);
84
85
  * @property {string} monitorStatusKindType - The monitor status kind type option.
85
86
  * @property {string} monitorStatusDeltaMs - The monitor status delta in milliseconds.
86
87
  * @property {string} monitorStatusMaxAttempts - The maximum number of attempts for monitor status.
88
+ * @property {boolean} dryRun - Whether to perform a dry run.
89
+ * @property {boolean} createJobNow - Whether to create the job immediately.
87
90
  * @property {boolean} logs - Whether to enable logs.
88
91
  * @memberof UnderpostRun
89
92
  */
@@ -127,7 +130,8 @@ const DEFAULT_OPTION = {
127
130
  etcHosts: false,
128
131
  confServerPath: '',
129
132
  underpostRoot: '',
130
- cronJobs: '',
133
+ cmdCronJobs: '',
134
+ deployIdCronJobs: '',
131
135
  timezone: '',
132
136
  kubeadm: false,
133
137
  kind: false,
@@ -144,6 +148,8 @@ const DEFAULT_OPTION = {
144
148
  monitorStatusDeltaMs: '',
145
149
  monitorStatusMaxAttempts: '',
146
150
  logs: false,
151
+ dryRun: false,
152
+ createJobNow: false,
147
153
  };
148
154
 
149
155
  /**
@@ -502,7 +508,21 @@ class UnderpostRun {
502
508
  if (!validVersion) throw new Error('Version mismatch');
503
509
  }
504
510
  if (options.timezone !== 'none') shellExec(`${baseCommand} run${baseClusterCommand} tz`);
505
- if (options.cronJobs !== 'none') shellExec(`${baseCommand} run${baseClusterCommand} cron`);
511
+ if (options.deployIdCronJobs !== 'none') {
512
+ const cronClusterFlag = options.k3s
513
+ ? ' --k3s'
514
+ : options.kind
515
+ ? ' --kind'
516
+ : options.kubeadm
517
+ ? ' --kubeadm'
518
+ : '';
519
+ const cronCmdFlag = options.cmdCronJobs ? ` --cmd-cron-jobs "${options.cmdCronJobs}"` : '';
520
+ const cronCreateJobNowFlag = options.createJobNow ? ' --create-job-now' : '';
521
+ const cronDryRunFlag = options.dryRun ? ' --dry-run' : '';
522
+ shellExec(
523
+ `${baseCommand} run${baseClusterCommand} cron${cronClusterFlag}${cronCmdFlag}${cronCreateJobNowFlag}${cronDryRunFlag}${options.deployIdCronJobs ? ` ${options.deployIdCronJobs}` : ''}`,
524
+ );
525
+ }
506
526
  }
507
527
 
508
528
  const currentTraffic = isDeployRunnerContext(path, options)
@@ -710,16 +730,29 @@ class UnderpostRun {
710
730
 
711
731
  /**
712
732
  * @method cron
713
- * @description Sets up and starts the `dd-cron` environment by writing environment variables, starting the cron service, and cleaning up.
733
+ * @description Sets up cron jobs using `underpost cron --setup-start` command, which likely configures scheduled tasks for the application.
714
734
  * @param {string} path - The input value, identifier, or path for the operation.
715
735
  * @param {Object} options - The default underpost runner options for customizing workflow
716
736
  * @memberof UnderpostRun
717
737
  */
718
738
  cron: (path, options = DEFAULT_OPTION) => {
719
- const env = options.dev ? 'development' : 'production';
720
- shellExec(`node bin env ${path ? path : 'dd-cron'} ${env}`);
721
- shellExec(`npm start`);
722
- shellExec(`node bin env clean`);
739
+ const baseCommand = options.dev ? 'node bin' : 'underpost';
740
+ const devFlag = options.dev ? ' --dev' : '';
741
+ const gitFlag = options.git ? ' --git' : '';
742
+ const namespaceFlag =
743
+ options.namespace && options.namespace !== 'default' ? ` --namespace ${options.namespace}` : '';
744
+ const imageFlag = options.image ? ` --image ${options.image}` : '';
745
+ const cmdFlag = options.cmdCronJobs ? ` --cmd "${options.cmdCronJobs}"` : '';
746
+ const ddCronPath = './engine-private/deploy/dd.cron';
747
+ const defaultDeployId = !path && fs.existsSync(ddCronPath) ? fs.readFileSync(ddCronPath, 'utf8').trim() : '';
748
+ const setupStartId = path || defaultDeployId;
749
+ const setupStart = setupStartId ? ` --setup-start ${setupStartId} --apply` : '';
750
+ const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : options.kubeadm ? ' --kubeadm' : '';
751
+ const createJobNowFlag = options.createJobNow ? ' --create-job-now' : '';
752
+ const dryRunFlag = options.dryRun ? ' --dry-run' : '';
753
+ shellExec(
754
+ `${baseCommand} cron${devFlag}${gitFlag}${namespaceFlag}${imageFlag}${cmdFlag}${clusterFlag}${createJobNowFlag}${dryRunFlag}${setupStart}`,
755
+ );
723
756
  },
724
757
 
725
758
  /**
@@ -991,7 +1024,7 @@ EOF
991
1024
  args: [daemonProcess(path ? path : `cd /home/dd/engine && npm install && npm run test`)],
992
1025
  };
993
1026
 
994
- await Underpost.run.RUNNERS['deploy-job'](path, payload);
1027
+ await Underpost.run.CALL('deploy-job', path, payload);
995
1028
  },
996
1029
 
997
1030
  /**
@@ -1550,7 +1583,7 @@ EOF
1550
1583
  `${baseCommand} secret underpost --create-from-file /etc/config/.env.${env}`,
1551
1584
  `${baseCommand} start --build --run ${deployId} ${env} --underpost-quickly-install`,
1552
1585
  ];
1553
- shellExec(`node bin run sync${baseClusterCommand} --cron-jobs none dd-test --cmd "${cmd}"`);
1586
+ shellExec(`node bin run sync${baseClusterCommand} --deploy-id-cron-jobs none dd-test --cmd "${cmd}"`);
1554
1587
  },
1555
1588
 
1556
1589
  /**
package/src/cli/static.js CHANGED
@@ -6,11 +6,12 @@
6
6
 
7
7
  import fs from 'fs-extra';
8
8
  import path from 'path';
9
+ import express from 'express';
9
10
  import { ssrFactory } from '../server/ssr.js';
10
11
  import { shellExec } from '../server/process.js';
11
12
  import Underpost from '../index.js';
12
13
  import { JSONweb } from '../server/client-formatted.js';
13
- import { loggerFactory } from '../server/logger.js';
14
+ import { loggerFactory, loggerMiddleware } from '../server/logger.js';
14
15
 
15
16
  const logger = loggerFactory(import.meta);
16
17
 
@@ -592,6 +593,31 @@ class UnderpostStatic {
592
593
  throw error;
593
594
  }
594
595
  }
596
+
597
+ // Start standalone static file server if --run-sv is specified
598
+ if (options.runSv !== undefined) {
599
+ const port = typeof options.runSv === 'string' ? parseInt(options.runSv, 10) : 5000;
600
+ const servePath =
601
+ options.outputPath && options.outputPath !== '.'
602
+ ? path.dirname(path.resolve(options.outputPath))
603
+ : path.resolve('.');
604
+
605
+ if (!fs.existsSync(servePath)) {
606
+ logger.error(`Serve path does not exist: ${servePath}`);
607
+ return;
608
+ }
609
+
610
+ const app = express();
611
+
612
+ app.use(loggerMiddleware(import.meta, 'debug', () => false));
613
+
614
+ app.use('/', express.static(servePath));
615
+
616
+ app.listen(port, () => {
617
+ logger.info(`Static file server running at http://localhost:${port}`);
618
+ logger.info(`Serving files from: ${servePath}`);
619
+ });
620
+ }
595
621
  },
596
622
 
597
623
  /**
@@ -22,8 +22,9 @@ const Docs = {
22
22
  return html`
23
23
  <iframe
24
24
  class="in iframe-${ModalId}"
25
- style="width: 100%; border: none; background: white"
25
+ style="width: 100%; border: none; background: white; display: block"
26
26
  src="${docData.url()}"
27
+ sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-popups-to-escape-sandbox"
27
28
  >
28
29
  </iframe>
29
30
  `;
@@ -37,9 +38,27 @@ const Docs = {
37
38
  query: true,
38
39
  RouterInstance: Modal.Data['modal-docs'].options.RouterInstance,
39
40
  });
41
+ const iframeEl = s(`.iframe-${ModalId}`);
42
+ if (iframeEl) {
43
+ iframeEl.addEventListener('load', () => {
44
+ try {
45
+ const iframeWin = iframeEl.contentWindow;
46
+ if (iframeWin) {
47
+ Object.defineProperty(iframeWin, 'parent', { get: () => iframeWin, configurable: false });
48
+ Object.defineProperty(iframeWin, 'top', { get: () => iframeWin, configurable: false });
49
+ }
50
+ } catch (e) {
51
+ // cross-origin or security restriction — safe to ignore
52
+ }
53
+ window.scrollTo(0, 0);
54
+ });
55
+ }
40
56
  Modal.Data[ModalId].onObserverListener[ModalId] = () => {
41
- if (s(`.iframe-${ModalId}`))
42
- s(`.iframe-${ModalId}`).style.height = `${s(`.${ModalId}`).offsetHeight - Modal.headerTitleHeight}px`;
57
+ if (s(`.iframe-${ModalId}`)) {
58
+ const barEl = s(`.bar-default-modal-${ModalId}`);
59
+ const barHeight = barEl ? barEl.offsetHeight : Modal.headerTitleHeight;
60
+ s(`.iframe-${ModalId}`).style.height = `${s(`.${ModalId}`).offsetHeight - barHeight}px`;
61
+ }
43
62
 
44
63
  if (type.match('coverage')) {
45
64
  simpleIconsRender(`.doc-icon-coverage`);
package/src/index.js CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import UnderpostBaremetal from './cli/baremetal.js';
8
8
  import UnderpostCloudInit from './cli/cloud-init.js';
9
+ import UnderpostKickStart from './cli/kickstart.js';
9
10
  import UnderpostCluster from './cli/cluster.js';
10
11
  import UnderpostDB from './cli/db.js';
11
12
  import UnderpostDeploy from './cli/deploy.js';
@@ -39,7 +40,7 @@ class Underpost {
39
40
  * @type {String}
40
41
  * @memberof Underpost
41
42
  */
42
- static version = 'v2.99.5';
43
+ static version = 'v2.99.6';
43
44
 
44
45
  /**
45
46
  * Required Node.js major version
@@ -179,6 +180,16 @@ class Underpost {
179
180
  return UnderpostCloudInit.API;
180
181
  }
181
182
 
183
+ /**
184
+ * KickStart cli API
185
+ * @static
186
+ * @type {UnderpostKickStart.API}
187
+ * @memberof Underpost
188
+ */
189
+ static get kickstart() {
190
+ return UnderpostKickStart.API;
191
+ }
192
+
182
193
  /**
183
194
  * Run cli API
184
195
  * @static
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Manages backup operations for deployments.
3
3
  * @module src/server/backup.js
4
- * @namespace BackUp
4
+ * @namespace UnderpostBakcUp
5
5
  */
6
6
 
7
7
  import fs from 'fs-extra';
@@ -16,7 +16,7 @@ const logger = loggerFactory(import.meta);
16
16
  /**
17
17
  * @class BackUp
18
18
  * @description Manages backup operations for deployments.
19
- * @memberof BackUp
19
+ * @memberof UnderpostBakcUp
20
20
  */
21
21
  class BackUp {
22
22
  /**
@@ -25,7 +25,10 @@ class BackUp {
25
25
  * @param {string} deployList - The list of deployments to backup.
26
26
  * @param {Object} options - The options for the backup operation.
27
27
  * @param {boolean} options.git - Whether to backup data using Git.
28
- * @memberof BackUp
28
+ * @param {boolean} [options.k3s] - Use k3s cluster context.
29
+ * @param {boolean} [options.kind] - Use kind cluster context.
30
+ * @param {boolean} [options.kubeadm] - Use kubeadm cluster context.
31
+ * @memberof UnderpostBakcUp
29
32
  */
30
33
  static callback = async function (deployList, options = { git: false }) {
31
34
  if ((!deployList || deployList === 'dd') && fs.existsSync(`./engine-private/deploy/dd.router`))
@@ -34,12 +37,16 @@ class BackUp {
34
37
  logger.info('init backups callback', deployList);
35
38
  await logger.setUpInfo();
36
39
 
40
+ const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : options.kubeadm ? ' --kubeadm' : '';
41
+
37
42
  for (const _deployId of deployList.split(',')) {
38
43
  const deployId = _deployId.trim();
39
44
  if (!deployId) continue;
40
45
 
41
46
  logger.info('Executing database export for', deployId);
42
- shellExec(`node bin db ${options.git ? '--git ' : ''}--export ${deployId}`);
47
+ shellExec(
48
+ `node bin db ${options.git ? '--git --force-clone ' : ''}--export --primary-pod${clusterFlag} ${deployId}`,
49
+ );
43
50
  }
44
51
  };
45
52
  }
@@ -137,7 +137,7 @@ const buildJsDocs = async ({ host, path, metadata = {} }) => {
137
137
 
138
138
  jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
139
139
  jsDocsConfig.opts.theme_opts.title = metadata?.title ? metadata.title : undefined;
140
- jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? path : `${path}/favicon.ico`}`;
140
+ jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? '/' : `${path}/`}favicon.ico`;
141
141
 
142
142
  fs.writeFileSync(`./jsdoc.json`, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
143
143
  logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
@@ -1172,14 +1172,6 @@ const getPathsSSR = (conf) => {
1172
1172
  * @memberof ServerConfBuilder
1173
1173
  */
1174
1174
  const Cmd = {
1175
- /**
1176
- * @method delete
1177
- * @description Deletes the deploy.
1178
- * @param {string} deployId - The deploy ID.
1179
- * @returns {string} - The delete command.
1180
- * @memberof Cmd
1181
- */
1182
- delete: (deployId) => `pm2 delete ${deployId}`,
1183
1175
  /**
1184
1176
  * @method run
1185
1177
  * @description Runs the deploy.
@@ -1221,20 +1213,6 @@ const Cmd = {
1221
1213
  * @memberof Cmd
1222
1214
  */
1223
1215
  syncPorts: () => `node bin/deploy sync-env-port`,
1224
- /**
1225
- * @method cron
1226
- * @description Creates a cron job.
1227
- * @param {string} deployList - The deploy list.
1228
- * @param {string} jobList - The job list.
1229
- * @param {string} name - The name.
1230
- * @param {string} expression - The expression.
1231
- * @param {object} options - The options.
1232
- * @param {number} instances - The number of PM2 instances (default: 1).
1233
- * @returns {string} - The cron command.
1234
- * @memberof Cmd
1235
- */
1236
- cron: (deployList, jobList, name, expression, options, instances = 1) =>
1237
- `pm2 start ./bin/index.js --no-autorestart --instances ${instances} --cron "${expression}" --name ${name} -- cron ${options?.git ? `--git ` : ''}${deployList} ${jobList}`,
1238
1216
  };
1239
1217
 
1240
1218
  /**