@underpostnet/underpost 2.99.5 → 2.99.7
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/.github/workflows/ghpkg.ci.yml +10 -25
- package/.github/workflows/npmpkg.ci.yml +13 -2
- package/CHANGELOG.md +496 -0
- package/README.md +4 -4
- package/baremetal/commission-workflows.json +43 -6
- package/bin/deploy.js +13 -0
- package/cli.md +84 -42
- package/examples/static-page/README.md +80 -13
- package/jsdoc.json +26 -5
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +47 -0
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +47 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +2 -4
- package/scripts/maas-setup.sh +13 -9
- package/scripts/rocky-kickstart.sh +294 -0
- package/src/cli/baremetal.js +237 -555
- package/src/cli/cloud-init.js +27 -45
- package/src/cli/index.js +52 -6
- package/src/cli/kickstart.js +149 -0
- package/src/cli/repository.js +166 -13
- package/src/cli/run.js +26 -19
- package/src/cli/ssh.js +1 -1
- package/src/cli/static.js +27 -1
- package/src/cli/system.js +332 -0
- package/src/client/components/core/Docs.js +22 -3
- package/src/db/DataBaseProvider.js +3 -3
- package/src/db/mariadb/MariaDB.js +3 -3
- package/src/db/mongo/MongooseDB.js +3 -3
- package/src/index.js +28 -5
- package/src/mailer/EmailRender.js +3 -3
- package/src/mailer/MailerProvider.js +4 -4
- package/src/server/backup.js +23 -5
- package/src/server/client-build-docs.js +29 -3
- package/src/server/conf.js +6 -27
- package/src/server/cron.js +354 -135
- package/src/server/dns.js +2 -0
package/src/cli/baremetal.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { getNpmRootPath, getUnderpostRootPath } from '../server/conf.js';
|
|
9
|
-
import {
|
|
9
|
+
import { pbcopy, shellExec } from '../server/process.js';
|
|
10
10
|
import dotenv from 'dotenv';
|
|
11
11
|
import { loggerFactory, loggerMiddleware } from '../server/logger.js';
|
|
12
12
|
import fs from 'fs-extra';
|
|
@@ -65,6 +65,7 @@ class UnderpostBaremetal {
|
|
|
65
65
|
* @param {boolean} [options.commission=false] - Flag to commission the baremetal machine.
|
|
66
66
|
* @param {number} [options.bootstrapHttpServerPort=8888] - Port for the bootstrap HTTP server.
|
|
67
67
|
* @param {string} [options.bootstrapHttpServerPath='./public/localhost'] - Path for the bootstrap HTTP server files.
|
|
68
|
+
* @param {boolean} [options.bootstrapHttpServerRun=false] - Flag to start the bootstrap HTTP server.
|
|
68
69
|
* @param {string} [options.isoUrl=''] - Uses a custom ISO URL for baremetal machine commissioning.
|
|
69
70
|
* @param {boolean} [options.ubuntuToolsBuild=false] - Builds ubuntu tools for chroot environment.
|
|
70
71
|
* @param {boolean} [options.ubuntuToolsTest=false] - Tests ubuntu tools in chroot environment.
|
|
@@ -75,6 +76,7 @@ class UnderpostBaremetal {
|
|
|
75
76
|
* @param {boolean} [options.nfsBuild=false] - Flag to build the NFS root filesystem.
|
|
76
77
|
* @param {boolean} [options.nfsBuildServer=false] - Flag to build the NFS server components.
|
|
77
78
|
* @param {boolean} [options.nfsMount=false] - Flag to mount the NFS root filesystem.
|
|
79
|
+
* @param {boolean} [options.nfsReset=false] - Flag to reset the NFS environment by unmounting and cleaning the host path.
|
|
78
80
|
* @param {boolean} [options.nfsUnmount=false] - Flag to unmount the NFS root filesystem.
|
|
79
81
|
* @param {boolean} [options.nfsSh=false] - Flag to chroot into the NFS environment for shell access.
|
|
80
82
|
* @param {string} [options.logs=''] - Specifies which logs to display ('dhcp', 'cloud', 'machine', 'cloud-config').
|
|
@@ -114,6 +116,7 @@ class UnderpostBaremetal {
|
|
|
114
116
|
commission: false,
|
|
115
117
|
bootstrapHttpServerPort: 8888,
|
|
116
118
|
bootstrapHttpServerPath: './public/localhost',
|
|
119
|
+
bootstrapHttpServerRun: false,
|
|
117
120
|
isoUrl: '',
|
|
118
121
|
ubuntuToolsBuild: false,
|
|
119
122
|
ubuntuToolsTest: false,
|
|
@@ -124,6 +127,7 @@ class UnderpostBaremetal {
|
|
|
124
127
|
nfsBuild: false,
|
|
125
128
|
nfsBuildServer: false,
|
|
126
129
|
nfsMount: false,
|
|
130
|
+
nfsReset: false,
|
|
127
131
|
nfsUnmount: false,
|
|
128
132
|
nfsSh: false,
|
|
129
133
|
logs: '',
|
|
@@ -184,47 +188,6 @@ class UnderpostBaremetal {
|
|
|
184
188
|
// Define the TFTP root prefix path based
|
|
185
189
|
const tftpRootPath = `${process.env.TFTP_ROOT}/${tftpPrefix}`;
|
|
186
190
|
|
|
187
|
-
if (options.ipxeBuildIso) {
|
|
188
|
-
let machine = null;
|
|
189
|
-
|
|
190
|
-
if (options.cloudInit) {
|
|
191
|
-
// Search for an existing machine by hostname to extract system_id for cloud-init
|
|
192
|
-
const [searchMachine] = Underpost.baremetal.maasCliExec(`machines read hostname=${hostname}`);
|
|
193
|
-
|
|
194
|
-
if (searchMachine) {
|
|
195
|
-
logger.info(`Found existing machine ${hostname} with system_id ${searchMachine.system_id}`);
|
|
196
|
-
machine = searchMachine;
|
|
197
|
-
} else {
|
|
198
|
-
// Machine does not exist, create it to obtain a system_id
|
|
199
|
-
logger.info(`Machine ${hostname} not found, creating new machine for cloud-init system_id...`);
|
|
200
|
-
machine = Underpost.baremetal.machineFactory({
|
|
201
|
-
hostname,
|
|
202
|
-
ipAddress,
|
|
203
|
-
macAddress,
|
|
204
|
-
architecture: workflowsConfig[workflowId].architecture,
|
|
205
|
-
}).machine;
|
|
206
|
-
logger.info(`✓ Machine created with system_id ${machine.system_id}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
await Underpost.baremetal.ipxeBuildIso({
|
|
211
|
-
workflowId,
|
|
212
|
-
isoOutputPath: options.ipxeBuildIso,
|
|
213
|
-
tftpPrefix,
|
|
214
|
-
ipFileServer,
|
|
215
|
-
ipAddress,
|
|
216
|
-
ipConfig,
|
|
217
|
-
netmask,
|
|
218
|
-
dnsServer,
|
|
219
|
-
macAddress,
|
|
220
|
-
cloudInit: options.cloudInit,
|
|
221
|
-
machine,
|
|
222
|
-
dev: options.dev,
|
|
223
|
-
bootstrapHttpServerPort: options.bootstrapHttpServerPort,
|
|
224
|
-
});
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
191
|
// Define the iPXE cache directory to preserve builds across tftproot cleanups
|
|
229
192
|
const ipxeCacheDir = `/tmp/ipxe-cache/${tftpPrefix}`;
|
|
230
193
|
|
|
@@ -296,6 +259,27 @@ class UnderpostBaremetal {
|
|
|
296
259
|
}
|
|
297
260
|
}
|
|
298
261
|
|
|
262
|
+
if (options.ipxeBuildIso)
|
|
263
|
+
return await Underpost.baremetal.ipxeBuildIso({
|
|
264
|
+
workflowId,
|
|
265
|
+
isoOutputPath: options.ipxeBuildIso,
|
|
266
|
+
tftpPrefix,
|
|
267
|
+
ipFileServer,
|
|
268
|
+
ipAddress,
|
|
269
|
+
ipConfig,
|
|
270
|
+
netmask,
|
|
271
|
+
dnsServer,
|
|
272
|
+
macAddress,
|
|
273
|
+
cloudInit: options.cloudInit,
|
|
274
|
+
dev: options.dev,
|
|
275
|
+
forceRebuild: options.ipxeRebuild,
|
|
276
|
+
bootstrapHttpServerPort: Underpost.baremetal.bootstrapHttpServerPortFactory({
|
|
277
|
+
port: options.bootstrapHttpServerPort,
|
|
278
|
+
workflowId,
|
|
279
|
+
workflowsConfig,
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
|
|
299
283
|
if (options.installPacker) {
|
|
300
284
|
await Underpost.baremetal.installPacker(underpostRoot);
|
|
301
285
|
return;
|
|
@@ -438,29 +422,9 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
438
422
|
|
|
439
423
|
logger.info(`Uploading image to MAAS...`);
|
|
440
424
|
|
|
441
|
-
// Detect MAAS profile from 'maas list' output
|
|
442
|
-
let maasProfile = process.env.MAAS_ADMIN_USERNAME;
|
|
443
|
-
if (!maasProfile) {
|
|
444
|
-
const profileList = shellExec('maas list', { silent: true, stdout: true });
|
|
445
|
-
if (profileList) {
|
|
446
|
-
const firstLine = profileList.trim().split('\n')[0];
|
|
447
|
-
const match = firstLine.match(/^(\S+)\s+http/);
|
|
448
|
-
if (match) {
|
|
449
|
-
maasProfile = match[1];
|
|
450
|
-
logger.info(`Detected MAAS profile: ${maasProfile}`);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (!maasProfile) {
|
|
456
|
-
throw new Error(
|
|
457
|
-
'MAAS profile not found. Please run "maas login" first or set MAAS_ADMIN_USERNAME environment variable.',
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
425
|
// Use the upload script to avoid MAAS CLI bugs
|
|
462
426
|
const uploadScript = `${underpostRoot}/scripts/maas-upload-boot-resource.sh`;
|
|
463
|
-
const uploadCmd = `${uploadScript} ${
|
|
427
|
+
const uploadCmd = `${uploadScript} ${process.env.MAAS_ADMIN_USERNAME} "${workflow.maas.name}" "${workflow.maas.title}" "${workflow.maas.architecture}" "${workflow.maas.base_image}" "${workflow.maas.filetype}" "${tarballPath}"`;
|
|
464
428
|
|
|
465
429
|
logger.info(`Uploading to MAAS using: ${uploadScript}`);
|
|
466
430
|
const uploadResult = shellExec(uploadCmd);
|
|
@@ -703,7 +667,11 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
703
667
|
bootstrapArch,
|
|
704
668
|
callbackMetaData,
|
|
705
669
|
steps: [
|
|
706
|
-
`dnf install -y --allowerasing ${allPackages.join(
|
|
670
|
+
`dnf install -y --allowerasing ${allPackages.join(
|
|
671
|
+
' ',
|
|
672
|
+
)} 2>/dev/null || yum install -y --allowerasing ${allPackages.join(
|
|
673
|
+
' ',
|
|
674
|
+
)} 2>/dev/null || echo "Package install completed"`,
|
|
707
675
|
`dnf clean all`,
|
|
708
676
|
`echo "=== Installed packages verification ==="`,
|
|
709
677
|
`rpm -qa | grep -E "dracut|kernel|nfs" | sort`,
|
|
@@ -805,13 +773,13 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
805
773
|
bootstrapArch,
|
|
806
774
|
callbackMetaData,
|
|
807
775
|
steps: [
|
|
808
|
-
...Underpost.
|
|
809
|
-
...Underpost.
|
|
810
|
-
...Underpost.
|
|
776
|
+
...Underpost.system.factory[systemProvisioning].base(),
|
|
777
|
+
...Underpost.system.factory[systemProvisioning].user(),
|
|
778
|
+
...Underpost.system.factory[systemProvisioning].timezone({
|
|
811
779
|
timezone,
|
|
812
780
|
chronyConfPath,
|
|
813
781
|
}),
|
|
814
|
-
...Underpost.
|
|
782
|
+
...Underpost.system.factory[systemProvisioning].keyboard(keyboard.layout),
|
|
815
783
|
],
|
|
816
784
|
});
|
|
817
785
|
}
|
|
@@ -833,7 +801,6 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
833
801
|
`chmod +x /underpost/shutdown.sh`,
|
|
834
802
|
`chmod +x /underpost/device_scan.sh`,
|
|
835
803
|
`chmod +x /underpost/mac.sh`,
|
|
836
|
-
`chmod +x /underpost/enlistment.sh`,
|
|
837
804
|
`sudo chmod 700 ~/.ssh/`, // Set secure permissions for .ssh directory.
|
|
838
805
|
`sudo chmod 600 ~/.ssh/authorized_keys`, // Set secure permissions for authorized_keys.
|
|
839
806
|
`sudo chmod 644 ~/.ssh/known_hosts`, // Set permissions for known_hosts.
|
|
@@ -859,13 +826,13 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
859
826
|
bootstrapArch,
|
|
860
827
|
callbackMetaData,
|
|
861
828
|
steps: [
|
|
862
|
-
...Underpost.
|
|
863
|
-
...Underpost.
|
|
864
|
-
...Underpost.
|
|
829
|
+
...Underpost.system.factory[systemProvisioning].base(),
|
|
830
|
+
...Underpost.system.factory[systemProvisioning].user(),
|
|
831
|
+
...Underpost.system.factory[systemProvisioning].timezone({
|
|
865
832
|
timezone,
|
|
866
833
|
chronyConfPath: chronyc.chronyConfPath,
|
|
867
834
|
}),
|
|
868
|
-
...Underpost.
|
|
835
|
+
...Underpost.system.factory[systemProvisioning].keyboard(keyboard.layout),
|
|
869
836
|
],
|
|
870
837
|
});
|
|
871
838
|
}
|
|
@@ -889,11 +856,14 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
889
856
|
});
|
|
890
857
|
}
|
|
891
858
|
|
|
859
|
+
// Generate MAAS authentication credentials
|
|
892
860
|
const authCredentials =
|
|
893
861
|
options.commission || options.cloudInit || options.cloudInitUpdate
|
|
894
862
|
? Underpost.baremetal.maasAuthCredentialsFactory()
|
|
895
863
|
: { consumer_key: '', consumer_secret: '', token_key: '', token_secret: '' };
|
|
896
864
|
|
|
865
|
+
// Generate cloud-init configuration if needed for commissioning or cloud-init update workflows.
|
|
866
|
+
let cloudConfigSrc = '';
|
|
897
867
|
if (options.cloudInit || options.cloudInitUpdate) {
|
|
898
868
|
const { chronyc, networkInterfaceName } = workflowsConfig[workflowId];
|
|
899
869
|
const { timezone, chronyConfPath } = chronyc;
|
|
@@ -910,7 +880,7 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
910
880
|
runcmd = '/usr/local/bin/underpost-enlist.sh';
|
|
911
881
|
}
|
|
912
882
|
|
|
913
|
-
|
|
883
|
+
cloudConfigSrc = Underpost.cloudInit.configFactory(
|
|
914
884
|
{
|
|
915
885
|
controlServerIp: callbackMetaData.runnerHost.ip,
|
|
916
886
|
hostname,
|
|
@@ -926,13 +896,45 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
926
896
|
write_files,
|
|
927
897
|
},
|
|
928
898
|
authCredentials,
|
|
929
|
-
);
|
|
899
|
+
).cloudConfigSrc;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Rocky/RHEL Kickstart generation
|
|
903
|
+
let kickstartSrc = '';
|
|
904
|
+
if (Underpost.baremetal.getFamilyBaseOs(workflowsConfig[workflowId].osIdLike).isRhelBased) {
|
|
905
|
+
kickstartSrc = Underpost.kickstart.kickstartFactory({
|
|
906
|
+
lang: 'en_US.UTF-8',
|
|
907
|
+
keyboard: workflowsConfig[workflowId].keyboard?.layout,
|
|
908
|
+
timezone: workflowsConfig[workflowId].chronyc?.timezone,
|
|
909
|
+
rootPassword: process.env.MAAS_ADMIN_PASS,
|
|
910
|
+
authorizedKeys: fs.readFileSync('/home/dd/engine/engine-private/deploy/id_rsa.pub', 'utf8').trim(),
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Build and optionally run the HTTP bootstrap server to serve cloud-init, kickstart, and ISO resources for commissioning and provisioning.
|
|
915
|
+
if (cloudConfigSrc || kickstartSrc || workflowsConfig[workflowId].isoUrl)
|
|
930
916
|
Underpost.baremetal.httpBootstrapServerStaticFactory({
|
|
931
917
|
bootstrapHttpServerPath,
|
|
932
918
|
hostname,
|
|
933
919
|
cloudConfigSrc,
|
|
920
|
+
kickstartSrc,
|
|
934
921
|
isoUrl: workflowsConfig[workflowId].isoUrl,
|
|
935
922
|
});
|
|
923
|
+
|
|
924
|
+
// Set up iptables rules for NAT and port forwarding to enable network connectivity for the baremetal machines.
|
|
925
|
+
shellExec(`${underpostRoot}/scripts/nat-iptables.sh`, { silent: true });
|
|
926
|
+
|
|
927
|
+
// Start HTTP bootstrap server if commissioning or if ISO URL is used (for ISO-based workflows).
|
|
928
|
+
if (options.bootstrapHttpServerRun || options.commission) {
|
|
929
|
+
Underpost.baremetal.httpBootstrapServerRunnerFactory({
|
|
930
|
+
hostname,
|
|
931
|
+
bootstrapHttpServerPath,
|
|
932
|
+
bootstrapHttpServerPort: Underpost.baremetal.bootstrapHttpServerPortFactory({
|
|
933
|
+
port: options.bootstrapHttpServerPort,
|
|
934
|
+
workflowId,
|
|
935
|
+
workflowsConfig,
|
|
936
|
+
}),
|
|
937
|
+
});
|
|
936
938
|
}
|
|
937
939
|
|
|
938
940
|
// Rebuild NFS server configuration.
|
|
@@ -941,12 +943,12 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
941
943
|
(workflowsConfig[workflowId].type === 'iso-nfs' ||
|
|
942
944
|
workflowsConfig[workflowId].type === 'chroot-debootstrap' ||
|
|
943
945
|
workflowsConfig[workflowId].type === 'chroot-container')
|
|
944
|
-
)
|
|
945
|
-
shellExec(`${underpostRoot}/scripts/nat-iptables.sh`);
|
|
946
|
+
)
|
|
946
947
|
Underpost.baremetal.rebuildNfsServer({
|
|
947
948
|
nfsHostPath,
|
|
949
|
+
nfsReset: options.nfsReset,
|
|
948
950
|
});
|
|
949
|
-
|
|
951
|
+
|
|
950
952
|
// Handle commissioning tasks
|
|
951
953
|
if (options.commission === true) {
|
|
952
954
|
let { firmwares, networkInterfaceName, maas, menuentryStr, type } = workflowsConfig[workflowId];
|
|
@@ -961,9 +963,11 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
961
963
|
);
|
|
962
964
|
logger.info('Commissioning resource', resource);
|
|
963
965
|
|
|
964
|
-
if (
|
|
966
|
+
if (
|
|
967
|
+
Underpost.baremetal.getFamilyBaseOs(workflowsConfig[workflowId].osIdLike).isDebianBased &&
|
|
968
|
+
(type === 'iso-nfs' || type === 'chroot-debootstrap' || type === 'chroot-container')
|
|
969
|
+
) {
|
|
965
970
|
// Prepare NFS casper path if using NFS boot.
|
|
966
|
-
shellExec(`sudo rm -rf ${nfsHostPath}`);
|
|
967
971
|
shellExec(`mkdir -p ${nfsHostPath}/casper`);
|
|
968
972
|
}
|
|
969
973
|
|
|
@@ -1038,7 +1042,11 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
1038
1042
|
networkInterfaceName,
|
|
1039
1043
|
fileSystemUrl:
|
|
1040
1044
|
type === 'iso-ram'
|
|
1041
|
-
? `http://${callbackMetaData.runnerHost.ip}:${Underpost.baremetal.bootstrapHttpServerPortFactory({
|
|
1045
|
+
? `http://${callbackMetaData.runnerHost.ip}:${Underpost.baremetal.bootstrapHttpServerPortFactory({
|
|
1046
|
+
port: options.bootstrapHttpServerPort,
|
|
1047
|
+
workflowId,
|
|
1048
|
+
workflowsConfig,
|
|
1049
|
+
})}/${hostname}/${kernelFilesPaths.isoUrl.split('/').pop()}`
|
|
1042
1050
|
: kernelFilesPaths.isoUrl,
|
|
1043
1051
|
bootstrapHttpServerPort: Underpost.baremetal.bootstrapHttpServerPortFactory({
|
|
1044
1052
|
port: options.bootstrapHttpServerPort,
|
|
@@ -1048,10 +1056,10 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
1048
1056
|
type,
|
|
1049
1057
|
macAddress,
|
|
1050
1058
|
cloudInit: options.cloudInit,
|
|
1051
|
-
machine,
|
|
1052
1059
|
dev: options.dev,
|
|
1053
1060
|
osIdLike: workflowsConfig[workflowId].osIdLike || '',
|
|
1054
1061
|
authCredentials,
|
|
1062
|
+
architecture: workflowsConfig[workflowId].architecture,
|
|
1055
1063
|
});
|
|
1056
1064
|
|
|
1057
1065
|
// Check if iPXE mode is enabled AND the iPXE EFI binary exists
|
|
@@ -1122,16 +1130,6 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
1122
1130
|
shellExec(`sudo chown -R $(whoami):$(whoami) ${process.env.TFTP_ROOT}`);
|
|
1123
1131
|
shellExec(`sudo sudo chmod 755 ${process.env.TFTP_ROOT}`);
|
|
1124
1132
|
|
|
1125
|
-
Underpost.baremetal.httpBootstrapServerRunnerFactory({
|
|
1126
|
-
hostname,
|
|
1127
|
-
bootstrapHttpServerPath,
|
|
1128
|
-
bootstrapHttpServerPort: Underpost.baremetal.bootstrapHttpServerPortFactory({
|
|
1129
|
-
port: options.bootstrapHttpServerPort,
|
|
1130
|
-
workflowId,
|
|
1131
|
-
workflowsConfig,
|
|
1132
|
-
}),
|
|
1133
|
-
});
|
|
1134
|
-
|
|
1135
1133
|
if (type === 'chroot-debootstrap' || type === 'chroot-container')
|
|
1136
1134
|
await Underpost.baremetal.nfsMountCallback({
|
|
1137
1135
|
hostname,
|
|
@@ -1155,49 +1153,6 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
1155
1153
|
const { discovery, machine: discoveredMachine } =
|
|
1156
1154
|
await Underpost.baremetal.commissionMonitor(commissionMonitorPayload);
|
|
1157
1155
|
if (discoveredMachine) machine = discoveredMachine;
|
|
1158
|
-
|
|
1159
|
-
if (machine) {
|
|
1160
|
-
const write_files = Underpost.baremetal.commissioningWriteFilesFactory({
|
|
1161
|
-
machine,
|
|
1162
|
-
authCredentials,
|
|
1163
|
-
runnerHostIp: callbackMetaData.runnerHost.ip,
|
|
1164
|
-
});
|
|
1165
|
-
|
|
1166
|
-
const { cloudConfigSrc } = Underpost.cloudInit.configFactory(
|
|
1167
|
-
{
|
|
1168
|
-
controlServerIp: callbackMetaData.runnerHost.ip,
|
|
1169
|
-
hostname,
|
|
1170
|
-
commissioningDeviceIp: ipAddress,
|
|
1171
|
-
gatewayip: callbackMetaData.runnerHost.ip,
|
|
1172
|
-
mac: macAddress,
|
|
1173
|
-
timezone: workflowsConfig[workflowId].chronyc.timezone,
|
|
1174
|
-
chronyConfPath: workflowsConfig[workflowId].chronyc.chronyConfPath,
|
|
1175
|
-
networkInterfaceName: workflowsConfig[workflowId].networkInterfaceName,
|
|
1176
|
-
ubuntuToolsBuild: options.ubuntuToolsBuild,
|
|
1177
|
-
bootcmd: options.bootcmd,
|
|
1178
|
-
runcmd: '/usr/local/bin/underpost-enlist.sh',
|
|
1179
|
-
write_files,
|
|
1180
|
-
},
|
|
1181
|
-
authCredentials,
|
|
1182
|
-
);
|
|
1183
|
-
|
|
1184
|
-
Underpost.baremetal.httpBootstrapServerStaticFactory({
|
|
1185
|
-
bootstrapHttpServerPath,
|
|
1186
|
-
hostname,
|
|
1187
|
-
cloudConfigSrc,
|
|
1188
|
-
isoUrl: workflowsConfig[workflowId].isoUrl,
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
if ((type === 'chroot-debootstrap' || type === 'chroot-container') && options.cloudInit === true) {
|
|
1193
|
-
openTerminal(`node ${underpostRoot}/bin baremetal ${workflowId} ${ipAddress} ${hostname} --logs cloud-init`);
|
|
1194
|
-
openTerminal(
|
|
1195
|
-
`node ${underpostRoot}/bin baremetal ${workflowId} ${ipAddress} ${hostname} --logs cloud-init-machine`,
|
|
1196
|
-
);
|
|
1197
|
-
shellExec(
|
|
1198
|
-
`node ${underpostRoot}/bin baremetal ${workflowId} ${ipAddress} ${hostname} --logs cloud-init-config`,
|
|
1199
|
-
);
|
|
1200
|
-
}
|
|
1201
1156
|
}
|
|
1202
1157
|
},
|
|
1203
1158
|
|
|
@@ -1228,8 +1183,8 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
1228
1183
|
* @param {string} [params.dnsServer='8.8.8.8'] - The DNS server address.
|
|
1229
1184
|
* @param {string} [params.macAddress=''] - The MAC address of the client machine.
|
|
1230
1185
|
* @param {boolean} [params.cloudInit=false] - Flag to enable cloud-init.
|
|
1231
|
-
* @param {object} [params.machine=null] - The machine object containing system_id for cloud-init.
|
|
1232
1186
|
* @param {boolean} [params.dev=false] - Development mode flag to determine paths.
|
|
1187
|
+
* @param {boolean} [params.forceRebuild=false] - Force a complete iPXE rebuild. Without this, reuses existing ISO.
|
|
1233
1188
|
* @param {number} [params.bootstrapHttpServerPort=8888] - Port for the bootstrap HTTP server used in ISO RAM workflows.
|
|
1234
1189
|
* @memberof UnderpostBaremetal
|
|
1235
1190
|
* @returns {Promise<void>}
|
|
@@ -1245,12 +1200,11 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
1245
1200
|
dnsServer,
|
|
1246
1201
|
macAddress,
|
|
1247
1202
|
cloudInit,
|
|
1248
|
-
machine,
|
|
1249
1203
|
dev,
|
|
1204
|
+
forceRebuild = false,
|
|
1250
1205
|
bootstrapHttpServerPort,
|
|
1251
1206
|
}) {
|
|
1252
1207
|
const outputPath = !isoOutputPath || isoOutputPath === '.' ? `./ipxe-${workflowId}.iso` : isoOutputPath;
|
|
1253
|
-
if (fs.existsSync(outputPath)) fs.removeSync(outputPath);
|
|
1254
1208
|
shellExec(`mkdir -p $(dirname ${outputPath})`);
|
|
1255
1209
|
|
|
1256
1210
|
const workflowsConfig = Underpost.baremetal.loadWorkflowsConfig();
|
|
@@ -1272,12 +1226,16 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
1272
1226
|
dnsServer,
|
|
1273
1227
|
fileSystemUrl:
|
|
1274
1228
|
dev && workflowsConfig[workflowId].type === 'iso-ram'
|
|
1275
|
-
? `http://${ipFileServer}:${Underpost.baremetal.bootstrapHttpServerPortFactory({
|
|
1229
|
+
? `http://${ipFileServer}:${Underpost.baremetal.bootstrapHttpServerPortFactory({
|
|
1230
|
+
port: bootstrapHttpServerPort,
|
|
1231
|
+
workflowId,
|
|
1232
|
+
workflowsConfig,
|
|
1233
|
+
})}/${workflowId}/${workflowsConfig[workflowId].isoUrl.split('/').pop()}`
|
|
1276
1234
|
: workflowsConfig[workflowId].isoUrl,
|
|
1277
1235
|
type: workflowsConfig[workflowId].type,
|
|
1236
|
+
architecture: workflowsConfig[workflowId].architecture,
|
|
1278
1237
|
macAddress,
|
|
1279
1238
|
cloudInit,
|
|
1280
|
-
machine,
|
|
1281
1239
|
osIdLike: workflowsConfig[workflowId].osIdLike,
|
|
1282
1240
|
networkInterfaceName: workflowsConfig[workflowId].networkInterfaceName,
|
|
1283
1241
|
authCredentials,
|
|
@@ -1293,20 +1251,18 @@ rm -rf ${artifacts.join(' ')}`);
|
|
|
1293
1251
|
const embedScriptName = `embed_${workflowId}.ipxe`;
|
|
1294
1252
|
const embedScriptPath = path.join(ipxeSrcDir, embedScriptName);
|
|
1295
1253
|
|
|
1296
|
-
const embedScriptContent =
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
boot || shell
|
|
1303
|
-
`;
|
|
1254
|
+
const embedScriptContent = Underpost.baremetal.ipxeScriptFactory({
|
|
1255
|
+
maasIp: ipFileServer,
|
|
1256
|
+
tftpPrefix,
|
|
1257
|
+
kernelCmd: cmd,
|
|
1258
|
+
minimal: true,
|
|
1259
|
+
});
|
|
1304
1260
|
|
|
1305
1261
|
fs.writeFileSync(embedScriptPath, embedScriptContent);
|
|
1306
1262
|
logger.info(`Created embedded script at ${embedScriptPath}`);
|
|
1307
1263
|
|
|
1308
1264
|
// Determine target architecture
|
|
1309
|
-
let targetArch = 'x86_64';
|
|
1265
|
+
let targetArch = 'x86_64';
|
|
1310
1266
|
if (
|
|
1311
1267
|
workflowsConfig[workflowId].architecture === 'arm64' ||
|
|
1312
1268
|
workflowsConfig[workflowId].architecture === 'aarch64'
|
|
@@ -1314,32 +1270,38 @@ boot || shell
|
|
|
1314
1270
|
targetArch = 'arm64';
|
|
1315
1271
|
}
|
|
1316
1272
|
|
|
1317
|
-
// Determine host architecture
|
|
1318
|
-
const hostArch = process.arch === 'arm64' ? 'arm64' : 'x86_64';
|
|
1319
|
-
|
|
1320
|
-
let crossCompile = '';
|
|
1321
|
-
if (hostArch === 'x86_64' && targetArch === 'arm64') {
|
|
1322
|
-
crossCompile = 'CROSS_COMPILE=aarch64-linux-gnu-';
|
|
1323
|
-
} else if (hostArch === 'arm64' && targetArch === 'x86_64') {
|
|
1324
|
-
crossCompile = 'CROSS_COMPILE=x86_64-linux-gnu-';
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
1273
|
const platformDir = targetArch === 'arm64' ? 'bin-arm64-efi' : 'bin-x86_64-efi';
|
|
1328
1274
|
const makeTarget = `${platformDir}/ipxe.iso`;
|
|
1329
|
-
|
|
1330
|
-
logger.info(
|
|
1331
|
-
`Building iPXE ISO for ${targetArch} on ${hostArch}: make ${makeTarget} ${crossCompile} EMBED=${embedScriptName}`,
|
|
1332
|
-
);
|
|
1333
|
-
|
|
1334
|
-
const buildCmd = `cd ${ipxeSrcDir} && make ${makeTarget} ${crossCompile} EMBED=${embedScriptName}`;
|
|
1335
|
-
shellExec(buildCmd);
|
|
1336
|
-
|
|
1337
1275
|
const builtIsoPath = path.join(ipxeSrcDir, makeTarget);
|
|
1338
|
-
|
|
1276
|
+
|
|
1277
|
+
if (!forceRebuild && fs.existsSync(builtIsoPath)) {
|
|
1339
1278
|
fs.copySync(builtIsoPath, outputPath);
|
|
1340
|
-
logger.info(`
|
|
1279
|
+
logger.info(`Reusing existing iPXE ISO: ${builtIsoPath} -> ${outputPath}`);
|
|
1341
1280
|
} else {
|
|
1342
|
-
logger.
|
|
1281
|
+
logger.info(`Rebuild: cleaning iPXE build artifacts...`);
|
|
1282
|
+
shellExec(`cd ${ipxeSrcDir} && make clean`);
|
|
1283
|
+
|
|
1284
|
+
const hostArch = process.arch === 'arm64' ? 'arm64' : 'x86_64';
|
|
1285
|
+
let crossCompile = '';
|
|
1286
|
+
if (hostArch === 'x86_64' && targetArch === 'arm64') {
|
|
1287
|
+
crossCompile = 'CROSS_COMPILE=aarch64-linux-gnu-';
|
|
1288
|
+
} else if (hostArch === 'arm64' && targetArch === 'x86_64') {
|
|
1289
|
+
crossCompile = 'CROSS_COMPILE=x86_64-linux-gnu-';
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
logger.info(
|
|
1293
|
+
`Building iPXE ISO for ${targetArch} on ${hostArch}: make ${makeTarget} ${crossCompile} EMBED=${embedScriptName}`,
|
|
1294
|
+
);
|
|
1295
|
+
|
|
1296
|
+
const buildCmd = `cd ${ipxeSrcDir} && make ${makeTarget} ${crossCompile} EMBED=${embedScriptName}`;
|
|
1297
|
+
shellExec(buildCmd);
|
|
1298
|
+
|
|
1299
|
+
if (fs.existsSync(builtIsoPath)) {
|
|
1300
|
+
fs.copySync(builtIsoPath, outputPath);
|
|
1301
|
+
logger.info(`ISO successfully built and copied to ${outputPath}`);
|
|
1302
|
+
} else {
|
|
1303
|
+
logger.error(`Failed to build ISO at ${builtIsoPath}`);
|
|
1304
|
+
}
|
|
1343
1305
|
}
|
|
1344
1306
|
},
|
|
1345
1307
|
|
|
@@ -1410,8 +1372,7 @@ boot || shell
|
|
|
1410
1372
|
const isoFilename = isoUrl.split('/').pop();
|
|
1411
1373
|
|
|
1412
1374
|
// Determine OS family from osIdLike
|
|
1413
|
-
const isDebianBased
|
|
1414
|
-
const isRhelBased = osIdLike && osIdLike.match(/rhel|centos|fedora|alma|rocky/i);
|
|
1375
|
+
const { isDebianBased, isRhelBased } = Underpost.baremetal.getFamilyBaseOs(osIdLike);
|
|
1415
1376
|
|
|
1416
1377
|
// Set extraction directory based on OS family
|
|
1417
1378
|
const extractDirName = isDebianBased ? 'casper' : 'iso-extract';
|
|
@@ -1437,7 +1398,7 @@ boot || shell
|
|
|
1437
1398
|
logger.info(`Downloaded ISO to ${isoPath} (${(stats.size / 1024 / 1024 / 1024).toFixed(2)} GB)`);
|
|
1438
1399
|
}
|
|
1439
1400
|
|
|
1440
|
-
//
|
|
1401
|
+
// ISO Logic
|
|
1441
1402
|
const mountPoint = `${nfsHostPath}/mnt-iso-${arch}`;
|
|
1442
1403
|
shellExec(`mkdir -p ${mountPoint}`);
|
|
1443
1404
|
|
|
@@ -1446,7 +1407,7 @@ boot || shell
|
|
|
1446
1407
|
|
|
1447
1408
|
try {
|
|
1448
1409
|
// Mount the ISO
|
|
1449
|
-
shellExec(`sudo mount -o loop,ro ${isoPath} ${mountPoint}
|
|
1410
|
+
shellExec(`sudo mount -o loop,ro ${isoPath} ${mountPoint}`);
|
|
1450
1411
|
logger.info(`Mounted ISO at ${mountPoint}`);
|
|
1451
1412
|
|
|
1452
1413
|
// Distribution-specific extraction logic
|
|
@@ -1458,6 +1419,15 @@ boot || shell
|
|
|
1458
1419
|
logger.info(`Checking casper directory contents...`);
|
|
1459
1420
|
shellExec(`ls -la ${mountPoint}/casper/ 2>/dev/null || echo "casper directory not found"`);
|
|
1460
1421
|
shellExec(`sudo cp -a ${mountPoint}/casper/* ${extractDir}/`);
|
|
1422
|
+
} else if (isRhelBased) {
|
|
1423
|
+
// RHEL/Rocky: Extract from images/pxeboot directory
|
|
1424
|
+
const pxebootDir = `${mountPoint}/images/pxeboot`;
|
|
1425
|
+
if (!fs.existsSync(pxebootDir)) {
|
|
1426
|
+
throw new Error(`Failed to mount ISO or images/pxeboot directory not found: ${isoPath}`);
|
|
1427
|
+
}
|
|
1428
|
+
logger.info(`Extracting kernel and initrd from ${pxebootDir}...`);
|
|
1429
|
+
shellExec(`sudo cp -a ${pxebootDir}/vmlinuz ${extractDir}/vmlinuz`);
|
|
1430
|
+
shellExec(`sudo cp -a ${pxebootDir}/initrd.img ${extractDir}/initrd`);
|
|
1461
1431
|
}
|
|
1462
1432
|
} finally {
|
|
1463
1433
|
shellExec(`ls -la ${mountPoint}/`);
|
|
@@ -1522,6 +1492,20 @@ boot || shell
|
|
|
1522
1492
|
}
|
|
1523
1493
|
},
|
|
1524
1494
|
|
|
1495
|
+
/**
|
|
1496
|
+
* @method getFamilyBaseOs
|
|
1497
|
+
* @description Determines if the OS belongs to Debian-based or RHEL-based family based on osIdLike string.
|
|
1498
|
+
* @param {string} osIdLike - The os_id_like string from MAAS boot resource or workflow configuration.
|
|
1499
|
+
* @returns {object} An object with boolean properties isDebianBased and isRhelBased indicating the OS family.
|
|
1500
|
+
* @memberof UnderpostBaremetal
|
|
1501
|
+
*/
|
|
1502
|
+
getFamilyBaseOs(osIdLike = '') {
|
|
1503
|
+
return {
|
|
1504
|
+
isDebianBased: osIdLike.match(/debian|ubuntu/i),
|
|
1505
|
+
isRhelBased: osIdLike.match(/rhel|centos|fedora|alma|rocky/i),
|
|
1506
|
+
};
|
|
1507
|
+
},
|
|
1508
|
+
|
|
1525
1509
|
/**
|
|
1526
1510
|
* @method kernelFactory
|
|
1527
1511
|
* @description Retrieves kernel, initrd, and root filesystem paths from a MAAS boot resource.
|
|
@@ -1538,8 +1522,10 @@ boot || shell
|
|
|
1538
1522
|
// For disk-based commissioning (casper/iso), use live ISO files
|
|
1539
1523
|
if (type === 'iso-ram' || type === 'iso-nfs') {
|
|
1540
1524
|
logger.info('Using live ISO for boot (disk-based commissioning)');
|
|
1541
|
-
const arch = resource.architecture.split('/')[0];
|
|
1542
1525
|
const workflowsConfig = Underpost.baremetal.loadWorkflowsConfig();
|
|
1526
|
+
const arch = resource?.architecture
|
|
1527
|
+
? resource.architecture.split('/')[0]
|
|
1528
|
+
: workflowsConfig[workflowId].architecture;
|
|
1543
1529
|
const kernelFilesPaths = Underpost.baremetal.downloadISO({
|
|
1544
1530
|
resource,
|
|
1545
1531
|
architecture: arch,
|
|
@@ -1877,16 +1863,28 @@ shell
|
|
|
1877
1863
|
* @method ipxeScriptFactory
|
|
1878
1864
|
* @description Generates the iPXE script content for stable identity.
|
|
1879
1865
|
* This iPXE script uses directly boots kernel/initrd via TFTP.
|
|
1866
|
+
* When minimal mode is enabled, generates a simple dhcp+kernel+initrd+boot script.
|
|
1880
1867
|
* @param {object} params - The parameters for generating the script.
|
|
1881
|
-
* @param {string} params.maasIp - The IP address of the MAAS server.
|
|
1868
|
+
* @param {string} params.maasIp - The IP address of the MAAS/file server.
|
|
1882
1869
|
* @param {string} [params.macAddress] - The MAC address registered in MAAS (for display only).
|
|
1883
|
-
* @param {string} params.architecture - The architecture (arm64/amd64).
|
|
1870
|
+
* @param {string} [params.architecture] - The architecture (arm64/amd64).
|
|
1884
1871
|
* @param {string} params.tftpPrefix - The TFTP prefix path (e.g., 'rpi4mb').
|
|
1885
1872
|
* @param {string} params.kernelCmd - The kernel command line parameters.
|
|
1873
|
+
* @param {boolean} [params.minimal=false] - Generate a minimal embedded script for ISO builds.
|
|
1886
1874
|
* @returns {string} The iPXE script content.
|
|
1887
1875
|
* @memberof UnderpostBaremetal
|
|
1888
1876
|
*/
|
|
1889
|
-
ipxeScriptFactory({ maasIp, macAddress, architecture, tftpPrefix, kernelCmd }) {
|
|
1877
|
+
ipxeScriptFactory({ maasIp, macAddress, architecture, tftpPrefix, kernelCmd, minimal = false }) {
|
|
1878
|
+
if (minimal) {
|
|
1879
|
+
return `#!ipxe
|
|
1880
|
+
dhcp
|
|
1881
|
+
set server_ip ${maasIp}
|
|
1882
|
+
set tftp_prefix ${tftpPrefix}
|
|
1883
|
+
kernel tftp://\${server_ip}/\${tftp_prefix}/pxe/vmlinuz-efi ${kernelCmd}
|
|
1884
|
+
initrd tftp://\${server_ip}/\${tftp_prefix}/pxe/initrd.img
|
|
1885
|
+
boot || shell
|
|
1886
|
+
`;
|
|
1887
|
+
}
|
|
1890
1888
|
const macInfo =
|
|
1891
1889
|
macAddress && macAddress !== '00:00:00:00:00:00'
|
|
1892
1890
|
? `echo Registered MAC: ${macAddress}`
|
|
@@ -1924,7 +1922,11 @@ echo ========================================
|
|
|
1924
1922
|
echo Loading kernel and initrd via TFTP...
|
|
1925
1923
|
echo Kernel: tftp://${maasIp}/${kernelPath}
|
|
1926
1924
|
echo Initrd: tftp://${maasIp}/${initrdPath}
|
|
1927
|
-
${
|
|
1925
|
+
${
|
|
1926
|
+
macAddress && macAddress !== '00:00:00:00:00:00'
|
|
1927
|
+
? `echo Kernel will use MAC: ${macAddress} (via ifname parameter)`
|
|
1928
|
+
: 'echo Kernel will use hardware MAC'
|
|
1929
|
+
}
|
|
1928
1930
|
echo ========================================
|
|
1929
1931
|
|
|
1930
1932
|
# Load kernel via TFTP
|
|
@@ -1955,7 +1957,9 @@ echo ========================================
|
|
|
1955
1957
|
# Fallback: Try MAAS HTTP bootloader (may have certificate issues)
|
|
1956
1958
|
set boot-url http://${maasIp}:5248/images/bootloader
|
|
1957
1959
|
echo Boot URL: \${boot-url}
|
|
1958
|
-
chain \${boot-url}/uefi/${architecture}/${
|
|
1960
|
+
chain \${boot-url}/uefi/${architecture}/${
|
|
1961
|
+
grubBootloader === 'grubaa64.efi' ? 'bootaa64.efi' : 'bootx64.efi'
|
|
1962
|
+
} || goto error
|
|
1959
1963
|
|
|
1960
1964
|
:error
|
|
1961
1965
|
echo ========================================
|
|
@@ -1991,22 +1995,12 @@ shell
|
|
|
1991
1995
|
* @memberof UnderpostBaremetal
|
|
1992
1996
|
*/
|
|
1993
1997
|
ipxeEfiFactory({ tftpRootPath, ipxeCacheDir, arch, underpostRoot, embeddedScriptPath, forceRebuild = false }) {
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1998
|
+
const embedArg =
|
|
1999
|
+
embeddedScriptPath && fs.existsSync(embeddedScriptPath) ? ` --embed-script ${embeddedScriptPath}` : '';
|
|
2000
|
+
const rebuildArg = forceRebuild ? ' --rebuild' : '';
|
|
1996
2001
|
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
if (embeddedScriptPath && fs.existsSync(embeddedScriptPath)) {
|
|
2000
|
-
logger.info('Rebuilding iPXE with embedded boot script...', {
|
|
2001
|
-
embeddedScriptPath,
|
|
2002
|
-
forced: forceRebuild,
|
|
2003
|
-
});
|
|
2004
|
-
shellExec(
|
|
2005
|
-
`${underpostRoot}/scripts/ipxe-setup.sh ${tftpRootPath} --target-arch ${arch} --embed-script ${embeddedScriptPath} --rebuild`,
|
|
2006
|
-
);
|
|
2007
|
-
} else if (shouldRebuild) {
|
|
2008
|
-
shellExec(`${underpostRoot}/scripts/ipxe-setup.sh ${tftpRootPath} --target-arch ${arch}`);
|
|
2009
|
-
}
|
|
2002
|
+
logger.info('Building iPXE EFI binary...', { embeddedScriptPath, forced: forceRebuild });
|
|
2003
|
+
shellExec(`${underpostRoot}/scripts/ipxe-setup.sh ${tftpRootPath} --target-arch ${arch}${embedArg}${rebuildArg}`);
|
|
2010
2004
|
|
|
2011
2005
|
shellExec(`mkdir -p ${ipxeCacheDir}`);
|
|
2012
2006
|
shellExec(`cp ${tftpRootPath}/ipxe.efi ${ipxeCacheDir}/ipxe.efi`);
|
|
@@ -2060,7 +2054,11 @@ shell
|
|
|
2060
2054
|
echo "${menuentryStr}"
|
|
2061
2055
|
echo " ${Underpost.version}"
|
|
2062
2056
|
echo "Date: ${new Date().toISOString()}"
|
|
2063
|
-
${
|
|
2057
|
+
${
|
|
2058
|
+
cmd.match('/MAAS/metadata/by-id/')
|
|
2059
|
+
? `echo "System ID: ${cmd.split('/MAAS/metadata/by-id/')[1].split('/')[0]}"`
|
|
2060
|
+
: ''
|
|
2061
|
+
}
|
|
2064
2062
|
echo "TFTP server: ${tftpIp}"
|
|
2065
2063
|
echo "Kernel path: ${kernelPath}"
|
|
2066
2064
|
echo "Initrd path: ${initrdPath}"
|
|
@@ -2132,8 +2130,7 @@ echo "Response Body:" | tee -a "$LOG_FILE"
|
|
|
2132
2130
|
cat "$RESPONSE_FILE" | tee -a "$LOG_FILE"
|
|
2133
2131
|
|
|
2134
2132
|
if [ "$HTTP_STATUS" -eq 200 ]; then
|
|
2135
|
-
echo "Commissioning requested successfully.
|
|
2136
|
-
reboot
|
|
2133
|
+
echo "Commissioning requested successfully." | tee -a "$LOG_FILE"
|
|
2137
2134
|
else
|
|
2138
2135
|
echo "ERROR: MAAS commissioning failed with status $HTTP_STATUS" | tee -a "$LOG_FILE"
|
|
2139
2136
|
exit 0
|
|
@@ -2150,34 +2147,24 @@ fi
|
|
|
2150
2147
|
* @param {string} params.bootstrapHttpServerPath - The path where static files will be created.
|
|
2151
2148
|
* @param {string} params.hostname - The hostname of the client machine.
|
|
2152
2149
|
* @param {string} params.cloudConfigSrc - The cloud-init configuration YAML source.
|
|
2153
|
-
* @param {
|
|
2154
|
-
* @param {string}
|
|
2155
|
-
* @param {string}
|
|
2150
|
+
* @param {string} params.kickstartSrc - The kickstart configuration content.
|
|
2151
|
+
* @param {string} params.vendorData - The cloud-init vendor-data content.
|
|
2152
|
+
* @param {string} params.isoUrl - Optional ISO URL to cache and serve.
|
|
2156
2153
|
* @memberof UnderpostBaremetal
|
|
2157
2154
|
* @returns {void}
|
|
2158
2155
|
*/
|
|
2159
2156
|
httpBootstrapServerStaticFactory({
|
|
2160
|
-
bootstrapHttpServerPath,
|
|
2161
|
-
hostname,
|
|
2162
|
-
cloudConfigSrc,
|
|
2163
|
-
|
|
2157
|
+
bootstrapHttpServerPath = '',
|
|
2158
|
+
hostname = '',
|
|
2159
|
+
cloudConfigSrc = '',
|
|
2160
|
+
kickstartSrc = '',
|
|
2164
2161
|
vendorData = '',
|
|
2165
2162
|
isoUrl = '',
|
|
2166
2163
|
}) {
|
|
2167
|
-
|
|
2168
|
-
shellExec(`mkdir -p ${bootstrapHttpServerPath}/${hostname}/cloud-init`);
|
|
2169
|
-
|
|
2170
|
-
// Write user-data file
|
|
2171
|
-
fs.writeFileSync(`${bootstrapHttpServerPath}/${hostname}/cloud-init/user-data`, cloudConfigSrc, 'utf8');
|
|
2172
|
-
|
|
2173
|
-
// Write meta-data file
|
|
2174
|
-
const metaDataContent = `instance-id: ${metadata.instanceId || hostname}\nlocal-hostname: ${metadata.localHostname || hostname}`;
|
|
2175
|
-
fs.writeFileSync(`${bootstrapHttpServerPath}/${hostname}/cloud-init/meta-data`, metaDataContent, 'utf8');
|
|
2176
|
-
|
|
2177
|
-
// Write vendor-data file
|
|
2178
|
-
fs.writeFileSync(`${bootstrapHttpServerPath}/${hostname}/cloud-init/vendor-data`, vendorData, 'utf8');
|
|
2164
|
+
shellExec(`mkdir -p ${bootstrapHttpServerPath}/${hostname}`);
|
|
2179
2165
|
|
|
2180
|
-
|
|
2166
|
+
Underpost.cloudInit.httpServerStaticFactory({ bootstrapHttpServerPath, hostname, cloudConfigSrc, vendorData });
|
|
2167
|
+
Underpost.kickstart.httpServerStaticFactory({ bootstrapHttpServerPath, hostname, kickstartSrc });
|
|
2181
2168
|
|
|
2182
2169
|
if (isoUrl) {
|
|
2183
2170
|
const isoFilename = isoUrl.split('/').pop();
|
|
@@ -2213,8 +2200,7 @@ fi
|
|
|
2213
2200
|
const bootstrapHttpServerPath = options.bootstrapHttpServerPath || './public/localhost';
|
|
2214
2201
|
const hostname = options.hostname || 'localhost';
|
|
2215
2202
|
|
|
2216
|
-
shellExec(`
|
|
2217
|
-
shellExec(`node bin run kill ${port}`);
|
|
2203
|
+
shellExec(`node bin run kill ${port}`, { silent: true });
|
|
2218
2204
|
|
|
2219
2205
|
const app = express();
|
|
2220
2206
|
|
|
@@ -2253,9 +2239,10 @@ fi
|
|
|
2253
2239
|
*/
|
|
2254
2240
|
updateKernelFiles({ commissioningImage, resourcesPath, tftpRootPath, kernelFilesPaths }) {
|
|
2255
2241
|
// Copy EFI bootloaders to TFTP path.
|
|
2256
|
-
const
|
|
2257
|
-
|
|
2258
|
-
: ['bootx64.efi', 'grubx64.efi'];
|
|
2242
|
+
const arch = resourcesPath.split('/').pop();
|
|
2243
|
+
const efiFiles =
|
|
2244
|
+
arch === 'arm64' || arch === 'aarch64' ? ['bootaa64.efi', 'grubaa64.efi'] : ['bootx64.efi', 'grubx64.efi'];
|
|
2245
|
+
|
|
2259
2246
|
for (const file of efiFiles) {
|
|
2260
2247
|
shellExec(`sudo cp -a ${resourcesPath}/${file} ${tftpRootPath}/pxe/${file}`);
|
|
2261
2248
|
}
|
|
@@ -2305,7 +2292,6 @@ fi
|
|
|
2305
2292
|
* @param {string} options.macAddress - The MAC address of the client.
|
|
2306
2293
|
* @param {boolean} options.cloudInit - Whether to include cloud-init parameters.
|
|
2307
2294
|
* @param {object} options.machine - The machine object containing system_id.
|
|
2308
|
-
* @param {string} options.machine.system_id - The system ID of the machine (for MAAS metadata).
|
|
2309
2295
|
* @param {boolean} [options.dev=false] - Whether to enable dev mode with dracut debugging parameters.
|
|
2310
2296
|
* @param {string} [options.osIdLike=''] - OS family identifier (e.g., 'rhel centos fedora' or 'debian ubuntu').
|
|
2311
2297
|
* @param {object} options.authCredentials - Authentication credentials for fetching files (if needed).
|
|
@@ -2313,6 +2299,7 @@ fi
|
|
|
2313
2299
|
* @param {string} options.authCredentials.consumer_secret - Consumer secret for authentication.
|
|
2314
2300
|
* @param {string} options.authCredentials.token_key - Token key for authentication.
|
|
2315
2301
|
* @param {string} options.authCredentials.token_secret - Token secret for authentication.
|
|
2302
|
+
* @param {string} options.architecture - The architecture of the machine (e.g., 'amd64', 'arm64').
|
|
2316
2303
|
* @returns {object} An object containing the constructed command line string.
|
|
2317
2304
|
* @memberof UnderpostBaremetal
|
|
2318
2305
|
*/
|
|
@@ -2331,10 +2318,10 @@ fi
|
|
|
2331
2318
|
type: '',
|
|
2332
2319
|
macAddress: '',
|
|
2333
2320
|
cloudInit: false,
|
|
2334
|
-
machine: { system_id: '' },
|
|
2335
2321
|
dev: false,
|
|
2336
2322
|
osIdLike: '',
|
|
2337
2323
|
authCredentials: { consumer_key: '', consumer_secret: '', token_key: '', token_secret: '' },
|
|
2324
|
+
architecture,
|
|
2338
2325
|
},
|
|
2339
2326
|
) {
|
|
2340
2327
|
// Construct kernel command line arguments for NFS boot.
|
|
@@ -2353,8 +2340,12 @@ fi
|
|
|
2353
2340
|
macAddress,
|
|
2354
2341
|
cloudInit,
|
|
2355
2342
|
osIdLike,
|
|
2343
|
+
architecture,
|
|
2356
2344
|
} = options;
|
|
2357
2345
|
|
|
2346
|
+
// Determine OS family from osIdLike
|
|
2347
|
+
const { isDebianBased, isRhelBased } = Underpost.baremetal.getFamilyBaseOs(options.osIdLike);
|
|
2348
|
+
|
|
2358
2349
|
const ipParam =
|
|
2359
2350
|
`ip=${ipClient}:${ipFileServer}:${ipDhcpServer}:${netmask}:${hostname}` +
|
|
2360
2351
|
`:${networkInterfaceName ? networkInterfaceName : 'eth0'}:${ipConfig}:${dnsServer}`;
|
|
@@ -2384,7 +2375,9 @@ fi
|
|
|
2384
2375
|
: []
|
|
2385
2376
|
}`;
|
|
2386
2377
|
|
|
2387
|
-
const nfsRootParam = `nfsroot=${ipFileServer}:${process.env.NFS_EXPORT_PATH}/${hostname}${
|
|
2378
|
+
const nfsRootParam = `nfsroot=${ipFileServer}:${process.env.NFS_EXPORT_PATH}/${hostname}${
|
|
2379
|
+
nfsOptions ? `,${nfsOptions}` : ''
|
|
2380
|
+
}`;
|
|
2388
2381
|
|
|
2389
2382
|
const permissionsParams = [
|
|
2390
2383
|
`rw`,
|
|
@@ -2402,8 +2395,8 @@ fi
|
|
|
2402
2395
|
// `layerfs-path=filesystem.squashfs`,
|
|
2403
2396
|
// `root=/dev/ram0`,
|
|
2404
2397
|
// `toram`,
|
|
2405
|
-
'nomodeset',
|
|
2406
|
-
`editable_rootfs=tmpfs`,
|
|
2398
|
+
// 'nomodeset',
|
|
2399
|
+
// `editable_rootfs=tmpfs`, // all writes to rootfs go to RAM, keeping underlying storage pristine
|
|
2407
2400
|
// `ramdisk_size=3550000`,
|
|
2408
2401
|
// `root=/dev/sda1`, // rpi4 usb port unit
|
|
2409
2402
|
'apparmor=0', // Disable AppArmor security
|
|
@@ -2446,22 +2439,22 @@ fi
|
|
|
2446
2439
|
|
|
2447
2440
|
let cmd = [];
|
|
2448
2441
|
if (type === 'iso-ram') {
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2442
|
+
if (isRhelBased) {
|
|
2443
|
+
cmd = Underpost.kickstart.kernelParamsFactory(macAddress, [ipParam, ...kernelParams], options);
|
|
2444
|
+
} else {
|
|
2445
|
+
// ISO-RAM (Debian/Ubuntu): full live ISO downloaded into RAM via casper toram.
|
|
2446
|
+
const netBootParams = [`netboot=url`];
|
|
2447
|
+
if (fileSystemUrl) netBootParams.push(`url=${fileSystemUrl.replace('https', 'http')}`);
|
|
2448
|
+
cmd = [ipParam, `boot=casper`, 'toram', ...netBootParams, ...kernelParams, ...performanceParams];
|
|
2449
|
+
}
|
|
2452
2450
|
} else if (type === 'chroot-debootstrap' || type === 'chroot-container') {
|
|
2453
2451
|
let qemuNfsRootParams = [`root=/dev/nfs`, `rootfstype=nfs`];
|
|
2454
2452
|
cmd = [ipParam, ...qemuNfsRootParams, nfsRootParam, ...kernelParams];
|
|
2455
2453
|
} else {
|
|
2456
|
-
// 'iso-nfs'
|
|
2454
|
+
// 'iso-nfs' — Debian/Ubuntu NFS root boot: kernel/initrd from ISO, root filesystem served via NFS.
|
|
2457
2455
|
cmd = [ipParam, `netboot=nfs`, nfsRootParam, ...kernelParams, ...performanceParams];
|
|
2458
|
-
// cmd.push(`ifname=${networkInterfaceName}:${macAddress}`);
|
|
2459
2456
|
}
|
|
2460
2457
|
|
|
2461
|
-
// Determine OS family from osIdLike configuration
|
|
2462
|
-
const isRhelBased = osIdLike && osIdLike.match(/rhel|centos|fedora|alma|rocky/i);
|
|
2463
|
-
const isDebianBased = osIdLike && osIdLike.match(/debian|ubuntu/i);
|
|
2464
|
-
|
|
2465
2458
|
// Add RHEL/Rocky/Fedora based images specific parameters
|
|
2466
2459
|
if (isRhelBased) {
|
|
2467
2460
|
cmd = cmd.concat([`rd.neednet=1`, `rd.timeout=180`, `selinux=0`, `enforcing=0`]);
|
|
@@ -2968,320 +2961,6 @@ EOF`);
|
|
|
2968
2961
|
throw new Error(`Unsupported host architecture: ${machine}`);
|
|
2969
2962
|
},
|
|
2970
2963
|
|
|
2971
|
-
/**
|
|
2972
|
-
* @property {object} systemProvisioningFactory
|
|
2973
|
-
* @description A factory object containing functions for system provisioning based on OS type.
|
|
2974
|
-
* Each OS type (e.g., 'ubuntu') provides methods for base system setup, user creation,
|
|
2975
|
-
* timezone configuration, and keyboard layout settings. *
|
|
2976
|
-
* @memberof UnderpostBaremetal
|
|
2977
|
-
* @namespace UnderpostBaremetal.systemProvisioningFactory
|
|
2978
|
-
*/
|
|
2979
|
-
systemProvisioningFactory: {
|
|
2980
|
-
/**
|
|
2981
|
-
* @property {object} ubuntu
|
|
2982
|
-
* @description Provisioning steps for Ubuntu-based systems.
|
|
2983
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory
|
|
2984
|
-
* @namespace UnderpostBaremetal.systemProvisioningFactory.ubuntu
|
|
2985
|
-
*/
|
|
2986
|
-
ubuntu: {
|
|
2987
|
-
/**
|
|
2988
|
-
* @method base
|
|
2989
|
-
* @description Generates shell commands for basic Ubuntu system provisioning.
|
|
2990
|
-
* This includes updating package lists, installing essential build tools,
|
|
2991
|
-
* kernel modules, cloud-init, SSH server, and other core utilities.
|
|
2992
|
-
* @param {object} params - The parameters for the function.
|
|
2993
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory.ubuntu
|
|
2994
|
-
* @returns {string[]} An array of shell commands.
|
|
2995
|
-
*/
|
|
2996
|
-
base: () => [
|
|
2997
|
-
// Configure APT sources for Ubuntu ports
|
|
2998
|
-
`cat <<SOURCES | tee /etc/apt/sources.list
|
|
2999
|
-
deb http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse
|
|
3000
|
-
deb http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse
|
|
3001
|
-
deb http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse
|
|
3002
|
-
SOURCES`,
|
|
3003
|
-
|
|
3004
|
-
// Update package lists and perform a full system upgrade
|
|
3005
|
-
`apt update -qq`,
|
|
3006
|
-
`apt -y full-upgrade`,
|
|
3007
|
-
|
|
3008
|
-
// Install all essential packages in one consolidated step
|
|
3009
|
-
`DEBIAN_FRONTEND=noninteractive apt install -y build-essential xinput x11-xkb-utils usbutils uuid-runtime linux-image-generic systemd-sysv openssh-server sudo locales udev util-linux iproute2 netplan.io ca-certificates curl wget chrony apt-utils tzdata kmod keyboard-configuration console-setup iputils-ping`,
|
|
3010
|
-
|
|
3011
|
-
// Ensure systemd is the init system
|
|
3012
|
-
`ln -sf /lib/systemd/systemd /sbin/init`,
|
|
3013
|
-
|
|
3014
|
-
// Clean up
|
|
3015
|
-
`apt-get clean`,
|
|
3016
|
-
],
|
|
3017
|
-
/**
|
|
3018
|
-
* @method user
|
|
3019
|
-
* @description Generates shell commands for creating a root user and configuring SSH access.
|
|
3020
|
-
* This is a critical security step for initial access to the provisioned system.
|
|
3021
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory.ubuntu
|
|
3022
|
-
* @returns {string[]} An array of shell commands.
|
|
3023
|
-
*/
|
|
3024
|
-
user: () => [
|
|
3025
|
-
`useradd -m -s /bin/bash -G sudo root`, // Create a root user with bash shell and sudo privileges.
|
|
3026
|
-
`echo 'root:root' | chpasswd`, // Set a default password for the root user (consider more secure methods for production).
|
|
3027
|
-
`mkdir -p /home/root/.ssh`, // Create .ssh directory for authorized keys.
|
|
3028
|
-
// Add the public SSH key to authorized_keys for passwordless login.
|
|
3029
|
-
`echo '${fs.readFileSync(
|
|
3030
|
-
`/home/dd/engine/engine-private/deploy/id_rsa.pub`,
|
|
3031
|
-
'utf8',
|
|
3032
|
-
)}' > /home/root/.ssh/authorized_keys`,
|
|
3033
|
-
`chown -R root /home/root/.ssh`, // Set ownership for security.
|
|
3034
|
-
`chmod 700 /home/root/.ssh`, // Set permissions for the .ssh directory.
|
|
3035
|
-
`chmod 600 /home/root/.ssh/authorized_keys`, // Set permissions for authorized_keys.
|
|
3036
|
-
],
|
|
3037
|
-
/**
|
|
3038
|
-
* @method timezone
|
|
3039
|
-
* @description Generates shell commands for configuring the system timezone and Chrony (NTP client).
|
|
3040
|
-
* Accurate time synchronization is essential for logging, security, and distributed systems.
|
|
3041
|
-
* @param {object} params - The parameters for the function.
|
|
3042
|
-
* @param {string} params.timezone - The timezone string (e.g., 'America/New_York').
|
|
3043
|
-
* @param {string} params.chronyConfPath - The path to the Chrony configuration file.
|
|
3044
|
-
* @param {string} [alias='chrony'] - The alias for the chrony service.
|
|
3045
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory.ubuntu
|
|
3046
|
-
* @returns {string[]} An array of shell commands.
|
|
3047
|
-
*/
|
|
3048
|
-
timezone: ({ timezone, chronyConfPath }, alias = 'chrony') => [
|
|
3049
|
-
`export DEBIAN_FRONTEND=noninteractive`, // Set non-interactive mode for Debian packages.
|
|
3050
|
-
`ln -fs /usr/share/zoneinfo/${timezone} /etc/localtime`, // Symlink timezone.
|
|
3051
|
-
`sudo dpkg-reconfigure --frontend noninteractive tzdata`, // Reconfigure timezone data.
|
|
3052
|
-
`sudo timedatectl set-timezone ${timezone}`, // Set timezone using timedatectl.
|
|
3053
|
-
`sudo timedatectl set-ntp true`, // Enable NTP synchronization.
|
|
3054
|
-
|
|
3055
|
-
// Write the Chrony configuration file.
|
|
3056
|
-
`echo '
|
|
3057
|
-
# Use public servers from the pool.ntp.org project.
|
|
3058
|
-
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
|
|
3059
|
-
# pool 2.pool.ntp.org iburst
|
|
3060
|
-
server ${process.env.MAAS_NTP_SERVER} iburst
|
|
3061
|
-
|
|
3062
|
-
# Record the rate at which the system clock gains/losses time.
|
|
3063
|
-
driftfile /var/lib/chrony/drift
|
|
3064
|
-
|
|
3065
|
-
# Allow the system clock to be stepped in the first three updates
|
|
3066
|
-
# if its offset is larger than 1 second.
|
|
3067
|
-
makestep 1.0 3
|
|
3068
|
-
|
|
3069
|
-
# Enable kernel synchronization of the real-time clock (RTC).
|
|
3070
|
-
rtcsync
|
|
3071
|
-
|
|
3072
|
-
# Enable hardware timestamping on all interfaces that support it.
|
|
3073
|
-
#hwtimestamp *
|
|
3074
|
-
|
|
3075
|
-
# Increase the minimum number of selectable sources required to adjust
|
|
3076
|
-
# the system clock.
|
|
3077
|
-
#minsources 2
|
|
3078
|
-
|
|
3079
|
-
# Allow NTP client access from local network.
|
|
3080
|
-
#allow 192.168.0.0/16
|
|
3081
|
-
|
|
3082
|
-
# Serve time even if not synchronized to a time source.
|
|
3083
|
-
#local stratum 10
|
|
3084
|
-
|
|
3085
|
-
# Specify file containing keys for NTP authentication.
|
|
3086
|
-
keyfile /etc/chrony.keys
|
|
3087
|
-
|
|
3088
|
-
# Get TAI-UTC offset and leap seconds from the system tz database.
|
|
3089
|
-
leapsectz right/UTC
|
|
3090
|
-
|
|
3091
|
-
# Specify directory for log files.
|
|
3092
|
-
logdir /var/log/chrony
|
|
3093
|
-
|
|
3094
|
-
# Select which information is logged.
|
|
3095
|
-
#log measurements statistics tracking
|
|
3096
|
-
' > ${chronyConfPath}`,
|
|
3097
|
-
`systemctl stop ${alias}`, // Stop Chrony service before reconfiguring.
|
|
3098
|
-
|
|
3099
|
-
// Enable, restart, and check status of Chrony service.
|
|
3100
|
-
`sudo systemctl enable --now ${alias}`,
|
|
3101
|
-
`sudo systemctl restart ${alias}`,
|
|
3102
|
-
|
|
3103
|
-
// Wait for chrony to synchronize
|
|
3104
|
-
`echo "Waiting for chrony to synchronize..."`,
|
|
3105
|
-
`for i in {1..30}; do chronyc tracking | grep -q "Leap status : Normal" && break || sleep 2; done`,
|
|
3106
|
-
|
|
3107
|
-
`sudo systemctl status ${alias}`,
|
|
3108
|
-
|
|
3109
|
-
// Verify Chrony synchronization.
|
|
3110
|
-
`chronyc sources`,
|
|
3111
|
-
`chronyc tracking`,
|
|
3112
|
-
|
|
3113
|
-
`chronyc sourcestats -v`, // Display source statistics.
|
|
3114
|
-
`timedatectl status`, // Display current time and date settings.
|
|
3115
|
-
],
|
|
3116
|
-
/**
|
|
3117
|
-
* @method keyboard
|
|
3118
|
-
* @description Generates shell commands for configuring the keyboard layout.
|
|
3119
|
-
* This ensures correct input behavior on the provisioned system.
|
|
3120
|
-
* @param {string} [keyCode='en'] - The keyboard layout code (e.g., 'en', 'es').
|
|
3121
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory.ubuntu
|
|
3122
|
-
* @returns {string[]} An array of shell commands.
|
|
3123
|
-
*/
|
|
3124
|
-
keyboard: (keyCode = 'en') => [
|
|
3125
|
-
`sudo locale-gen en_US.UTF-8`,
|
|
3126
|
-
`sudo update-locale LANG=en_US.UTF-8`,
|
|
3127
|
-
`sudo sed -i 's/XKBLAYOUT="us"/XKBLAYOUT="${keyCode}"/' /etc/default/keyboard`,
|
|
3128
|
-
`sudo dpkg-reconfigure --frontend noninteractive keyboard-configuration`,
|
|
3129
|
-
`sudo systemctl restart keyboard-setup.service`,
|
|
3130
|
-
],
|
|
3131
|
-
},
|
|
3132
|
-
/**
|
|
3133
|
-
* @property {object} rocky
|
|
3134
|
-
* @description Provisioning steps for Rocky Linux-based systems.
|
|
3135
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory
|
|
3136
|
-
* @namespace UnderpostBaremetal.systemProvisioningFactory.rocky
|
|
3137
|
-
*/
|
|
3138
|
-
rocky: {
|
|
3139
|
-
/**
|
|
3140
|
-
* @method base
|
|
3141
|
-
* @description Generates shell commands for basic Rocky Linux system provisioning.
|
|
3142
|
-
* This includes installing Node.js, npm, and underpost CLI tools.
|
|
3143
|
-
* @param {object} params - The parameters for the function.
|
|
3144
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory.rocky
|
|
3145
|
-
* @returns {string[]} An array of shell commands.
|
|
3146
|
-
*/
|
|
3147
|
-
base: () => [
|
|
3148
|
-
// Update system and install EPEL repository
|
|
3149
|
-
`dnf -y update`,
|
|
3150
|
-
`dnf -y install epel-release`,
|
|
3151
|
-
|
|
3152
|
-
// Install essential system tools (avoiding duplicates from container packages)
|
|
3153
|
-
`dnf -y install --allowerasing bzip2 openssh-server nano vim-enhanced less openssl-devel git gnupg2 libnsl perl`,
|
|
3154
|
-
`dnf clean all`,
|
|
3155
|
-
|
|
3156
|
-
// Install Node.js
|
|
3157
|
-
`curl -fsSL https://rpm.nodesource.com/setup_24.x | bash -`,
|
|
3158
|
-
`dnf install -y nodejs`,
|
|
3159
|
-
`dnf clean all`,
|
|
3160
|
-
|
|
3161
|
-
// Verify Node.js and npm versions
|
|
3162
|
-
`node --version`,
|
|
3163
|
-
`npm --version`,
|
|
3164
|
-
|
|
3165
|
-
// Install underpost ci/cd cli
|
|
3166
|
-
`npm install -g underpost`,
|
|
3167
|
-
`underpost --version`,
|
|
3168
|
-
],
|
|
3169
|
-
/**
|
|
3170
|
-
* @method user
|
|
3171
|
-
* @description Generates shell commands for creating a root user and configuring SSH access on Rocky Linux.
|
|
3172
|
-
* This is a critical security step for initial access to the provisioned system.
|
|
3173
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory.rocky
|
|
3174
|
-
* @returns {string[]} An array of shell commands.
|
|
3175
|
-
*/
|
|
3176
|
-
user: () => [
|
|
3177
|
-
`useradd -m -s /bin/bash -G wheel root`, // Create a root user with bash shell and wheel group (sudo on RHEL)
|
|
3178
|
-
`echo 'root:root' | chpasswd`, // Set a default password for the root user
|
|
3179
|
-
`mkdir -p /home/root/.ssh`, // Create .ssh directory for authorized keys
|
|
3180
|
-
// Add the public SSH key to authorized_keys for passwordless login
|
|
3181
|
-
`echo '${fs.readFileSync(
|
|
3182
|
-
`/home/dd/engine/engine-private/deploy/id_rsa.pub`,
|
|
3183
|
-
'utf8',
|
|
3184
|
-
)}' > /home/root/.ssh/authorized_keys`,
|
|
3185
|
-
`chown -R root:root /home/root/.ssh`, // Set ownership for security
|
|
3186
|
-
`chmod 700 /home/root/.ssh`, // Set permissions for the .ssh directory
|
|
3187
|
-
`chmod 600 /home/root/.ssh/authorized_keys`, // Set permissions for authorized_keys
|
|
3188
|
-
],
|
|
3189
|
-
/**
|
|
3190
|
-
* @method timezone
|
|
3191
|
-
* @description Generates shell commands for configuring the system timezone on Rocky Linux.
|
|
3192
|
-
* @param {object} params - The parameters for the function.
|
|
3193
|
-
* @param {string} params.timezone - The timezone string (e.g., 'America/Santiago').
|
|
3194
|
-
* @param {string} params.chronyConfPath - The path to the Chrony configuration file (optional).
|
|
3195
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory.rocky
|
|
3196
|
-
* @returns {string[]} An array of shell commands.
|
|
3197
|
-
*/
|
|
3198
|
-
timezone: ({ timezone, chronyConfPath = '/etc/chrony.conf' }) => [
|
|
3199
|
-
// Set system timezone using both methods (for chroot and running system)
|
|
3200
|
-
`ln -sf /usr/share/zoneinfo/${timezone} /etc/localtime`,
|
|
3201
|
-
`echo '${timezone}' > /etc/timezone`,
|
|
3202
|
-
`timedatectl set-timezone ${timezone} 2>/dev/null`,
|
|
3203
|
-
|
|
3204
|
-
// Configure chrony with local NTP server and common NTP pools
|
|
3205
|
-
`echo '# Local NTP server' > ${chronyConfPath}`,
|
|
3206
|
-
`echo 'server 192.168.1.1 iburst prefer' >> ${chronyConfPath}`,
|
|
3207
|
-
`echo '' >> ${chronyConfPath}`,
|
|
3208
|
-
`echo '# Fallback public NTP servers' >> ${chronyConfPath}`,
|
|
3209
|
-
`echo 'server 0.pool.ntp.org iburst' >> ${chronyConfPath}`,
|
|
3210
|
-
`echo 'server 1.pool.ntp.org iburst' >> ${chronyConfPath}`,
|
|
3211
|
-
`echo 'server 2.pool.ntp.org iburst' >> ${chronyConfPath}`,
|
|
3212
|
-
`echo 'server 3.pool.ntp.org iburst' >> ${chronyConfPath}`,
|
|
3213
|
-
`echo '' >> ${chronyConfPath}`,
|
|
3214
|
-
`echo '# Configuration' >> ${chronyConfPath}`,
|
|
3215
|
-
`echo 'driftfile /var/lib/chrony/drift' >> ${chronyConfPath}`,
|
|
3216
|
-
`echo 'makestep 1.0 3' >> ${chronyConfPath}`,
|
|
3217
|
-
`echo 'rtcsync' >> ${chronyConfPath}`,
|
|
3218
|
-
`echo 'logdir /var/log/chrony' >> ${chronyConfPath}`,
|
|
3219
|
-
|
|
3220
|
-
// Enable chronyd to start on boot
|
|
3221
|
-
`systemctl enable chronyd 2>/dev/null`,
|
|
3222
|
-
|
|
3223
|
-
// Create systemd link for boot (works in chroot)
|
|
3224
|
-
`mkdir -p /etc/systemd/system/multi-user.target.wants`,
|
|
3225
|
-
`ln -sf /usr/lib/systemd/system/chronyd.service /etc/systemd/system/multi-user.target.wants/chronyd.service 2>/dev/null`,
|
|
3226
|
-
|
|
3227
|
-
// Start chronyd if systemd is running
|
|
3228
|
-
`systemctl start chronyd 2>/dev/null`,
|
|
3229
|
-
|
|
3230
|
-
// Restart chronyd to apply configuration
|
|
3231
|
-
`systemctl restart chronyd 2>/dev/null`,
|
|
3232
|
-
|
|
3233
|
-
// Force immediate time synchronization (only if chronyd is running)
|
|
3234
|
-
`chronyc makestep 2>/dev/null`,
|
|
3235
|
-
|
|
3236
|
-
// Verify timezone configuration
|
|
3237
|
-
`ls -l /etc/localtime`,
|
|
3238
|
-
`cat /etc/timezone || echo 'No /etc/timezone file'`,
|
|
3239
|
-
`timedatectl status 2>/dev/null || echo 'Timezone set to ${timezone} (timedatectl not available in chroot)'`,
|
|
3240
|
-
`chronyc tracking 2>/dev/null || echo 'Chrony configured but not running (will start on boot)'`,
|
|
3241
|
-
],
|
|
3242
|
-
/**
|
|
3243
|
-
* @method keyboard
|
|
3244
|
-
* @description Generates shell commands for configuring the keyboard layout on Rocky Linux.
|
|
3245
|
-
* This uses localectl to set the keyboard layout for both console and X11.
|
|
3246
|
-
* @param {string} [keyCode='us'] - The keyboard layout code (e.g., 'us', 'es').
|
|
3247
|
-
* @memberof UnderpostBaremetal.systemProvisioningFactory.rocky
|
|
3248
|
-
* @returns {string[]} An array of shell commands.
|
|
3249
|
-
*/
|
|
3250
|
-
keyboard: (keyCode = 'us') => [
|
|
3251
|
-
// Configure vconsole.conf for console keyboard layout (persistent)
|
|
3252
|
-
`echo 'KEYMAP=${keyCode}' > /etc/vconsole.conf`,
|
|
3253
|
-
`echo 'FONT=latarcyrheb-sun16' >> /etc/vconsole.conf`,
|
|
3254
|
-
|
|
3255
|
-
// Configure locale.conf for system locale
|
|
3256
|
-
`echo 'LANG=en_US.UTF-8' > /etc/locale.conf`,
|
|
3257
|
-
`echo 'LC_ALL=en_US.UTF-8' >> /etc/locale.conf`,
|
|
3258
|
-
|
|
3259
|
-
// Set keyboard layout using localectl (works if systemd is running)
|
|
3260
|
-
`localectl set-locale LANG=en_US.UTF-8 2>/dev/null`,
|
|
3261
|
-
`localectl set-keymap ${keyCode} 2>/dev/null`,
|
|
3262
|
-
`localectl set-x11-keymap ${keyCode} 2>/dev/null`,
|
|
3263
|
-
|
|
3264
|
-
// Configure X11 keyboard layout file directly
|
|
3265
|
-
`mkdir -p /etc/X11/xorg.conf.d`,
|
|
3266
|
-
`echo 'Section "InputClass"' > /etc/X11/xorg.conf.d/00-keyboard.conf`,
|
|
3267
|
-
`echo ' Identifier "system-keyboard"' >> /etc/X11/xorg.conf.d/00-keyboard.conf`,
|
|
3268
|
-
`echo ' MatchIsKeyboard "on"' >> /etc/X11/xorg.conf.d/00-keyboard.conf`,
|
|
3269
|
-
`echo ' Option "XkbLayout" "${keyCode}"' >> /etc/X11/xorg.conf.d/00-keyboard.conf`,
|
|
3270
|
-
`echo 'EndSection' >> /etc/X11/xorg.conf.d/00-keyboard.conf`,
|
|
3271
|
-
|
|
3272
|
-
// Load the keymap immediately (if not in chroot)
|
|
3273
|
-
`loadkeys ${keyCode} 2>/dev/null || echo 'Keymap ${keyCode} configured (loadkeys not available in chroot)'`,
|
|
3274
|
-
|
|
3275
|
-
// Verify configuration
|
|
3276
|
-
`echo 'Keyboard configuration files:'`,
|
|
3277
|
-
`cat /etc/vconsole.conf`,
|
|
3278
|
-
`cat /etc/locale.conf`,
|
|
3279
|
-
`cat /etc/X11/xorg.conf.d/00-keyboard.conf 2>/dev/null || echo 'X11 config created'`,
|
|
3280
|
-
`localectl status 2>/dev/null || echo 'Keyboard layout set to ${keyCode} (localectl not available in chroot)'`,
|
|
3281
|
-
],
|
|
3282
|
-
},
|
|
3283
|
-
},
|
|
3284
|
-
|
|
3285
2964
|
/**
|
|
3286
2965
|
* @method rebuildNfsServer
|
|
3287
2966
|
* @description Configures and restarts the NFS server to export the specified path.
|
|
@@ -3290,9 +2969,10 @@ logdir /var/log/chrony
|
|
|
3290
2969
|
* @param {string} params.nfsHostPath - The path to the NFS server export.
|
|
3291
2970
|
* @memberof UnderpostBaremetal
|
|
3292
2971
|
* @param {string} [params.subnet='192.168.1.0/24'] - The subnet allowed to access the NFS export.
|
|
2972
|
+
* @param {boolean} [params.nfsReset=false] - Flag to completely reset the NFS server (restart service).
|
|
3293
2973
|
* @returns {void}
|
|
3294
2974
|
*/
|
|
3295
|
-
rebuildNfsServer({ nfsHostPath, subnet }) {
|
|
2975
|
+
rebuildNfsServer({ nfsHostPath, subnet, nfsReset }) {
|
|
3296
2976
|
if (!subnet) subnet = '192.168.1.0/24'; // Default subnet if not provided.
|
|
3297
2977
|
// Write the NFS exports configuration to /etc/exports.
|
|
3298
2978
|
fs.writeFileSync(
|
|
@@ -3341,9 +3021,11 @@ udp-port = 32766
|
|
|
3341
3021
|
|
|
3342
3022
|
// Restart the nfs-server service to apply all configuration changes,
|
|
3343
3023
|
// including port settings from /etc/nfs.conf and export changes.
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3024
|
+
if (nfsReset) {
|
|
3025
|
+
logger.info('Restarting nfs-server service...');
|
|
3026
|
+
shellExec(`sudo systemctl restart nfs-server`);
|
|
3027
|
+
logger.info('NFS server restarted.');
|
|
3028
|
+
}
|
|
3347
3029
|
},
|
|
3348
3030
|
|
|
3349
3031
|
/**
|