@underpostnet/underpost 2.99.1 → 2.99.5

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 (42) hide show
  1. package/.env.development +0 -3
  2. package/.env.production +1 -3
  3. package/.env.test +0 -3
  4. package/LICENSE +1 -1
  5. package/README.md +30 -30
  6. package/baremetal/commission-workflows.json +52 -0
  7. package/bin/deploy.js +101 -47
  8. package/cli.md +47 -43
  9. package/examples/static-page/README.md +55 -378
  10. package/examples/static-page/ssr-components/CustomPage.js +1 -13
  11. package/jsconfig.json +4 -2
  12. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  13. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  14. package/manifests/deployment/playwright/deployment.yaml +52 -0
  15. package/package.json +2 -2
  16. package/scripts/disk-devices.sh +13 -0
  17. package/scripts/rocky-pwa.sh +2 -2
  18. package/scripts/ssl.sh +12 -6
  19. package/src/api/user/user.model.js +1 -0
  20. package/src/cli/baremetal.js +576 -176
  21. package/src/cli/cloud-init.js +97 -79
  22. package/src/cli/deploy.js +6 -24
  23. package/src/cli/env.js +4 -1
  24. package/src/cli/image.js +7 -40
  25. package/src/cli/index.js +37 -7
  26. package/src/cli/repository.js +3 -1
  27. package/src/cli/run.js +109 -92
  28. package/src/cli/secrets.js +0 -34
  29. package/src/cli/static.js +0 -26
  30. package/src/cli/test.js +13 -1
  31. package/src/client/components/core/Polyhedron.js +896 -7
  32. package/src/client/components/core/Translate.js +4 -0
  33. package/src/client/services/default/default.management.js +12 -2
  34. package/src/index.js +27 -1
  35. package/src/runtime/express/Express.js +3 -3
  36. package/src/server/conf.js +6 -4
  37. package/src/server/logger.js +33 -31
  38. package/src/server/process.js +27 -2
  39. package/src/server/proxy.js +4 -6
  40. package/src/server/tls.js +30 -25
  41. package/examples/static-page/QUICK-REFERENCE.md +0 -481
  42. package/examples/static-page/STATIC-GENERATOR-GUIDE.md +0 -757
@@ -261,6 +261,12 @@ curl -X POST \\
261
261
  * @param {boolean} params.ubuntuToolsBuild - Flag to determine if Ubuntu tools should be built.
262
262
  * @param {string} [params.bootcmd] - Optional custom commands to run during boot.
263
263
  * @param {string} [params.runcmd] - Optional custom commands to run during first boot.
264
+ * @param {object} [params.write_files] - Optional array of files to write during cloud-init, each with path, permissions, owner, and content.
265
+ * @param {string} [params.write_files[].path] - The file path where the content will be written on the target machine.
266
+ * @param {string} [params.write_files[].permissions] - The file permissions to set for the written file (e.g., '0644').
267
+ * @param {string} [params.write_files[].owner] - The owner of the written file (e.g., 'root:root').
268
+ * @param {string} [params.write_files[].content] - The content to write into the file.
269
+ * @param {boolean} [params.write_files[].defer] - Whether to defer writing the file until the 'final' stage of cloud-init.
264
270
  * @param {object} [authCredentials={}] - Optional MAAS authentication credentials.
265
271
  * @returns {object} The generated cloud-init configuration content.
266
272
  * @memberof UnderpostCloudInit
@@ -278,6 +284,15 @@ curl -X POST \\
278
284
  ubuntuToolsBuild,
279
285
  bootcmd: bootcmdParam,
280
286
  runcmd: runcmdParam,
287
+ write_files = [
288
+ {
289
+ path: '',
290
+ permissions: '',
291
+ owner: '',
292
+ content: '',
293
+ defer: false,
294
+ },
295
+ ],
281
296
  },
282
297
  authCredentials = { consumer_key: '', consumer_secret: '', token_key: '', token_secret: '' },
