@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.
- package/README.md +3 -3
- package/baremetal/commission-workflows.json +43 -6
- package/bin/deploy.js +3 -0
- package/cli.md +39 -15
- package/examples/static-page/README.md +80 -13
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +40 -0
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +40 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +2 -3
- package/scripts/maas-setup.sh +13 -9
- package/scripts/rocky-kickstart.sh +294 -0
- package/src/cli/baremetal.js +229 -233
- package/src/cli/cloud-init.js +25 -43
- package/src/cli/index.js +40 -5
- package/src/cli/kickstart.js +149 -0
- package/src/cli/run.js +43 -10
- package/src/cli/static.js +27 -1
- package/src/client/components/core/Docs.js +22 -3
- package/src/index.js +12 -1
- package/src/server/backup.js +11 -4
- package/src/server/client-build-docs.js +1 -1
- package/src/server/conf.js +0 -22
- package/src/server/cron.js +339 -130
- package/src/server/dns.js +10 -0
package/src/cli/cloud-init.js
CHANGED
|
@@ -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('--
|
|
406
|
-
.option('--
|
|
407
|
-
.option(
|
|
408
|
-
|
|
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>', '
|
|
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}
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
package/src/server/backup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Manages backup operations for deployments.
|
|
3
3
|
* @module src/server/backup.js
|
|
4
|
-
* @namespace
|
|
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
|
|
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
|
-
* @
|
|
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(
|
|
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 === '/' ?
|
|
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);
|
package/src/server/conf.js
CHANGED
|
@@ -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
|
/**
|