@underpostnet/underpost 2.99.4 → 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.
Files changed (38) hide show
  1. package/.env.development +0 -3
  2. package/.env.production +1 -3
  3. package/.env.test +0 -3
  4. package/README.md +3 -3
  5. package/baremetal/commission-workflows.json +93 -4
  6. package/bin/deploy.js +56 -45
  7. package/cli.md +45 -28
  8. package/examples/static-page/README.md +101 -357
  9. package/examples/static-page/ssr-components/CustomPage.js +1 -13
  10. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +40 -0
  11. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +40 -0
  12. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  13. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  14. package/package.json +3 -4
  15. package/scripts/disk-devices.sh +13 -0
  16. package/scripts/maas-setup.sh +13 -9
  17. package/scripts/rocky-kickstart.sh +294 -0
  18. package/src/cli/baremetal.js +657 -263
  19. package/src/cli/cloud-init.js +120 -120
  20. package/src/cli/env.js +4 -1
  21. package/src/cli/image.js +4 -37
  22. package/src/cli/index.js +56 -11
  23. package/src/cli/kickstart.js +149 -0
  24. package/src/cli/repository.js +3 -1
  25. package/src/cli/run.js +56 -10
  26. package/src/cli/secrets.js +0 -34
  27. package/src/cli/static.js +23 -23
  28. package/src/client/components/core/Docs.js +22 -3
  29. package/src/index.js +30 -5
  30. package/src/server/backup.js +11 -4
  31. package/src/server/client-build-docs.js +1 -1
  32. package/src/server/conf.js +0 -22
  33. package/src/server/cron.js +339 -130
  34. package/src/server/dns.js +10 -0
  35. package/src/server/logger.js +22 -27
  36. package/src/server/tls.js +14 -14
  37. package/examples/static-page/QUICK-REFERENCE.md +0 -481
  38. package/examples/static-page/STATIC-GENERATOR-GUIDE.md +0 -757