283
298
  ) {
@@ -305,6 +320,7 @@ curl -X POST \\
305
320
  let runcmd = [
306
321
  'echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"',
307
322
  'echo "Init runcmd"',
323
+ 'systemctl enable --now ssh',
308
324
  'echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"',
309
325
  ];
310
326
 
@@ -334,12 +350,14 @@ curl -X POST \\
334
350
  name: process.env.MAAS_ADMIN_USERNAME,
335
351
  sudo: ['ALL=(ALL) NOPASSWD:ALL'],
336
352
  shell: '/bin/bash',
337
- lock_passwd: false,
353
+ lock_passwd: true,
338
354
  groups: 'sudo,users,admin,wheel,lxd',
339
- plain_text_passwd: process.env.MAAS_ADMIN_USERNAME,
355
+ // plain_text_passwd: process.env.MAAS_ADMIN_USERNAME,
340
356
  ssh_authorized_keys: [fs.readFileSync(`/home/dd/engine/engine-private/deploy/id_rsa.pub`, 'utf8')],
341
357
  },
342
358
  ],
359
+ disable_root: true,
360
+ ssh_pwauth: false,
343
361
  timezone,
344
362
  ntp: {
345
363
  enabled: true,
@@ -360,6 +378,7 @@ curl -X POST \\
360
378
  'smartmontools',
361
379
  'net-tools',
362
380
  'util-linux',
381
+ 'openssh-server',
363
382
  ],
364
383
  resize_rootfs: false,
365
384
  growpart: { mode: 'off' },
@@ -380,7 +399,7 @@ curl -X POST \\
380
399
  final_message: '====== Cloud init finished ======',
381
400
  bootcmd,
382
401
  runcmd,
383
- disable_root: true,
402
+ write_files,
384
403
  preserve_hostname: false,
385
404
  cloud_init_modules: [
386
405
  // minimal for commissioning
@@ -394,37 +413,37 @@ curl -X POST \\
394
413
  'ssh', // enable/configure SSH for temporary access
395
414
 
396
415
  // optional modules (commented out by default)
397
- // 'migrator',
398
- // 'seed_random',
399
- // 'growpart',
400
- // 'resizefs',
401
- // 'users-groups',
416
+ 'migrator',
417
+ 'seed_random',
418
+ 'growpart',
419
+ 'resizefs',
420
+ 'users-groups',
402
421
  ],
403
422
  cloud_config_modules: [
404
423
  // minimal so MAAS can run commissioning scripts
405
424
  'runcmd', // commissioning / final script execution
406
425
  'mounts', // mount devices during commissioning if needed
407
- // 'ntp', // optional — enable if you want time sync
426
+ 'ntp', // optional — enable if you want time sync
408
427
 
409
428
  // 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
429
+ 'emit_upstart',
430
+ 'disk_setup',
431
+ 'ssh-import-id',
432
+ 'locale',
433
+ 'set-passwords',
434
+ 'grub-dpkg',
435
+ 'apt-pipelining',
436
+ 'apt-configure',
437
+ 'package-update-upgrade-install', // heavy; do NOT enable by default
438
+ 'landscape',
439
+ 'timezone',
440
+ 'puppet',
441
+ 'chef',
442
+ 'salt-minion',
443
+ 'mcollective',
444
+ 'disable-ec2-metadata',
445
+ 'byobu',
446
+ 'ssh-import-id', // duplicate in original list
428
447
  ],
429
448
  cloud_final_modules: [
430
449
  // minimal suggestions so final scripts run and node reports status
@@ -432,64 +451,19 @@ curl -X POST \\
432
451
  'final-message', // useful for logs/reporting
433
452
 
434
453
  // 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)
454
+ 'rightscale_userdata',
455
+ 'scripts-vendor',
456
+ 'scripts-per-once',
457
+ 'scripts-user',
458
+ 'ssh-authkey-fingerprints',
459
+ 'keys-to-console',
460
+ 'power-state-change', // use carefully (can poweroff/reboot)
442
461
  ],
443
462
  });
444
463
 
445
464
  return { cloudConfigSrc };
446
465
  },
