underpost 2.95.3 → 2.96.0

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 (43) hide show
  1. package/README.md +2 -2
  2. package/baremetal/commission-workflows.json +44 -0
  3. package/baremetal/packer-workflows.json +13 -0
  4. package/bin/deploy.js +6 -26
  5. package/cli.md +40 -43
  6. package/conf.js +4 -1
  7. package/examples/{QUICK-REFERENCE.md → static-page/QUICK-REFERENCE.md} +0 -18
  8. package/examples/{README.md → static-page/README.md} +3 -44
  9. package/examples/{STATIC-GENERATOR-GUIDE.md → static-page/STATIC-GENERATOR-GUIDE.md} +0 -50
  10. package/examples/{ssr-components → static-page/ssr-components}/CustomPage.js +0 -13
  11. package/examples/{static-config-simple.json → static-page/static-config-example.json} +1 -1
  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 +1 -1
  15. package/packer/images/Rocky9Amd64/Makefile +62 -0
  16. package/packer/images/Rocky9Amd64/QUICKSTART.md +113 -0
  17. package/packer/images/Rocky9Amd64/README.md +122 -0
  18. package/packer/images/Rocky9Amd64/http/rocky9.ks.pkrtpl.hcl +114 -0
  19. package/packer/images/Rocky9Amd64/rocky9.pkr.hcl +160 -0
  20. package/packer/scripts/fuse-nbd +64 -0
  21. package/packer/scripts/fuse-tar-root +63 -0
  22. package/scripts/maas-setup.sh +13 -2
  23. package/scripts/maas-upload-boot-resource.sh +183 -0
  24. package/scripts/packer-init-vars-file.sh +30 -0
  25. package/scripts/packer-setup.sh +52 -0
  26. package/src/cli/baremetal.js +262 -65
  27. package/src/cli/cloud-init.js +11 -5
  28. package/src/cli/cron.js +161 -29
  29. package/src/cli/db.js +59 -92
  30. package/src/cli/env.js +24 -3
  31. package/src/cli/index.js +18 -58
  32. package/src/cli/repository.js +178 -0
  33. package/src/cli/run.js +2 -3
  34. package/src/cli/static.js +99 -194
  35. package/src/client/services/default/default.management.js +7 -0
  36. package/src/index.js +1 -1
  37. package/src/server/backup.js +4 -53
  38. package/src/server/conf.js +3 -4
  39. package/examples/static-config-example.json +0 -183
  40. package/src/client/ssr/pages/404.js +0 -12
  41. package/src/client/ssr/pages/500.js +0 -12
  42. package/src/client/ssr/pages/maintenance.js +0 -14
  43. package/src/client/ssr/pages/offline.js +0 -21
@@ -4,15 +4,19 @@
4
4
  * @namespace UnderpostBaremetal
5
5
  */
6
6
 
7
+ import { fileURLToPath } from 'url';
7
8
  import { getNpmRootPath, getUnderpostRootPath } from '../server/conf.js';
8
9
  import { openTerminal, pbcopy, shellExec } from '../server/process.js';
9
10
  import dotenv from 'dotenv';
10
11
  import { loggerFactory } from '../server/logger.js';
11
12
  import { getLocalIPv4Address } from '../server/dns.js';
12
13
  import fs from 'fs-extra';
14
+ import path from 'path';
13
15
  import Downloader from '../server/downloader.js';
14
16
  import UnderpostCloudInit from './cloud-init.js';
17
+ import UnderpostRepository from './repository.js';
15
18
  import { s4, timer } from '../client/components/core/CommonJs.js';
19
+ import { spawnSync } from 'child_process';
16
20
 
17
21
  const logger = loggerFactory(import.meta);
18
22
 
@@ -25,6 +29,19 @@ const logger = loggerFactory(import.meta);
25
29
  */
