underpost 3.2.10 → 3.2.11

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 (54) hide show
  1. package/.vscode/extensions.json +9 -9
  2. package/.vscode/settings.json +12 -1
  3. package/CHANGELOG.md +74 -1
  4. package/CLI-HELP.md +80 -26
  5. package/README.md +3 -3
  6. package/bin/build.js +9 -6
  7. package/bin/build.template.js +187 -0
  8. package/bin/deploy.js +29 -18
  9. package/conf.js +1 -4
  10. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  11. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +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/manifests/lxd/lxd-admin-profile.yaml +12 -3
  15. package/manifests/mongodb-4.4/headless-service.yaml +10 -0
  16. package/manifests/mongodb-4.4/kustomization.yaml +3 -1
  17. package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
  18. package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
  19. package/manifests/mongodb-4.4/statefulset.yaml +79 -0
  20. package/manifests/mongodb-4.4/storage-class.yaml +9 -0
  21. package/manifests/valkey/statefulset.yaml +1 -1
  22. package/manifests/valkey/valkey-nodeport.yaml +17 -0
  23. package/package.json +3 -3
  24. package/scripts/ipxe-setup.sh +52 -49
  25. package/scripts/k3s-node-setup.sh +84 -68
  26. package/scripts/lxd-vm-setup.sh +193 -8
  27. package/scripts/maas-nat-firewalld.sh +145 -0
  28. package/src/cli/baremetal.js +115 -93
  29. package/src/cli/cluster.js +548 -221
  30. package/src/cli/deploy.js +131 -166
  31. package/src/cli/fs.js +11 -3
  32. package/src/cli/index.js +75 -17
  33. package/src/cli/lxd.js +1034 -240
  34. package/src/cli/monitor.js +9 -3
  35. package/src/cli/release.js +72 -36
  36. package/src/cli/repository.js +10 -16
  37. package/src/cli/run.js +70 -53
  38. package/src/cli/secrets.js +11 -2
  39. package/src/client/components/core/Auth.js +4 -3
  40. package/src/client/components/core/ClientEvents.js +76 -0
  41. package/src/client/components/core/EventBus.js +4 -0
  42. package/src/client/components/core/Modal.js +82 -41
  43. package/src/db/DataBaseProvider.js +9 -9
  44. package/src/db/mariadb/MariaDB.js +2 -1
  45. package/src/db/mongo/MongoBootstrap.js +592 -522
  46. package/src/db/mongo/MongooseDB.js +19 -15
  47. package/src/index.js +1 -1
  48. package/src/server/conf.js +62 -15
  49. package/src/server/proxy.js +9 -2
  50. package/src/server/start.js +7 -3
  51. package/src/server/valkey.js +2 -0
  52. package/bin/file.js +0 -220
  53. package/bin/vs.js +0 -74
  54. package/bin/zed.js +0 -84
@@ -27,6 +27,13 @@ const logger = loggerFactory(import.meta);
27
27
  * and system provisioning for different architectures.
28
28
  */