447
466
 
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
467
  /**
494
468
  * @method generateCloudConfig
495
469
  * @description Generates a generic cloud-init configuration string.
@@ -518,6 +492,7 @@ curl -X POST \\
518
492
  growpart,
519
493
  network,
520
494
  runcmd,
495
+ write_files,
521
496
  final_message,
522
497
  bootcmd,
523
498
  disable_root,
@@ -645,6 +620,21 @@ curl -X POST \\
645
620
  runcmd.forEach((cmd) => yaml.push(` - ${cmd}`));
646
621
  }
647
622
 
623
+ if (write_files) {
624
+ yaml.push('write_files:');
625
+ write_files.forEach((file) => {
626
+ yaml.push(` - path: ${file.path}`);
627
+ if (file.encoding) yaml.push(` encoding: ${file.encoding}`);
628
+ if (file.owner) yaml.push(` owner: ${file.owner}`);
629
+ if (file.permissions) yaml.push(` permissions: '${file.permissions}'`);
630
+ if (file.defer) yaml.push(` defer: ${file.defer}`);
631
+ if (file.content) {
632
+ yaml.push(` content: |`);
633
+ file.content.split('\n').forEach((line) => yaml.push(` ${line}`));
634
+ }
635
+ });
636
+ }
637
+
648
638
  if (final_message) yaml.push(`final_message: "${final_message}"`);
649
639
 
650
640
  if (bootcmd) {
@@ -669,6 +659,34 @@ curl -X POST \\
669
659
  }
670
660
 
671
661
  return yaml.join('\n');
662
+ } /**
663
+ * @method kernelParamsFactory
664
+ * @description Generates the kernel parameters for the target machine's bootloader configuration,
665
+ * including the necessary parameters to enable cloud-init with a specific configuration URL and logging settings.
666
+ * @param {array} cmd - The existing array of kernel parameters to which cloud-init parameters will be appended.
667
+ * @param {object} options - Options for generating kernel parameters.
668
+ * @param {string} options.ipDhcpServer - The IP address of the DHCP server.
669
+ * @param {object} [options.machine] - The machine information, including system_id for constructing the cloud-init configuration URL.
670
+ * @param {string} [options.machine.system_id] - The unique identifier of the machine, used to fetch the correct cloud-init preseed configuration from MAAS.
671
+ * @return {array} The modified array of kernel parameters with cloud-init parameters included.
672
+ * @memberof UnderpostCloudInit
673
+ */,
674
+ kernelParamsFactory(
675
+ macAddress,
676
+ cmd = [],
677
+ options = {
678
+ ipDhcpServer: '',
679
+ bootstrapHttpServerPort: 8888,
680
+ hostname: '',
681
+ machine: {
682
+ system_id: '',
683
+ },
684
+ authCredentials: { consumer_key: '', consumer_secret: '', token_key: '', token_secret: '' },
685
+ },
686
+ ) {
687
+ const { ipDhcpServer, bootstrapHttpServerPort, hostname } = options;
688
+ const cloudConfigUrl = `http://${ipDhcpServer}:${bootstrapHttpServerPort}/${hostname}/cloud-init/user-data`;
689
+ return cmd.concat([`cloud-config-url=${cloudConfigUrl}`]);
672
690
  },
673
691
  };
674
692
  }