26
30
  class UnderpostBaremetal {
27
31
  static API = {
32
+ /**
33
+ * @method installPacker
34
+ * @description Installs Packer CLI.
35
+ * @memberof UnderpostBaremetal
36
+ * @returns {Promise<void>}
37
+ */
38
+ async installPacker(underpostRoot) {
39
+ const scriptPath = `${underpostRoot}/scripts/packer-setup.sh`;
40
+ logger.info(`Installing Packer using script: ${scriptPath}`);
41
+ shellExec(`sudo chmod +x ${scriptPath}`);
42
+ shellExec(`sudo ${scriptPath}`);
43
+ },
44
+
28
45
  /**
29
46
  * @method callback
30
47
  * @description Initiates a baremetal provisioning workflow based on the provided options.
@@ -40,6 +57,12 @@ class UnderpostBaremetal {
40
57
  * @param {boolean} [options.controlServerUninstall=false] - Flag to uninstall the control server.
41
58
  * @param {boolean} [options.controlServerDbInstall=false] - Flag to install the control server's database.
42
59
  * @param {boolean} [options.controlServerDbUninstall=false] - Flag to uninstall the control server's database.
60
+ * @param {boolean} [options.installPacker=false] - Flag to install Packer CLI.
61
+ * @param {string} [options.packerMaasImageTemplate] - Template path from canonical/packer-maas to extract (requires workflow-id).
62
+ * @param {string} [options.packerWorkflowId] - Workflow ID for Packer MAAS image operations (used with --packer-maas-image-build or --packer-maas-image-upload).
63
+ * @param {boolean} [options.packerMaasImageBuild=false] - Flag to build a Packer MAAS image for the workflow specified by packerWorkflowId.
64
+ * @param {boolean} [options.packerMaasImageUpload=false] - Flag to upload a Packer MAAS image artifact without rebuilding for the workflow specified by packerWorkflowId.
65
+ * @param {boolean} [options.cloudInitUpdate=false] - Flag to update cloud-init configuration on the baremetal machine.
43
66
  * @param {boolean} [options.commission=false] - Flag to commission the baremetal machine.
44
67
  * @param {boolean} [options.nfsBuild=false] - Flag to build the NFS root filesystem.
45
68
  * @param {boolean} [options.nfsMount=false] - Flag to mount the NFS root filesystem.
@@ -59,6 +82,12 @@ class UnderpostBaremetal {
59
82
  controlServerUninstall: false,
60
83
  controlServerDbInstall: false,
61
84
  controlServerDbUninstall: false,
85
+ installPacker: false,
86
+ packerMaasImageTemplate: false,
87
+ packerWorkflowId: '',
88
+ packerMaasImageBuild: false,
89
+ packerMaasImageUpload: false,
90
+ cloudInitUpdate: false,
62
91
  commission: false,
63
92
  nfsBuild: false,
64
93
  nfsMount: false,
@@ -106,6 +135,164 @@ class UnderpostBaremetal {
106
135
  // Log the initiation of the baremetal callback with relevant metadata.
107
136
  logger.info('Baremetal callback', callbackMetaData);
108
137
 
138
+ if (options.installPacker) {
139
+ await UnderpostBaremetal.API.installPacker(underpostRoot);
140
+ return;
141
+ }
142
+
143
+ if (options.packerMaasImageTemplate) {
144
+ if (!workflowId) {
145
+ throw new Error('workflow-id is required when using --packer-maas-image-template');
146
+ }
147
+
148
+ const templatePath = options.packerMaasImageTemplate;
149
+ const targetDir = `${underpostRoot}/packer/images/${workflowId}`;
150
+
151
+ logger.info(`Creating new Packer MAAS image template for workflow: ${workflowId}`);
152
+ logger.info(`Template path: ${templatePath}`);
153
+ logger.info(`Target directory: ${targetDir}`);
154
+
155
+ try {
156
+ // Use UnderpostRepository to copy files from GitHub
157
+ const result = await UnderpostRepository.API.copyGitUrlDirectoryRecursive({
158
+ gitUrl: 'https://github.com/canonical/packer-maas',
159
+ directoryPath: templatePath,
160
+ targetPath: targetDir,
161
+ branch: 'main',
162
+ overwrite: false,
163
+ });
164
+
165
+ logger.info(`\nSuccessfully copied ${result.filesCount} files`);
166
+
167
+ // Create empty workflow configuration entry
168
+ const workflowConfig = {
169
+ dir: `packer/images/${workflowId}`,
170
+ maas: {
171
+ name: `custom/${workflowId.toLowerCase()}`,
172
+ title: `${workflowId} Custom`,
173
+ architecture: 'amd64/generic',
174
+ base_image: 'ubuntu/22.04',
175
+ filetype: 'tgz',
176
+ content: `${workflowId.toLowerCase()}.tar.gz`,
177
+ },
178
+ };
179
+
180
+ const workflows = UnderpostBaremetal.API.loadPackerMaasImageBuildWorkflows();
181
+ workflows[workflowId] = workflowConfig;
182
+ UnderpostBaremetal.API.writePackerMaasImageBuildWorkflows(workflows);
183
+
184
+ logger.info('\nTemplate extracted successfully!');
185
+ logger.info(`\nAdded configuration for ${workflowId} to engine/baremetal/packer-workflows.json`);
186
+ logger.info('\nNext steps:');
187
+ logger.info(`1. Review and customize the Packer template files in: ${targetDir}`);
188
+ logger.info(`2. Review the workflow configuration in engine/baremetal/packer-workflows.json`);
189
+ logger.info(
190
+ `3. Build the image with: underpost baremetal ${workflowId} --packer-maas-image-build ${workflowId}`,
191
+ );
192
+ } catch (error) {
193
+ throw new Error(`Failed to extract template: ${error.message}`);
194
+ }
195
+
196
+ return;
197
+ }
198
+
199
+ if (options.packerMaasImageBuild || options.packerMaasImageUpload) {
200
+ // Use the workflow ID from --packer-workflow-id option
201
+ if (!options.packerWorkflowId) {
202
+ throw new Error('Workflow ID is required. Please specify using --packer-workflow-id <workflow-id>');
203
+ }
204
+
205
+ workflowId = options.packerWorkflowId;
206
+
207
+ const workflow = UnderpostBaremetal.API.loadPackerMaasImageBuildWorkflows()[workflowId];
208
+ if (!workflow) {
209
+ throw new Error(`Packer MAAS image build workflow not found: ${workflowId}`);
210
+ }
211
+ const packerDir = `${underpostRoot}/${workflow.dir}`;
212
+ const tarballPath = `${packerDir}/${workflow.maas.content}`;
213
+
214
+ // Build phase (skip if upload-only mode)
215
+ if (options.packerMaasImageBuild) {
216
+ if (shellExec('packer version', { silent: true }).code !== 0) {
217
+ throw new Error('Packer is not installed. Please install Packer to proceed.');
218
+ }
219
+
220
+ logger.info(`Building Packer image for ${workflowId} in ${packerDir}...`);
221
+ const artifacts = ['output-rocky9', 'packer_cache', 'x86_64_VARS.fd', 'rocky9.tar.gz'];
222
+ shellExec(`cd packer/images/${workflowId}
223
+ rm -rf ${artifacts.join(' ')}`);
224
+ shellExec(`chmod +x ${underpostRoot}/scripts/packer-init-vars-file.sh`);
225
+ shellExec(`${underpostRoot}/scripts/packer-init-vars-file.sh`);
226
+
227
+ const init = spawnSync('packer', ['init', '.'], { stdio: 'inherit', cwd: packerDir });
228
+ if (init.status !== 0) {
229
+ throw new Error('Packer init failed');
230
+ }
231
+
232
+ const build = spawnSync('packer', ['build', '.'], {
233
+ stdio: 'inherit',
234
+ cwd: packerDir,
235
+ env: { ...process.env, PACKER_LOG: '1' },
236
+ });
237
+
238
+ if (build.status !== 0) {
239
+ throw new Error('Packer build failed');
240
+ }
241
+ } else {
242
+ // Upload-only mode: verify tarball exists
243
+ logger.info(`Upload-only mode: checking for existing build artifact...`);
244
+ if (!fs.existsSync(tarballPath)) {
245
+ throw new Error(
246
+ `Build artifact not found: ${tarballPath}\n` +
247
+ `Please build first with: --packer-maas-image-build ${workflowId}`,
248
+ );
249
+ }
250
+ const stats = fs.statSync(tarballPath);
251
+ logger.info(`Found existing artifact: ${tarballPath} (${(stats.size / 1024 / 1024 / 1024).toFixed(2)} GB)`);
252
+ }
253
+
254
+ logger.info(`Uploading image to MAAS...`);
255
+
256
+ // Detect MAAS profile from 'maas list' output
257
+ let maasProfile = process.env.MAAS_ADMIN_USERNAME;
258
+ if (!maasProfile) {
259
+ const profileList = shellExec('maas list', { silent: true, stdout: true });
260
+ if (profileList) {
261
+ const firstLine = profileList.trim().split('\n')[0];
262
+ const match = firstLine.match(/^(\S+)\s+http/);
263
+ if (match) {
264
+ maasProfile = match[1];
265
+ logger.info(`Detected MAAS profile: ${maasProfile}`);
266
+ }
267
+ }
268
+ }
269
+
270
+ if (!maasProfile) {
271
+ throw new Error(
272
+ 'MAAS profile not found. Please run "maas login" first or set MAAS_ADMIN_USERNAME environment variable.',
273
+ );
274
+ }
275
+
276
+ // Use the upload script to avoid MAAS CLI bugs
277
+ const uploadScript = `${underpostRoot}/scripts/maas-upload-boot-resource.sh`;
278
+ const uploadCmd = `${uploadScript} ${maasProfile} "${workflow.maas.name}" "${workflow.maas.title}" "${workflow.maas.architecture}" "${workflow.maas.base_image}" "${workflow.maas.filetype}" "${tarballPath}"`;
279
+
280
+ logger.info(`Uploading to MAAS using: ${uploadScript}`);
281
+ const uploadResult = shellExec(uploadCmd);
282
+ if (uploadResult.code !== 0) {
283
+ logger.error(`Upload failed with exit code: ${uploadResult.code}`);
284
+ if (uploadResult.stdout) {
285
+ logger.error(`Upload output:\n${uploadResult.stdout}`);
286
+ }
287
+ if (uploadResult.stderr) {
288
+ logger.error(`Upload error output:\n${uploadResult.stderr}`);
289
+ }
290
+ throw new Error('MAAS upload failed - see output above for details');
291
+ }
292
+ logger.info(`Successfully uploaded ${workflow.maas.name} to MAAS!`);
293
+ return;
294
+ }
295
+
109
296
  // Handle various log display options.
110
297
  if (options.logs === 'dhcp') {
111
298
  shellExec(`journalctl -f -t dhcpd -u snap.maas.pebble.service`);
@@ -129,7 +316,11 @@ class UnderpostBaremetal {
129
316
 
130
317
  // Handle NFS shell access option.
131
318
  if (options.nfsSh === true) {
132
- const { debootstrap } = UnderpostBaremetal.API.workflowsConfig[workflowId];
319
+ const workflowsConfig = UnderpostBaremetal.API.loadWorkflowsConfig();
320
+ if (!workflowsConfig[workflowId]) {
321
+ throw new Error(`Workflow configuration not found for ID: ${workflowId}`);
322
+ }
323
+ const { debootstrap } = workflowsConfig[workflowId];
133
324
  // Copy the chroot command to the clipboard for easy execution.
134
325
  if (debootstrap.image.architecture !== callbackMetaData.runnerHost.architecture)
135
326
  switch (debootstrap.image.architecture) {
@@ -194,9 +385,14 @@ class UnderpostBaremetal {
194
385
  return;
195
386
  }
196
387
 
388
+ const workflowsConfig = UnderpostBaremetal.API.loadWorkflowsConfig();
389
+ if (!workflowsConfig[workflowId]) {
390
+ throw new Error(`Workflow configuration not found for ID: ${workflowId}`);
391
+ }
392
+
197
393
  // Set debootstrap architecture.
198
394
  {
199
- const { architecture } = UnderpostBaremetal.API.workflowsConfig[workflowId].debootstrap.image;
395
+ const { architecture } = workflowsConfig[workflowId].debootstrap.image;
200
396
  debootstrapArch = architecture;
201
397
  }
202
398
 
@@ -216,11 +412,6 @@ class UnderpostBaremetal {
216
412
  if (options.nfsBuild === true) {
217
413
  // Check if NFS is already mounted to avoid redundant builds.
218
414
  const { isMounted } = UnderpostBaremetal.API.nfsMountCallback({ hostname, workflowId });
219
- if (isMounted) {
220
- logger.warn('NFS root filesystem is mounted, skipping build.');
221
- return; // Exit if already mounted.
222
- }
223
- logger.info('NFS root filesystem is not mounted, building...');
224
415
 
225
416
  // Clean and create the NFS host path.
226
417
  shellExec(`sudo rm -rf ${nfsHostPath}/*`);
@@ -231,7 +422,7 @@ class UnderpostBaremetal {
231
422
 
232
423
  // Perform the first stage of debootstrap.
233
424
  {
234
- const { architecture, name } = UnderpostBaremetal.API.workflowsConfig[workflowId].debootstrap.image;
425
+ const { architecture, name } = workflowsConfig[workflowId].debootstrap.image;
235
426
  shellExec(
236
427
  [
237
428
  `sudo debootstrap`,
@@ -276,7 +467,7 @@ class UnderpostBaremetal {
276
467
 
277
468
  // Apply system provisioning steps (base, user, timezone, keyboard).
278
469
  {
279
- const { systemProvisioning, kernelLibVersion, chronyc } = UnderpostBaremetal.API.workflowsConfig[workflowId];
470
+ const { systemProvisioning, kernelLibVersion, chronyc } = workflowsConfig[workflowId];
280
471
  const { timezone, chronyConfPath } = chronyc;
281
472
 
282
473
  UnderpostBaremetal.API.crossArchRunner({
@@ -330,8 +521,7 @@ class UnderpostBaremetal {
330
521
 
331
522
  // Handle commissioning tasks (placeholder for future implementation).
332
523
  if (options.commission === true) {
333
- const { firmwares, networkInterfaceName, maas, netmask, menuentryStr } =
334
- UnderpostBaremetal.API.workflowsConfig[workflowId];
524
+ const { firmwares, networkInterfaceName, maas, netmask, menuentryStr } = workflowsConfig[workflowId];
335
525
  const resource = resources.find(
336
526
  (o) => o.architecture === maas.image.architecture && o.name === maas.image.name,
337
527
  );
@@ -493,7 +683,7 @@ menuentry '${menuentryStr}' {
493
683
 
494
684
  // Final commissioning steps.
495
685
  if (options.commission === true || options.cloudInitUpdate === true) {
496
- const { debootstrap, networkInterfaceName, chronyc, maas } = UnderpostBaremetal.API.workflowsConfig[workflowId];
686
+ const { debootstrap, networkInterfaceName, chronyc, maas } = workflowsConfig[workflowId];
497
687
  const { timezone, chronyConfPath } = chronyc;
498
688
 
499
689
  // Build cloud-init tools.
@@ -630,8 +820,13 @@ menuentry '${menuentryStr}' {
630
820
  const machine = {
631
821
  architecture: maas.image.architecture.match('amd') ? 'amd64/generic' : 'arm64/generic',
632
822
  mac_address: discovery.mac_address,
633
- hostname:
634
- discovery.hostname ?? discovery.mac_organization ?? discovery.domain ?? `generic-host-${s4()}${s4()}`,
823
+ hostname: discovery.hostname
824
+ ? discovery.hostname
825
+ : discovery.mac_organization
826
+ ? discovery.mac_organization
827
+ : discovery.domain
828
+ ? discovery.domain
829
+ : `generic-host-${s4()}${s4()}`,
635
830
  power_type: 'manual',
636
831
  mac_addresses: discovery.mac_address,
637
832
  ip: discovery.ip,
@@ -740,9 +935,9 @@ menuentry '${menuentryStr}' {
740
935
  // Install necessary packages for debootstrap and QEMU.
741
936
  shellExec(`sudo dnf install -y iptables-legacy`);
742
937
  shellExec(`sudo dnf install -y debootstrap`);
743
- shellExec(`sudo dnf install kernel-modules-extra-$(uname -r)`);
938
+ shellExec(`sudo dnf install -y kernel-modules-extra-$(uname -r)`);
744
939
  // Reset QEMU user-static binfmt for proper cross-architecture execution.
745
- shellExec(`sudo podman run --rm --privileged multiarch/qemu-user-static --reset -p yes`);
940
+ shellExec(`sudo podman run --rm --privileged docker.io/multiarch/qemu-user-static:latest --reset -p yes`);
746
941
  // Mount binfmt_misc filesystem.
747
942
  shellExec(`sudo modprobe binfmt_misc`);
748
943
  shellExec(`sudo mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc`);
@@ -826,8 +1021,8 @@ menuentry '${menuentryStr}' {
826
1021
  break;
827
1022
  }
828
1023
  // Install GRUB EFI modules for both architectures to ensure compatibility.
829
- shellExec(`sudo dnf install grub2-efi-aa64-modules`);
830
- shellExec(`sudo dnf install grub2-efi-x64-modules`);
1024
+ shellExec(`sudo dnf install -y grub2-efi-aa64-modules`);
1025
+ shellExec(`sudo dnf install -y grub2-efi-x64-modules`);
831
1026
  },
832
1027
 
833
1028
  /**
@@ -912,9 +1107,13 @@ EOF`);
912
1107
  */
913
1108
  nfsMountCallback({ hostname, workflowId, mount, unmount }) {
914
1109
  let isMounted = false;
1110
+ const workflowsConfig = UnderpostBaremetal.API.loadWorkflowsConfig();
1111
+ if (!workflowsConfig[workflowId]) {
1112
+ throw new Error(`Workflow configuration not found for ID: ${workflowId}`);
1113
+ }
915
1114
  // Iterate through defined NFS mounts in the workflow configuration.
916
- for (const mountCmd of Object.keys(UnderpostBaremetal.API.workflowsConfig[workflowId].nfs.mounts)) {
917
- for (const mountPath of UnderpostBaremetal.API.workflowsConfig[workflowId].nfs.mounts[mountCmd]) {
1115
+ for (const mountCmd of Object.keys(workflowsConfig[workflowId].nfs.mounts)) {
1116
+ for (const mountPath of workflowsConfig[workflowId].nfs.mounts[mountCmd]) {
918
1117
  const hostMountPath = `${process.env.NFS_EXPORT_PATH}/${hostname}${mountPath}`;
919
1118
  // Check if the path is already mounted using `mountpoint` command.
920
1119
  const isPathMounted = !shellExec(`mountpoint ${hostMountPath}`, { silent: true, stdout: true }).match(
@@ -1044,6 +1243,8 @@ SOURCES`,
1044
1243
  `export DEBIAN_FRONTEND=noninteractive`, // Set non-interactive mode for Debian packages.
1045
1244
  `ln -fs /usr/share/zoneinfo/${timezone} /etc/localtime`, // Symlink timezone.
1046
1245
  `sudo dpkg-reconfigure --frontend noninteractive tzdata`, // Reconfigure timezone data.
1246
+ `sudo timedatectl set-timezone ${timezone}`, // Set timezone using timedatectl.
1247
+ `sudo timedatectl set-ntp true`, // Enable NTP synchronization.
1047
1248
 
1048
1249
  // Write the Chrony configuration file.
1049
1250
  `echo '
@@ -1092,6 +1293,11 @@ logdir /var/log/chrony
1092
1293
  // Enable, restart, and check status of Chrony service.
1093
1294
  `sudo systemctl enable --now ${alias}`,
1094
1295
  `sudo systemctl restart ${alias}`,
1296
+
1297
+ // Wait for chrony to synchronize
1298
+ `echo "Waiting for chrony to synchronize..."`,
1299
+ `for i in {1..30}; do chronyc tracking | grep -q "Leap status : Normal" && break || sleep 2; done`,
1300
+
1095
1301
  `sudo systemctl status ${alias}`,
1096
1302
 
1097
1303
  // Verify Chrony synchronization.
@@ -1253,55 +1459,46 @@ GATEWAY=${gateway}`;
1253
1459
  },
1254
1460
 
1255
1461
  /**
1256
- * @property {object} workflowsConfig
1257
- * @description Configuration for different baremetal provisioning workflows.
1462
+ * @method loadWorkflowsConfig
1463
+ * @namespace UnderpostBaremetal.API
1464
+ * @description Loads the commission workflows configuration from commission-workflows.json.
1258
1465
  * Each workflow defines specific parameters like system provisioning type,
1259
1466
  * kernel version, Chrony settings, debootstrap image details, and NFS mounts. *
1260
1467
  * @memberof UnderpostBaremetal
1261
1468
  */
1262
- workflowsConfig: {
1263
- /**
1264
- * @property {object} rpi4mb
1265
- * @description Configuration for the Raspberry Pi 4 Model B workflow.
1266
- * @memberof UnderpostBaremetal.workflowsConfig
1267
- */
1268
- rpi4mb: {
1269
- menuentryStr: 'UNDERPOST.NET UEFI/GRUB/MAAS RPi4 commissioning (ARM64)',
1270
- systemProvisioning: 'ubuntu', // Specifies the system provisioning factory to use.
1271
- kernelLibVersion: `6.8.0-41-generic`, // The kernel library version for this workflow.
1272
- networkInterfaceName: 'enabcm6e4ei0', // The name of the primary network interface on the RPi4.
1273
- netmask: '255.255.255.0', // Subnet mask for the network.
1274
- firmwares: [
1275
- {
1276
- url: 'https://github.com/pftf/RPi4/releases/download/v1.41/RPi4_UEFI_Firmware_v1.41.zip',
1277
- gateway: '192.168.1.1',
1278
- subnet: '255.255.255.0',
1279
- },
1280
- ],
1281
- chronyc: {
1282
- timezone: 'America/New_York', // Timezone for Chrony configuration.
1283
- chronyConfPath: `/etc/chrony/chrony.conf`, // Path to Chrony configuration file.
1284
- },
1285
- debootstrap: {
1286
- image: {
1287
- architecture: 'arm64', // Architecture for the debootstrap image.
1288
- name: 'noble', // Codename of the Ubuntu release (e.g., 'noble' for 24.04 LTS).
1289
- },
1290
- },
1291
- maas: {
1292
- image: {
1293
- architecture: 'arm64/ga-24.04', // Architecture for MAAS image.
1294
- name: 'ubuntu/noble', // Name of the MAAS Ubuntu image.
1295
- },
1296
- },
1297
- nfs: {
1298
- mounts: {
1299
- // Define NFS mount points and their types (bind, rbind).
1300
- bind: ['/proc', '/sys', '/run'], // Standard bind mounts.
1301
- rbind: ['/dev'], // Recursive bind mount for /dev.
1302
- },
1303
- },
1304
- },
1469
+ loadWorkflowsConfig() {
1470
+ if (this._workflowsConfig) return this._workflowsConfig;
1471
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1472
+ const configPath = path.resolve(__dirname, '../../baremetal/commission-workflows.json');
1473
+ this._workflowsConfig = fs.readJsonSync(configPath);
1474
+ return this._workflowsConfig;
1475
+ },
1476
+
1477
+ /**
1478
+ * @property {object} packerMaasImageBuildWorkflows
1479
+ * @description Configuration for PACKe mass image workflows.
1480
+ * @memberof UnderpostBaremetal
1481
+ */
1482
+ loadPackerMaasImageBuildWorkflows() {
1483
+ if (this._packerMaasImageBuildWorkflows) return this._packerMaasImageBuildWorkflows;
1484
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1485
+ const configPath = path.resolve(__dirname, '../../baremetal/packer-workflows.json');
1486
+ this._packerMaasImageBuildWorkflows = fs.readJsonSync(configPath);
1487
+ return this._packerMaasImageBuildWorkflows;
1488
+ },
1489
+
1490
+ /**
1491
+ * Write Packer MAAS image build workflows configuration to file
1492
+ * @param {object} workflows - The workflows configuration object
1493
+ * @description Writes the Packer MAAS image build workflows to packer-workflows.json
1494
+ * @memberof UnderpostBaremetal
1495
+ */
1496
+ writePackerMaasImageBuildWorkflows(workflows) {
1497
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1498
+ const configPath = path.resolve(__dirname, '../../baremetal/packer-workflows.json');
1499
+ fs.writeJsonSync(configPath, workflows, { spaces: 2 });
1500
+ this._packerMaasImageBuildWorkflows = workflows;
1501
+ return configPath;
1305
1502
  },
1306
1503
  };
1307
1504
  }
@@ -43,7 +43,7 @@ class UnderpostCloudInit {
43
43
  buildTools({ workflowId, nfsHostPath, hostname, callbackMetaData, dev }) {
44
44
  // Destructure workflow configuration for easier access.
45
45
  const { systemProvisioning, chronyc, networkInterfaceName, debootstrap } =
46
- UnderpostBaremetal.API.workflowsConfig[workflowId];
46
+ UnderpostBaremetal.API.loadWorkflowsConfig()[workflowId];
47
47
  const { timezone, chronyConfPath } = chronyc;
48
48
  // Define the specific directory for underpost tools within the NFS host path.
49
49
  const nfsHostToolsPath = `${nfsHostPath}/underpost`;
@@ -159,12 +159,18 @@ echo "sudo cloud-init modules --mode=final"`,
159
159
  logger.info('Build', `${nfsHostToolsPath}/test.sh`);
160
160
  fs.writeFileSync(
161
161
  `${nfsHostToolsPath}/test.sh`,
162
- `echo -e "\n=== Current date/time ==="
162
+ `echo -e "\necho -e "\n=== Registered users ==="
163
+ cut -d: -f1 /etc/passwd
164
+ === Current date/time ==="
163
165
  date '+%Y-%m-%d %H:%M:%S'
166
+ echo -e "\n=== Timezone Configuration ==="
167
+ timedatectl status
168
+ echo -e "\n=== Chrony Synchronization Status ==="
169
+ chronyc tracking
170
+ echo -e "\n=== Chrony Sources ==="
171
+ chronyc sources
164
172
  echo -e "\n=== Keyboard layout ==="
165
- cat /etc/default/keyboard
166
- echo -e "\n=== Registered users ==="
167
- cut -d: -f1 /etc/passwd`,
173
+ cat /etc/default/keyboard`,
168
174
  'utf8',
169
175
  );
170
176