@@ -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`);
@@ -261,6 +220,12 @@ curl -X POST \\
261
220
  * @param {boolean} params.ubuntuToolsBuild - Flag to determine if Ubuntu tools should be built.
262
221
  * @param {string} [params.bootcmd] - Optional custom commands to run during boot.
263
222
  * @param {string} [params.runcmd] - Optional custom commands to run during first boot.
223
+ * @param {object} [params.write_files] - Optional array of files to write during cloud-init, each with path, permissions, owner, and content.
224
+ * @param {string} [params.write_files[].path] - The file path where the content will be written on the target machine.
225
+ * @param {string} [params.write_files[].permissions] - The file permissions to set for the written file (e.g., '0644').
226
+ * @param {string} [params.write_files[].owner] - The owner of the written file (e.g., 'root:root').
227
+ * @param {string} [params.write_files[].content] - The content to write into the file.
228
+ * @param {boolean} [params.write_files[].defer] - Whether to defer writing the file until the 'final' stage of cloud-init.
264
229
  * @param {object} [authCredentials={}] - Optional MAAS authentication credentials.
265
230
  * @returns {object} The generated cloud-init configuration content.
266
231
  * @memberof UnderpostCloudInit
@@ -278,6 +243,15 @@ curl -X POST \\
278
243
  ubuntuToolsBuild,
279
244
  bootcmd: bootcmdParam,
280
245
  runcmd: runcmdParam,
246
+ write_files = [
247
+ {
248
+ path: '',
249
+ permissions: '',
250
+ owner: '',
251
+ content: '',
252
+ defer: false,
253
+ },
254
+ ],
281
255
  },
282
256
  authCredentials = { consumer_key: '', consumer_secret: '', token_key: '', token_secret: '' },
283
257
  ) {
@@ -305,6 +279,7 @@ curl -X POST \\
305
279
  let runcmd = [
306
280
  'echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"',
307
281
  'echo "Init runcmd"',
282
+ 'systemctl enable --now ssh',
308
283
  'echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"',
309
284
  ];
310
285
 
@@ -334,12 +309,14 @@ curl -X POST \\
334
309
  name: process.env.MAAS_ADMIN_USERNAME,
335
310
  sudo: ['ALL=(ALL) NOPASSWD:ALL'],
336
311
  shell: '/bin/bash',
337
- lock_passwd: false,
312
+ lock_passwd: true,
338
313
  groups: 'sudo,users,admin,wheel,lxd',
339
- plain_text_passwd: process.env.MAAS_ADMIN_USERNAME,
314
+ // plain_text_passwd: process.env.MAAS_ADMIN_USERNAME,
340
315
  ssh_authorized_keys: [fs.readFileSync(`/home/dd/engine/engine-private/deploy/id_rsa.pub`, 'utf8')],
341
316
  },
342
317
  ],
318
+ disable_root: true,
319
+ ssh_pwauth: false,
343
320
  timezone,
344
321
  ntp: {
345
322
  enabled: true,
@@ -360,6 +337,7 @@ curl -X POST \\
360
337
  'smartmontools',
361
338
  'net-tools',
362
339
  'util-linux',
340
+ 'openssh-server',
363
341
  ],
364
342
  resize_rootfs: false,
365
343
  growpart: { mode: 'off' },
@@ -380,7 +358,7 @@ curl -X POST \\
380
358
  final_message: '====== Cloud init finished ======',
381
359
  bootcmd,
382
360
  runcmd,
383
- disable_root: true,
361
+ write_files,
384
362
  preserve_hostname: false,
385
363
  cloud_init_modules: [
386
364
  // minimal for commissioning
@@ -394,37 +372,37 @@ curl -X POST \\
394
372
  'ssh', // enable/configure SSH for temporary access
395
373
 
396
374
  // optional modules (commented out by default)
397
- // 'migrator',
398
- // 'seed_random',
399
- // 'growpart',
400
- // 'resizefs',
401
- // 'users-groups',
375
+ 'migrator',
376
+ 'seed_random',
377
+ 'growpart',
378
+ 'resizefs',
379
+ 'users-groups',
402
380
  ],
403
381
  cloud_config_modules: [
404
382
  // minimal so MAAS can run commissioning scripts
405
383
  'runcmd', // commissioning / final script execution
406
384
  'mounts', // mount devices during commissioning if needed
407
- // 'ntp', // optional — enable if you want time sync
385
+ 'ntp', // optional — enable if you want time sync
408
386
 
409
387
  // typically not required for basic commissioning (commented)
410
- // 'emit_upstart',
411
- // 'disk_setup',
412
- // 'ssh-import-id',
413
- // 'locale',
414
- // 'set-passwords',
415
- // 'grub-dpkg',
416
- // 'apt-pipelining',
417
- // 'apt-configure',
418
- // 'package-update-upgrade-install', // heavy; do NOT enable by default
419
- // 'landscape',
420
- // 'timezone',
421
- // 'puppet',
422
- // 'chef',
423
- // 'salt-minion',
424
- // 'mcollective',
425
- // 'disable-ec2-metadata',
426
- // 'byobu',
427
- // 'ssh-import-id', // duplicate in original list
388
+ 'emit_upstart',
389
+ 'disk_setup',
390
+ 'ssh-import-id',
391
+ 'locale',
392
+ 'set-passwords',
393
+ 'grub-dpkg',
394
+ 'apt-pipelining',
395
+ 'apt-configure',
396
+ 'package-update-upgrade-install', // heavy; do NOT enable by default
397
+ 'landscape',
398
+ 'timezone',
399
+ 'puppet',
400
+ 'chef',
401
+ 'salt-minion',
402
+ 'mcollective',
403
+ 'disable-ec2-metadata',
404
+ 'byobu',
405
+ 'ssh-import-id', // duplicate in original list
428
406
  ],
429
407
  cloud_final_modules: [
430
408
  // minimal suggestions so final scripts run and node reports status
@@ -432,64 +410,19 @@ curl -X POST \\
432
410
  'final-message', // useful for logs/reporting
433
411
 
434
412
  // optional / commented
435
- // 'rightscale_userdata',
436
- // 'scripts-vendor',
437
- // 'scripts-per-once',
438
- // 'scripts-user',
439
- // 'ssh-authkey-fingerprints',
440
- // 'keys-to-console',
441
- // 'power-state-change', // use carefully (can poweroff/reboot)
413
+ 'rightscale_userdata',
414
+ 'scripts-vendor',
415
+ 'scripts-per-once',
416
+ 'scripts-user',
417
+ 'ssh-authkey-fingerprints',
418
+ 'keys-to-console',
419
+ 'power-state-change', // use carefully (can poweroff/reboot)
442
420
  ],
443
421
  });
444
422
 
445
423
  return { cloudConfigSrc };
446
424
  },
447
425
 
448
- /**
449
- * @method authCredentialsFactory
450
- * @description Retrieves MAAS API key credentials from the MAAS CLI.
451
- * This method parses the output of `maas apikey` to extract the consumer key,
452
- * consumer secret, token key, and token secret.
453
- * @returns {object} An object containing the MAAS authentication credentials.
454
- * @memberof UnderpostCloudInit
455
- * @throws {Error} If the MAAS API key format is invalid.
456
- */
457
- authCredentialsFactory() {
458
- // Expected formats:
459
- // <consumer_key>:<consumer_token>:<secret> (older format)
460
- // <consumer_key>:<consumer_secret>:<token_key>:<token_secret> (newer format)
461
- // Commands used to generate API keys:
462
- // maas apikey --with-names --username ${process.env.MAAS_ADMIN_USERNAME}
463
- // maas ${process.env.MAAS_ADMIN_USERNAME} account create-authorisation-token
464
- // maas apikey --generate --username ${process.env.MAAS_ADMIN_USERNAME}
465
- // Reference: https://github.com/CanonicalLtd/maas-docs/issues/647
466
-
467
- const parts = shellExec(`maas apikey --with-names --username ${process.env.MAAS_ADMIN_USERNAME}`, {
468
- stdout: true,
469
- })
470
- .trim()
471
- .split(`\n`)[0] // Take only the first line of output.
472
- .split(':'); // Split by colon to get individual parts.
473
-
474
- let consumer_key, consumer_secret, token_key, token_secret;
475
-
476
- // Determine the format of the API key and assign parts accordingly.
477
- if (parts.length === 4) {
478
- [consumer_key, consumer_secret, token_key, token_secret] = parts;
479
- } else if (parts.length === 3) {
480
- // Handle older 3-part format, setting consumer_secret as empty.
481
- [consumer_key, token_key, token_secret] = parts;
482
- consumer_secret = '""';
483
- token_secret = token_secret.split(' MAAS consumer')[0].trim(); // Clean up token secret.
484
- } else {
485
- // Throw an error if the format is not recognized.
486
- throw new Error('Invalid token format');
487
- }
488
-
489
- logger.info('Maas api token generated', { consumer_key, consumer_secret, token_key, token_secret });
490
- return { consumer_key, consumer_secret, token_key, token_secret };
491
- },
492
-
493
426
  /**
494
427
  * @method generateCloudConfig
495
428
  * @description Generates a generic cloud-init configuration string.
@@ -518,6 +451,7 @@ curl -X POST \\
518
451
  growpart,
519
452
  network,
520
453
  runcmd,
454
+ write_files,
521
455
  final_message,
522
456
  bootcmd,
523
457
  disable_root,
@@ -645,6 +579,21 @@ curl -X POST \\
645
579
  runcmd.forEach((cmd) => yaml.push(` - ${cmd}`));
646
580
  }
647
581
 
582
+ if (write_files) {
583
+ yaml.push('write_files:');
584
+ write_files.forEach((file) => {
585
+ yaml.push(` - path: ${file.path}`);
586
+ if (file.encoding) yaml.push(` encoding: ${file.encoding}`);
587
+ if (file.owner) yaml.push(` owner: ${file.owner}`);
588
+ if (file.permissions) yaml.push(` permissions: '${file.permissions}'`);
589
+ if (file.defer) yaml.push(` defer: ${file.defer}`);
590
+ if (file.content) {
591
+ yaml.push(` content: |`);
592
+ file.content.split('\n').forEach((line) => yaml.push(` ${line}`));
593
+ }
594
+ });
595
+ }
596
+
648
597
  if (final_message) yaml.push(`final_message: "${final_message}"`);
649
598
 
650
599
  if (bootcmd) {
@@ -670,6 +619,57 @@ curl -X POST \\
670
619
 
671
620
  return yaml.join('\n');
672
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
+ /**
645
+ * @method kernelParamsFactory
646
+ * @description Generates the kernel parameters for the target machine's bootloader configuration,
647
+ * including the necessary parameters to enable cloud-init with a specific configuration URL and logging settings.
648
+ * @param {array} cmd - The existing array of kernel parameters to which cloud-init parameters will be appended.
649
+ * @param {object} options - Options for generating kernel parameters.
650
+ * @param {string} options.ipDhcpServer - The IP address of the DHCP server.
651
+ * @param {object} [options.machine] - The machine information, including system_id for constructing the cloud-init configuration URL.
652
+ * @param {string} [options.machine.system_id] - The unique identifier of the machine, used to fetch the correct cloud-init preseed configuration from MAAS.
653
+ * @return {array} The modified array of kernel parameters with cloud-init parameters included.
654
+ * @memberof UnderpostCloudInit
655
+ */
656
+ kernelParamsFactory(
657
+ macAddress,
658
+ cmd = [],
659
+ options = {
660
+ ipDhcpServer: '',
661
+ bootstrapHttpServerPort: 8888,
662
+ hostname: '',
663
+ machine: {
664
+ system_id: '',
665
+ },
666
+ authCredentials: { consumer_key: '', consumer_secret: '', token_key: '', token_secret: '' },
667
+ },
668
+ ) {
669
+ const { ipDhcpServer, bootstrapHttpServerPort, hostname } = options;
670
+ const cloudConfigUrl = `http://${ipDhcpServer}:${bootstrapHttpServerPort}/${hostname}/cloud-init/user-data`;
671
+ return cmd.concat([`cloud-config-url=${cloudConfigUrl}`]);
672
+ },
673
673
  };