package/src/cli/deploy.js CHANGED
@@ -698,30 +698,12 @@ EOF`);
698
698
  * @memberof UnderpostDeploy
699
699
  */
700
700
  existsContainerFile({ podName, path }) {
701
- if (podName === 'kind-worker') {
702
- const isFile = JSON.parse(
703
- shellExec(`docker exec ${podName} sh -c 'test -f "$1" && echo true || echo false' sh ${path}`, {
704
- stdout: true,
705
- disableLog: true,
706
- silent: true,
707
- }).trim(),
708
- );
709
- const isFolder = JSON.parse(
710
- shellExec(`docker exec ${podName} sh -c 'test -d "$1" && echo true || echo false' sh ${path}`, {
711
- stdout: true,
712
- disableLog: true,
713
- silent: true,
714
- }).trim(),
715
- );
716
- return isFolder || isFile;
717
- }
718
- return JSON.parse(
719
- shellExec(`kubectl exec ${podName} -- test -f ${path} && echo "true" || echo "false"`, {
720
- stdout: true,
721
- disableLog: true,
722
- silent: true,
723
- }).trim(),
724
- );
701
+ const result = shellExec(`kubectl exec ${podName} -- test -f ${path} && echo "true" || echo "false"`, {
702
+ stdout: true,
703
+ disableLog: true,
704
+ silent: true,
705
+ }).trim();
706
+ return result === 'true';
725
707
  },
726
708
  /**
727
709
  * Checks the status of a deployment.
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
@@ -8,7 +8,7 @@ import fs from 'fs-extra';
8
8
  import dotenv from 'dotenv';
9
9
  import { loggerFactory } from '../server/logger.js';
10
10
  import Underpost from '../index.js';
11
- import { getUnderpostRootPath } from '../server/conf.js';
11
+ import { getNpmRootPath, getUnderpostRootPath } from '../server/conf.js';
12
12
  import { shellExec } from '../server/process.js';
13
13
 
14
14
  dotenv.config();
@@ -32,7 +32,7 @@ class UnderpostImage {
32
32
  * @param {boolean} [options.kind=false] - If true, load image into Kind cluster.
33
33
  * @param {boolean} [options.kubeadm=false] - If true, load image into Kubeadm cluster.
34
34
  * @param {boolean} [options.k3s=false] - If true, load image into K3s cluster.
35
- * @param {string} [options.path=false] - Path to the Dockerfile context.
35
+ * @param {string} [options.path=''] - Path to the Dockerfile context.
36
36
  * @param {boolean} [options.dev=false] - If true, use development mode.
37
37
  * @param {string} [options.version=''] - Version tag for the image.
38
38
  * @param {string} [options.imageName=''] - Custom name for the image.
@@ -43,7 +43,7 @@ class UnderpostImage {
43
43
  kind: false,
44
44
  kubeadm: false,
45
45
  k3s: false,
46
- path: false,
46
+ path: '',
47
47
  dev: false,
48
48
  version: '',
49
49
  imageName: '',
@@ -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]}" `; // Example: $(cat gitlab-token.txt)
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).')
@@ -165,6 +162,7 @@ program
165
162
  .option('--filter <keyword>', 'Filters the list by matching key or value (only for list operation).')
166
163
  .option('--deploy-id <deploy-id>', 'Sets the deployment configuration ID for the operation context.')
167
164
  .option('--build', 'Sets the build context for the operation.')
165
+ .option('--copy', 'Copies the configuration value to the clipboard (only for get operation).')
168
166
  .description(`Manages Underpost configurations using various operators.`)
169
167
  .action((...args) => Underpost.env[args[0]](args[1], args[2], args[3]));
170
168
 
@@ -320,8 +318,6 @@ program
320
318
  .option('--kubeadm', 'Set kubeadm cluster env image context management.')
321
319
  .option('--k3s', 'Set k3s cluster env image context management.')
322
320
  .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
321
  .option('--reset', 'Performs a build without using the cache.')
326
322
  .option('--dev', 'Use development mode.')
327
323
  .option('--pull-dockerhub <dockerhub-image>', 'Sets a custom Docker Hub image for base image pulls.')
@@ -557,6 +553,20 @@ program
557
553
  .option('--retry-count <count>', 'Sets HTTPProxy per-route retry count (e.g., 3).')
558
554
  .option('--retry-per-try-timeout <duration>', 'Sets HTTPProxy retry per-try timeout (e.g., "150ms").')
559
555
  .option('--disable-private-conf-update', 'Disables updates to private configuration during execution.')
556
+ .option('--logs', 'Streams logs during the runner execution.')
557
+ .option('--monitor-status <status>', 'Sets the status to monitor for pod/resource (default: "Running").')
558
+ .option(
559
+ '--monitor-status-kind-type <kind-type>',
560
+ 'Sets the Kubernetes resource kind type to monitor (default: "pods").',
561
+ )
562
+ .option(
563
+ '--monitor-status-delta-ms <milliseconds>',
564
+ 'Sets the polling interval in milliseconds for status monitoring (default: 1000).',
565
+ )
566
+ .option(
567
+ '--monitor-status-max-attempts <attempts>',
568
+ 'Sets the maximum number of status check attempts (default: 600).',
569
+ )
560
570
  .description('Runs specified scripts using various runners.')