29
29
  class UnderpostBaremetal {
30
+ // NFSv3 RPC ports. Single source of truth shared by the firewall/export setup
31
+ // (rebuildNfsServer) and the kernel `nfsroot=` mount options so the client mount and the
32
+ // opened firewall ports always agree.
33
+ // rpc.statd rejects identical listen and outgoing ports (exit 255 "Listening and outgoing ports cannot be the same!").
34
+ // statd=32765 (listen), statdOutgoing=32766 (SM_NOTIFY source port) is the standard split.
35
+ static NFS_V3_PORTS = { mountd: 20048, statd: 32765, statdOutgoing: 32766, lockd: 32803 };
36
+
30
37
  static API = {
31
38
  /**
32
39
  * @method callback
@@ -498,10 +505,13 @@ rm -rf ${artifacts.join(' ')}`);
498
505
 
499
506
  // Handle control server installation.
500
507
  if (options.controlServerInstall === true) {
501
- // Ensure scripts are executable and then run them.
508
+ // Ensure the MAAS setup script is executable and then run it.
502
509
  shellExec(`chmod +x ${underpostRoot}/scripts/maas-setup.sh`);
503
- shellExec(`chmod +x ${underpostRoot}/scripts/nat-iptables.sh`);
510
+ if (!fs.existsSync(`${process.env.HOME}/.ssh/id_rsa.pub`)) shellExec(`node bin ssh --generate`);
504
511
  shellExec(`${underpostRoot}/scripts/maas-setup.sh`);
512
+ // Install GRUB modules into the NFS root filesystem to
513
+ // ensure the necessary files are present for bootloader installation later.
514
+ Underpost.baremetal.installGrubModules();
505
515
  return;
506
516
  }
507
517
 
@@ -601,7 +611,7 @@ rm -rf ${artifacts.join(' ')}`);
601
611
  }
602
612
 
603
613
  // Create a podman container to extract QEMU static binaries.
604
- shellExec(`sudo podman create --name extract multiarch/qemu-user-static`);
614
+ shellExec(`sudo podman create --name extract docker.io/multiarch/qemu-user-static`);
605
615
  shellExec(`podman ps -a`); // List all podman containers for verification.
606
616
 
607
617
  // If cross-architecture, copy the QEMU static binary into the chroot.
@@ -917,9 +927,6 @@ rm -rf ${artifacts.join(' ')}`);
917
927
  isoUrl: workflowsConfig[workflowId].isoUrl,
918
928
  });
919
929
 
920
- // Set up iptables rules for NAT and port forwarding to enable network connectivity for the baremetal machines.
921
- shellExec(`${underpostRoot}/scripts/nat-iptables.sh`, { silent: true });
922
-
923
930
  // Start HTTP bootstrap server if commissioning or if ISO URL is used (for ISO-based workflows).
924
931
  if (options.bootstrapHttpServerRun || options.commission) {
925
932
  Underpost.baremetal.httpBootstrapServerRunnerFactory({
@@ -933,7 +940,7 @@ rm -rf ${artifacts.join(' ')}`);
933
940
  });
934
941
  }
935
942
 
936
- // Rebuild NFS server configuration.
943
+ // Rebuild NFS exports and the matching MAAS/firewalld host configuration.
937
944
  if (
938
945
  (options.nfsBuildServer === true || options.commission === true) &&
939
946
  (workflowsConfig[workflowId].type === 'iso-nfs' ||
@@ -943,6 +950,7 @@ rm -rf ${artifacts.join(' ')}`);
943
950
  Underpost.baremetal.rebuildNfsServer({
944
951
  nfsHostPath,
945
952
  nfsReset: options.nfsReset,
953
+ underpostRoot,
946
954
  });
947
955
 
948
956
  // Handle commissioning tasks
@@ -1399,7 +1407,9 @@ rm -rf ${artifacts.join(' ')}`);
1399
1407
  shellExec(`mkdir -p ${mountPoint}`);
1400
1408
 
1401
1409
  // Ensure mount point is not already mounted
1402
- shellExec(`sudo umount ${mountPoint} 2>/dev/null`);
1410
+ shellExec(`sudo umount ${mountPoint}`, {
1411
+ silentOnError: true, // Ignore errors if not mounted
1412
+ });
1403
1413
 
1404
1414
  try {
1405
1415
  // Mount the ISO
@@ -2342,39 +2352,36 @@ fi
2342
2352
  // Determine OS family from osIdLike
2343
2353
  const { isDebianBased, isRhelBased } = Underpost.baremetal.getFamilyBaseOs(options.osIdLike);
2344
2354
 
2345
- const ipParam =
2346
- `ip=${ipClient}:${ipFileServer}:${ipDhcpServer}:${netmask}:${hostname}` +
2347
- `:${networkInterfaceName ? networkInterfaceName : 'eth0'}:${ipConfig}:${dnsServer}`;
2348
-
2349
- const nfsOptions = `${
2350
- type === 'chroot-debootstrap' || type === 'chroot-container'
2351
- ? [
2352
- 'tcp',
2353
- 'nfsvers=3',
2354
- 'nolock',
2355
- // 'protocol=tcp',
2356
- // 'hard=true',
2357
- 'port=2049',
2358
- // 'sec=none',
2359
- 'hard',
2360
- 'intr',
2361
- 'rsize=32768',
2362
- 'wsize=32768',
2363
- 'acregmin=0',
2364
- 'acregmax=0',
2365
- 'acdirmin=0',
2366
- 'acdirmax=0',
2367
- 'noac',
2368
- // 'nodev',
2369
- // 'nosuid',
2370
- ]
2371
- : []
2372
- }`;
2373
-
2374
- const nfsRootParam = `nfsroot=${ipFileServer}:${process.env.NFS_EXPORT_PATH}/${hostname}${
2375
- nfsOptions ? `,${nfsOptions}` : ''
2376
- }`;
2377
-
2355
+ const ifaceName = networkInterfaceName ? networkInterfaceName : 'eth0';
2356
+ const isStaticIp = ipConfig === 'none' || ipConfig === 'off';
2357
+ const ipParam = isStaticIp
2358
+ ? `ip=${ipClient}:${ipFileServer}:${ipDhcpServer}:${netmask}:${hostname}:${ifaceName}:${ipConfig}:${dnsServer}`
2359
+ : `ip=::::${hostname}:${ifaceName}:${ipConfig}`;
2360
+
2361
+ let nfsMountOptions = [];
2362
+ if (type === 'chroot-debootstrap' || type === 'chroot-container') {
2363
+ nfsMountOptions = [
2364
+ 'tcp',
2365
+ 'nfsvers=3',
2366
+ 'nolock',
2367
+ 'port=2049',
2368
+ 'hard',
2369
+ 'intr',
2370
+ 'rsize=32768',
2371
+ 'wsize=32768',
2372
+ 'acregmin=0',
2373
+ 'acregmax=0',
2374
+ 'acdirmin=0',
2375
+ 'acdirmax=0',
2376
+ 'noac',
2377
+ ];
2378
+ } else if (type === 'iso-nfs' && isDebianBased) {
2379
+ nfsMountOptions = ['nolock', 'nfsvers=3', 'tcp', 'hard', 'port=2049', 'rsize=32768', 'wsize=32768'];
2380
+ }
2381
+ const nfsOptions = nfsMountOptions.join(',');
2382
+ const nfsServerPath = `${ipFileServer}:${process.env.NFS_EXPORT_PATH}/${hostname}`;
2383
+ const nfsRootParam = `nfsroot=${nfsServerPath}${nfsOptions ? `,${nfsOptions}` : ''}`;
2384
+ const casperNfsParams = [`nfsroot=${nfsServerPath}`, ...(nfsOptions ? [`nfsopts=${nfsOptions}`] : [])];
2378
2385
  const permissionsParams = [
2379
2386
  `rw`,
2380
2387
  // `ro`
@@ -2447,8 +2454,12 @@ fi
2447
2454
  let qemuNfsRootParams = [`root=/dev/nfs`, `rootfstype=nfs`];
2448
2455
  cmd = [ipParam, ...qemuNfsRootParams, nfsRootParam, ...kernelParams];
2449
2456
  } else {
2450
- // 'iso-nfs' — Debian/Ubuntu NFS root boot: kernel/initrd from ISO, root filesystem served via NFS.
2451
- cmd = [ipParam, `netboot=nfs`, nfsRootParam, ...kernelParams, ...performanceParams];
2457
+ // 'iso-nfs' — kernel/initrd from ISO, root filesystem served via NFS.
2458
+ if (isDebianBased) {
2459
+ cmd = [ipParam, `boot=casper`, `netboot=nfs`, ...casperNfsParams, ...kernelParams];
2460
+ } else {
2461
+ cmd = [ipParam, `netboot=nfs`, nfsRootParam, ...kernelParams, ...performanceParams];
2462
+ }
2452
2463
  }
2453
2464
 
2454
2465
  // Add RHEL/Rocky/Fedora based images specific parameters
@@ -2458,7 +2469,7 @@ fi
2458
2469
  }
2459
2470
  // Add Debian/Ubuntu based images specific parameters
2460
2471
  else if (isDebianBased) {
2461
- cmd = cmd.concat([`initrd=initrd.img`, `init=/sbin/init`]);
2472
+ if (type !== 'iso-nfs') cmd = cmd.concat([`initrd=initrd.img`, `init=/sbin/init`]);
2462
2473
  if (options.dev) cmd = cmd.concat([`debug`, `ignore_loglevel`]);
2463
2474
  }
2464
2475
 
@@ -2704,7 +2715,7 @@ fi
2704
2715
  shellExec(`sudo podman run --rm --privileged docker.io/multiarch/qemu-user-static:latest --reset -p yes`);
2705
2716
  // Mount binfmt_misc filesystem.
2706
2717
  shellExec(`sudo modprobe binfmt_misc`);
2707
- shellExec(`sudo mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc`);
2718
+ shellExec(`sudo mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc`, { silentOnError: true });
2708
2719
  },
2709
2720
 
2710
2721
  /**
@@ -2761,6 +2772,19 @@ fi
2761
2772
  await Underpost.baremetal.macMonitor({ nfsHostPath });
2762
2773
  },
2763
2774
 
2775
+ /**
2776
+ * @method installGrubModules
2777
+ * @description Installs the necessary GRUB modules for both ARM64 and AMD64 architectures.
2778
+ * This ensures that the GRUB bootloader can properly load the kernel and initrd images
2779
+ * during the network boot process, regardless of the target architecture.
2780
+ * @memberof UnderpostBaremetal
2781
+ * @returns {void}
2782
+ */
2783
+ installGrubModules() {
2784
+ if (!fs.existsSync('/usr/lib/grub/x86_64-efi')) shellExec(`sudo dnf install -y grub2-efi-x64-modules`);
2785
+ if (!fs.existsSync('/usr/lib/grub/arm64-efi')) shellExec(`sudo dnf install -y grub2-efi-aa64-modules`);
2786
+ },
2787
+
2764
2788
  /**
2765
2789
  * @method crossArchBinFactory
2766
2790
  * @description Copies the appropriate QEMU static binary into the NFS root filesystem
@@ -2786,9 +2810,6 @@ fi
2786
2810
  logger.warn(`Unsupported bootstrap architecture: ${bootstrapArch}`);
2787
2811
  break;
2788
2812
  }
2789
- // Install GRUB EFI modules for both architectures to ensure compatibility.
2790
- shellExec(`sudo dnf install -y grub2-efi-aa64-modules`);
2791
- shellExec(`sudo dnf install -y grub2-efi-x64-modules`);
2792
2813
  },
2793
2814
 
2794
2815
  /**
@@ -2904,9 +2925,10 @@ EOF`);
2904
2925
  stdout: true,
2905
2926
  silentOnError: true,
2906
2927
  });
2907
- const isPathMounted = typeof mountpointOut === 'string' && mountpointOut.length > 0
2908
- ? !mountpointOut.match('not a mountpoint') && !mountpointOut.match('No such file')
2909
- : false;
2928
+ const isPathMounted =
2929
+ typeof mountpointOut === 'string' && mountpointOut.length > 0
2930
+ ? !mountpointOut.match('not a mountpoint') && !mountpointOut.match('No such file')
2931
+ : false;
2910
2932
 
2911
2933
  if (isPathMounted) {
2912
2934
  logger.warn('Nfs path already mounted', mountPath);
@@ -2966,54 +2988,48 @@ EOF`);
2966
2988
 
2967
2989
  /**
2968
2990
  * @method rebuildNfsServer
2969
- * @description Configures and restarts the NFS server to export the specified path.
2970
- * This is crucial for allowing baremetal machines to boot via NFS.
2991
+ * @description Configures NFS exports and aligns host firewall/NFS daemon ports for MAAS workflows.
2971
2992
  * @param {object} params - The parameters for the function.
2972
2993
  * @param {string} params.nfsHostPath - The path to the NFS server export.
2973
2994
  * @memberof UnderpostBaremetal
2974
2995
  * @param {string} [params.subnet='192.168.1.0/24'] - The subnet allowed to access the NFS export.
2975
2996
  * @param {boolean} [params.nfsReset=false] - Flag to completely reset the NFS server (restart service).
2997
+ * @param {string} [params.underpostRoot] - Repository root used to locate helper scripts.
2976
2998
  * @returns {void}
2977
2999
  */
2978
- rebuildNfsServer({ nfsHostPath, subnet, nfsReset }) {
3000
+ rebuildNfsServer({ nfsHostPath, subnet, nfsReset, underpostRoot }) {
2979
3001
  if (!subnet) subnet = '192.168.1.0/24'; // Default subnet if not provided.
3002
+ if (!underpostRoot) {
3003
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
3004
+ underpostRoot = path.resolve(__dirname, '../..');
3005
+ }
3006
+
3007
+ const maasNatFirewalldPath = path.resolve(underpostRoot, 'scripts/maas-nat-firewalld.sh');
3008
+ const nfsPorts = UnderpostBaremetal.NFS_V3_PORTS;
3009
+ const exportOptions = ['rw', 'sync', 'no_root_squash', 'no_subtree_check', 'insecure'].join(',');
3010
+
3011
+ if (!fs.existsSync(maasNatFirewalldPath)) {
3012
+ throw new Error(`MAAS firewalld helper not found: ${maasNatFirewalldPath}`);
3013
+ }
3014
+
2980
3015
  // Write the NFS exports configuration to /etc/exports.
2981
- fs.writeFileSync(
2982
- `/etc/exports`,
2983
- `${nfsHostPath} ${subnet}(${[
2984
- 'rw', // Read-write access.
2985
- // 'all_squash', // Squash all client UIDs/GIDs to anonymous.
2986
- 'sync', // Synchronous writes.
2987
- 'no_root_squash', // Do not squash root user.
2988
- 'no_subtree_check', // Disable subtree checking.
2989
- 'insecure', // Allow connections from non-privileged ports.
2990
- ]})`,
2991
- 'utf8',
2992
- );
3016
+ if (!fs.existsSync(nfsHostPath)) fs.mkdirSync(nfsHostPath, { recursive: true });
3017
+ fs.writeFileSync(`/etc/exports`, `${nfsHostPath} ${subnet}(${exportOptions})`, 'utf8');
2993
3018
 
2994
- logger.info('Writing NFS server configuration to /etc/nfs.conf...');
2995
- // Write NFS daemon configuration, including port settings.
2996
- fs.writeFileSync(
2997
- `/etc/nfs.conf`,
2998
- `[mountd]
2999
- port = 20048
3000
-
3001
- [statd]
3002
- port = 32765
3003
- outgoing-port = 32765
3004
-
3005
- [nfsd]
3006
- # Enable RDMA support if desired and hardware supports it.
3007
- rdma=y
3008
- rdma-port=20049
3009
-
3010
- [lockd]
3011
- port = 32766
3012
- udp-port = 32766
3013
- `,
3014
- 'utf8',
3019
+ logger.info('Configuring MAAS firewalld and fixed NFSv3 ports...');
3020
+ shellExec(
3021
+ [
3022
+ `MAAS_LAN_CIDR=${subnet}`,
3023
+ `NFS_MODE=v3`,
3024
+ `CONFIGURE_NFS_V3_PORTS=true`,
3025
+ `NFS_MOUNTD_PORT=${nfsPorts.mountd}`,
3026
+ `NFS_STATD_PORT=${nfsPorts.statd}`,
3027
+ `NFS_STATD_OUTGOING_PORT=${nfsPorts.statdOutgoing}`,
3028
+ `NFS_LOCKD_PORT=${nfsPorts.lockd}`,
3029
+ `NFS_LOCKD_UDP_PORT=${nfsPorts.lockd}`,
3030
+ `bash "${maasNatFirewalldPath}"`,
3031
+ ].join(' '),
3015
3032
  );
3016
- logger.info('NFS configuration written.');
3017
3033
 
3018
3034
  logger.info('Reloading NFS exports...');
3019
3035
  shellExec(`sudo exportfs -rav`);
@@ -3022,12 +3038,18 @@ udp-port = 32766
3022
3038
  logger.info('Displaying active NFS exports');
3023
3039
  shellExec(`sudo exportfs -s`);
3024
3040
 
3025
- // Restart the nfs-server service to apply all configuration changes,
3026
- // including port settings from /etc/nfs.conf and export changes.
3027
3041
  if (nfsReset) {
3028
- logger.info('Restarting nfs-server service...');
3029
- shellExec(`sudo systemctl restart nfs-server`);
3030
- logger.info('NFS server restarted.');
3042
+ logger.info('Restarting NFS server service...');
3043
+ let restarted = false;
3044
+ for (const unit of ['nfs-server', 'nfs-kernel-server']) {
3045
+ const result = shellExec(`sudo systemctl restart ${unit}`, { silentOnError: true });
3046
+ if (result.code === 0) {
3047
+ restarted = true;
3048
+ logger.info(`NFS server restarted via ${unit}.`);
3049
+ break;
3050
+ }
3051
+ }
3052
+ if (!restarted) logger.warn('Unable to restart nfs-server or nfs-kernel-server after export reload.');
3031
3053
  }
3032
3054
  },
3033
3055