674
674
  }
675
675
 
package/src/cli/env.js CHANGED
@@ -8,6 +8,7 @@ import { getNpmRootPath, writeEnv } from '../server/conf.js';
8
8
  import fs from 'fs-extra';
9
9
  import { loggerFactory } from '../server/logger.js';
10
10
  import dotenv from 'dotenv';
11
+ import { pbcopy } from '../server/process.js';
11
12
 
12
13
  dotenv.config();
13
14
 
@@ -72,9 +73,10 @@ class UnderpostRootEnv {
72
73
  * @param {object} options - Options for getting the environment variable.
73
74
  * @param {boolean} [options.plain=false] - If true, returns the environment variable value as a string.
74
75
  * @param {boolean} [options.disableLog=false] - If true, disables logging of the environment variable value.
76
+ * @param {boolean} [options.copy=false] - If true, copies the environment variable value to the clipboard.
75
77
  * @memberof UnderpostEnv
76
78
  */
77
- get(key, value, options = { plain: false, disableLog: false }) {
79
+ get(key, value, options = { plain: false, disableLog: false, copy: false }) {
78
80
  const exeRootPath = `${getNpmRootPath()}/underpost`;
79
81
  const envPath = `${exeRootPath}/.env`;
80
82
  if (!fs.existsSync(envPath)) {
@@ -84,6 +86,7 @@ class UnderpostRootEnv {
84
86
  const env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
85
87
  if (!options.disableLog)
86
88
  options?.plain === true ? console.log(env[key]) : logger.info(`${key}(${typeof env[key]})`, env[key]);
89
+ if (options.copy === true) pbcopy(env[key]);
87
90
  return env[key];
88
91
  },
89
92
  /**
package/src/cli/image.js CHANGED
@@ -79,8 +79,6 @@ class UnderpostImage {
79
79
  * @param {boolean} [options.kind=false] - If true, load the image archive into a Kind cluster.
80
80
  * @param {boolean} [options.kubeadm=false] - If true, load the image archive into a Kubeadm cluster (uses 'ctr').
81
81
  * @param {boolean} [options.k3s=false] - If true, load the image archive into a K3s cluster (uses 'k3s ctr').
82
- * @param {boolean} [options.secrets=false] - If true, load secrets from the .env file for the build.
83
- * @param {string} [options.secretsPath=''] - Custom path to the .env file for secrets.
84
82
  * @param {boolean} [options.reset=false] - If true, perform a no-cache build.
85
83
  * @param {boolean} [options.dev=false] - If true, use development mode.
86
84
  * @memberof UnderpostImage
@@ -96,27 +94,11 @@ class UnderpostImage {
96
94
  kind: false,
97
95
  kubeadm: false,
98
96
  k3s: false,
99
- secrets: false,
100
- secretsPath: '',
101
97
  reset: false,
102
98
  dev: false,
103
99
  },
104
100
  ) {
105
- let {
106
- path,
107
- imageName,
108
- version,
109
- imagePath,
110
- dockerfileName,
111
- podmanSave,
112
- secrets,
113
- secretsPath,
114
- kind,
115
- kubeadm,
116
- k3s,
117
- reset,
118
- dev,
119
- } = options;
101
+ let { path, imageName, version, imagePath, dockerfileName, podmanSave, kind, kubeadm, k3s, reset, dev } = options;
120
102
  if (!path) path = '.';
121
103
  if (!imageName) imageName = `rockylinux9-underpost:${Underpost.version}`;
122
104
  if (!imagePath) imagePath = '.';
@@ -126,29 +108,14 @@ class UnderpostImage {
126
108
  if (imagePath && typeof imagePath === 'string' && !fs.existsSync(imagePath))
127
109
  fs.mkdirSync(imagePath, { recursive: true });
128
110
  const tarFile = `${imagePath}/${imageName.replace(':', '_')}.tar`;
129
- let secretsInput = ' ';
130
- let secretDockerInput = '';
131
111
  let cache = '';
132
- if (secrets === true) {
133
- const envObj = dotenv.parse(
134
- fs.readFileSync(
135
- secretsPath && typeof secretsPath === 'string' ? secretsPath : `${getNpmRootPath()}/underpost/.env`,
136
- 'utf8',
137
- ),
138
- );
139
- for (const key of Object.keys(envObj)) {
140
- secretsInput += ` && export ${key}="${envObj[key]}" `;
141
- secretDockerInput += ` --secret id=${key},env=${key} \ `;
142
- }
143
- }
144
112
  if (reset === true) cache += ' --rm --no-cache';
145
- if (path && typeof path === 'string')
113
+ if (path)
146
114
  shellExec(
147
- `cd ${path}${secretsInput}&& sudo podman build -f ./${
115
+ `cd ${path} && sudo podman build -f ./${
148
116
  dockerfileName && typeof dockerfileName === 'string' ? dockerfileName : 'Dockerfile'
149
- } -t ${imageName} --pull=never --cap-add=CAP_AUDIT_WRITE${cache}${secretDockerInput} --network host`,
117
+ } -t ${imageName} --pull=never --cap-add=CAP_AUDIT_WRITE${cache} --network host`,
150
118
  );
151
-
152
119
  if (podmanSave === true) {
153
120
  if (fs.existsSync(tarFile)) fs.removeSync(tarFile);
154
121
  shellExec(`podman save -o ${tarFile} ${podManImg}`);
package/src/cli/index.js CHANGED
@@ -138,9 +138,6 @@ program
138
138
  .option('--head-components <paths>', 'Comma-separated SSR head component paths.')
139
139
  .option('--body-components <paths>', 'Comma-separated SSR body component paths.')
140
140
 
141
- .option('--deploy-id <deploy-id>', 'Build static assets for a specific deployment ID.')
142
- .option('--build', 'Triggers the static build process for the specified deployment ID.')
143
- .option('--build-host <build-host>', 'Sets a custom build host for static documents or assets.')
144
141
  .option('--build-path <build-path>', 'Sets a custom build path for static documents or assets.')
145
142
  .option('--env <env>', 'Sets the environment for the static build (e.g., "development", "production").')
146
143
  .option('--minify', 'Minify HTML output (default: true for production).')
@@ -153,6 +150,11 @@ program
153
150
  .option('--dir <dir>', 'HTML dir attribute (default: ltr).')
154
151
  .option('--dev', 'Sets the development cli context')
155
152
 
153
+ .option(
154
+ '--run-sv [port]',
155
+ 'Start a standalone Express static server to preview the static build (default port: 5000).',
156
+ )
157
+
156
158
  .description(`Manages static build of page, bundles, and documentation with comprehensive customization options.`)
157
159
  .action(Underpost.static.callback);
158
160
 
@@ -165,6 +167,7 @@ program
165
167
  .option('--filter <keyword>', 'Filters the list by matching key or value (only for list operation).')
166
168
  .option('--deploy-id <deploy-id>', 'Sets the deployment configuration ID for the operation context.')
167
169
  .option('--build', 'Sets the build context for the operation.')
170
+ .option('--copy', 'Copies the configuration value to the clipboard (only for get operation).')
168
171
  .description(`Manages Underpost configurations using various operators.`)
169
172
  .action((...args) => Underpost.env[args[0]](args[1], args[2], args[3]));
170
173
 
@@ -320,8 +323,6 @@ program
320
323
  .option('--kubeadm', 'Set kubeadm cluster env image context management.')
321
324
  .option('--k3s', 'Set k3s cluster env image context management.')
322
325
  .option('--node-name', 'Set node name for kubeadm or k3s cluster env image context management.')
323
- .option('--secrets', 'Includes Dockerfile environment secrets during the build.')
324
- .option('--secrets-path [secrets-path]', 'Specifies a custom path for Dockerfile environment secrets.')
325
326
  .option('--reset', 'Performs a build without using the cache.')
326
327
  .option('--dev', 'Use development mode.')
327
328
  .option('--pull-dockerhub <dockerhub-image>', 'Sets a custom Docker Hub image for base image pulls.')
@@ -406,10 +407,26 @@ program
406
407
  '[job-list]',
407
408
  `A comma-separated list of job IDs. Options: ${Underpost.cron.getJobsIDs()}. Defaults to all available jobs.`,
408
409
  )
409
- .option('--init-pm2-cronjobs', 'Initializes PM2 cron jobs from configuration for the specified deployment IDs.')
410
- .option('--git', 'Uploads cron job configurations to GitHub.')
411
- .option('--update-package-scripts', 'Updates package.json start scripts for each deploy-id configuration.')
412
- .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.')
413
430
  .action(Underpost.cron.callback);
414
431
 
415
432
  program
@@ -538,7 +555,11 @@ program
538
555
  .option('--expose', 'Enables service exposure for the runner execution.')
539
556
  .option('--conf-server-path <conf-server-path>', 'Sets a custom configuration server path.')
540
557
  .option('--underpost-root <underpost-root>', 'Sets a custom Underpost root path.')
541
- .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
+ )
542
563
  .option('--timezone <timezone>', 'Sets the timezone for the runner execution.')
543
564
  .option('--kubeadm', 'Sets the kubeadm cluster context for the runner execution.')
544
565
  .option('--k3s', 'Sets the k3s cluster context for the runner execution.')
@@ -571,6 +592,11 @@ program
571
592
  '--monitor-status-max-attempts <attempts>',
572
593
  'Sets the maximum number of status check attempts (default: 600).',
573
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
+ )
574
600
  .description('Runs specified scripts using various runners.')
575
601
  .action(Underpost.run.callback);
576
602
 
@@ -629,6 +655,10 @@ program
629
655
  )
630
656
  .option('--ipxe', 'Chainloads iPXE to normalize identity before commissioning.')
631
657
  .option('--ipxe-rebuild', 'Forces rebuild of iPXE binary with embedded boot script.')
658
+ .option(
659
+ '--ipxe-build-iso <iso-path>',
660
+ 'Builds a standalone iPXE ISO with embedded script for the specified workflow ID.',
661
+ )
632
662
  .option('--install-packer', 'Installs Packer CLI.')
633
663
  .option(
634
664
  '--packer-maas-image-template <template-path>',
@@ -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.')
@@ -672,7 +707,17 @@ program
672
707
  .option('--rocky-tools-test', 'Tests rocky linux tools in chroot environment.')
673
708
  .option('--bootcmd <bootcmd-list>', 'Comma-separated list of boot commands to execute.')
674
709
  .option('--runcmd <runcmd-list>', 'Comma-separated list of run commands to execute.')
675
- .option('--logs <log-id>', 'Displays logs for log id: dhcp, cloud, machine, cloud-config.')
710
+ .option(
711
+ '--logs <log-id>',
712
+ `Displays logs for log id: ${[
713
+ 'dhcp',
714
+ 'dhcp-lease',
715
+ 'dhcp-lan',
716
+ 'cloud-init',
717
+ 'cloud-init-machine',
718
+ 'cloud-init-config',
719
+ ]}`,
720
+ )
676
721
  .option('--dev', 'Sets the development context environment for baremetal operations.')
677
722
  .option('--ls', 'Lists available boot resources and machines.')
678
723
  .description(