561
571
  .action(Underpost.run.callback);
562
572
 
@@ -596,7 +606,13 @@ program
596
606
  .action(Underpost.lxd.callback);
597
607
 
598
608
  program
599
- .command('baremetal [workflow-id] [ip-address] [hostname] [ip-file-server] [ip-config] [netmask] [dns-server]')
609
+ .command('baremetal [workflow-id]')
610
+ .option('--ip-address <ip-address>', 'The IP address of the control server or the local machine.')
611
+ .option('--hostname <hostname>', 'The hostname of the target baremetal machine.')
612
+ .option('--ip-file-server <ip-file-server>', 'The IP address of the file server (NFS/TFTP).')
613
+ .option('--ip-config <ip-config>', 'IP configuration string for the baremetal machine.')
614
+ .option('--netmask <netmask>', 'Netmask of network.')
615
+ .option('--dns-server <dns-server>', 'DNS server IP address.')
600
616
  .option('--control-server-install', 'Installs the baremetal control server.')
601
617
  .option('--control-server-uninstall', 'Uninstalls the baremetal control server.')
602
618
  .option('--control-server-restart', 'Restarts the baremetal control server.')
@@ -609,6 +625,10 @@ program
609
625
  )
610
626
  .option('--ipxe', 'Chainloads iPXE to normalize identity before commissioning.')
611
627
  .option('--ipxe-rebuild', 'Forces rebuild of iPXE binary with embedded boot script.')
628
+ .option(
629
+ '--ipxe-build-iso <iso-path>',
630
+ 'Builds a standalone iPXE ISO with embedded script for the specified workflow ID.',
631
+ )
612
632
  .option('--install-packer', 'Installs Packer CLI.')
613
633
  .option(
614
634
  '--packer-maas-image-template <template-path>',
@@ -652,7 +672,17 @@ program
652
672
  .option('--rocky-tools-test', 'Tests rocky linux tools in chroot environment.')
653
673
  .option('--bootcmd <bootcmd-list>', 'Comma-separated list of boot commands to execute.')
654
674
  .option('--runcmd <runcmd-list>', 'Comma-separated list of run commands to execute.')
655
- .option('--logs <log-id>', 'Displays logs for log id: dhcp, cloud, machine, cloud-config.')
675
+ .option(
676
+ '--logs <log-id>',
677
+ `Displays logs for log id: ${[
678
+ 'dhcp',
679
+ 'dhcp-lease',
680
+ 'dhcp-lan',
681
+ 'cloud-init',
682
+ 'cloud-init-machine',
683
+ 'cloud-init-config',
684
+ ]}`,
685
+ )
656
686
  .option('--dev', 'Sets the development context environment for baremetal operations.')
657
687
  .option('--ls', 'Lists available boot resources and machines.')
658
688
  .description(
@@ -152,6 +152,7 @@ class UnderpostRepository {
152
152
  .map((commitData, i) => `${i === 0 ? '' : ' && '}git ${diffCmd} ${commitData.hash}`)
153
153
  .join('');
154
154
  if (history[0]) {
155
+ let index = history.length;
155
156
  for (const commit of history) {
156
157
  console.log(
157
158
  shellExec(`git show -s --format=%ci ${commit.hash}`, {
@@ -160,7 +161,8 @@ class UnderpostRepository {
160
161
  disableLog: true,
161
162
  }).trim().green,
162
163
  );
163
- console.log(commit.hash.yellow, commit.message);
164
+ console.log(`${index}`.magenta, commit.hash.yellow, commit.message);
165
+ index--;
164
166
  console.log(
165
167
  shellExec(`git show --name-status --pretty="" ${commit.hash}`, {
166
168
  stdout: true,