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.
- package/README.md +2 -2
- package/baremetal/commission-workflows.json +44 -0
- package/baremetal/packer-workflows.json +13 -0
- package/bin/deploy.js +6 -26
- package/cli.md +40 -43
- package/conf.js +4 -1
- package/examples/{QUICK-REFERENCE.md → static-page/QUICK-REFERENCE.md} +0 -18
- package/examples/{README.md → static-page/README.md} +3 -44
- package/examples/{STATIC-GENERATOR-GUIDE.md → static-page/STATIC-GENERATOR-GUIDE.md} +0 -50
- package/examples/{ssr-components → static-page/ssr-components}/CustomPage.js +0 -13
- package/examples/{static-config-simple.json → static-page/static-config-example.json} +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/packer/images/Rocky9Amd64/Makefile +62 -0
- package/packer/images/Rocky9Amd64/QUICKSTART.md +113 -0
- package/packer/images/Rocky9Amd64/README.md +122 -0
- package/packer/images/Rocky9Amd64/http/rocky9.ks.pkrtpl.hcl +114 -0
- package/packer/images/Rocky9Amd64/rocky9.pkr.hcl +160 -0
- package/packer/scripts/fuse-nbd +64 -0
- package/packer/scripts/fuse-tar-root +63 -0
- package/scripts/maas-setup.sh +13 -2
- package/scripts/maas-upload-boot-resource.sh +183 -0
- package/scripts/packer-init-vars-file.sh +30 -0
- package/scripts/packer-setup.sh +52 -0
- package/src/cli/baremetal.js +262 -65
- package/src/cli/cloud-init.js +11 -5
- package/src/cli/cron.js +161 -29
- package/src/cli/db.js +59 -92
- package/src/cli/env.js +24 -3
- package/src/cli/index.js +18 -58
- package/src/cli/repository.js +178 -0
- package/src/cli/run.js +2 -3
- package/src/cli/static.js +99 -194
- package/src/client/services/default/default.management.js +7 -0
- package/src/index.js +1 -1
- package/src/server/backup.js +4 -53
- package/src/server/conf.js +3 -4
- package/examples/static-config-example.json +0 -183
- package/src/client/ssr/pages/404.js +0 -12
- package/src/client/ssr/pages/500.js +0 -12
- package/src/client/ssr/pages/maintenance.js +0 -14
- package/src/client/ssr/pages/offline.js +0 -21
package/src/cli/baremetal.js
CHANGED
|
@@ -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
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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
|
|
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(
|
|
917
|
-
for (const mountPath of
|
|
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
|
-
* @
|
|
1257
|
-
* @
|
|
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
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
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
|
}
|
package/src/cli/cloud-init.js
CHANGED
|
@@ -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.
|
|
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===
|
|
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
|
|