underpost 2.8.78 → 2.8.82

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 (45) hide show
  1. package/.github/workflows/ghpkg.yml +23 -21
  2. package/.github/workflows/npmpkg.yml +16 -11
  3. package/.github/workflows/pwa-microservices-template.page.yml +12 -3
  4. package/.github/workflows/pwa-microservices-template.test.yml +20 -17
  5. package/.vscode/extensions.json +1 -2
  6. package/.vscode/settings.json +3 -0
  7. package/Dockerfile +14 -33
  8. package/README.md +25 -24
  9. package/bin/db.js +1 -0
  10. package/bin/deploy.js +91 -796
  11. package/bin/vs.js +10 -3
  12. package/cli.md +340 -191
  13. package/conf.js +4 -0
  14. package/docker-compose.yml +1 -1
  15. package/manifests/deployment/dd-template-development/deployment.yaml +167 -0
  16. package/manifests/deployment/dd-template-development/proxy.yaml +46 -0
  17. package/manifests/lxd/lxd-admin-profile.yaml +17 -0
  18. package/manifests/lxd/lxd-preseed.yaml +30 -0
  19. package/manifests/lxd/underpost-setup.sh +163 -0
  20. package/manifests/maas/device-scan.sh +43 -0
  21. package/manifests/maas/lxd-preseed.yaml +32 -0
  22. package/manifests/maas/maas-setup.sh +120 -0
  23. package/manifests/maas/nat-iptables.sh +26 -0
  24. package/manifests/mariadb/statefulset.yaml +2 -1
  25. package/manifests/mariadb/storage-class.yaml +10 -0
  26. package/manifests/mongodb-4.4/service-deployment.yaml +2 -2
  27. package/manifests/valkey/service.yaml +3 -9
  28. package/manifests/valkey/statefulset.yaml +10 -12
  29. package/package.json +1 -1
  30. package/src/cli/baremetal.js +1248 -0
  31. package/src/cli/cloud-init.js +528 -0
  32. package/src/cli/cluster.js +459 -232
  33. package/src/cli/deploy.js +34 -10
  34. package/src/cli/env.js +2 -2
  35. package/src/cli/image.js +57 -9
  36. package/src/cli/index.js +256 -218
  37. package/src/cli/lxd.js +380 -4
  38. package/src/index.js +40 -14
  39. package/src/runtime/lampp/Dockerfile +41 -47
  40. package/src/server/conf.js +58 -0
  41. package/src/server/logger.js +3 -3
  42. package/src/server/runtime.js +1 -6
  43. package/src/server/ssl.js +1 -12
  44. package/src/server/valkey.js +3 -3
  45. package/supervisord-openssh-server.conf +0 -5
package/bin/deploy.js CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  setUpProxyMaintenanceServer,
28
28
  writeEnv,
29
29
  getUnderpostRootPath,
30
+ buildCliDoc,
30
31
  } from '../src/server/conf.js';
31
32
  import { buildClient } from '../src/server/client-build.js';
32
33
  import { range, s4, setPad, timer, uniqueArray } from '../src/client/components/core/CommonJs.js';
@@ -37,10 +38,10 @@ import { JSONweb } from '../src/server/client-formatted.js';
37
38
 
38
39
  import { Xampp } from '../src/runtime/xampp/Xampp.js';
39
40
  import { ejs } from '../src/server/json-schema.js';
40
- import { buildCliDoc } from '../src/cli/index.js';
41
41
  import { getLocalIPv4Address, ip } from '../src/server/dns.js';
42
42
  import { Downloader } from '../src/server/downloader.js';
43
43
  import colors from 'colors';
44
+ import { program } from '../src/cli/index.js';
44
45
 
45
46
  colors.enable();
46
47
 
@@ -50,82 +51,6 @@ logger.info('argv', process.argv);
50
51
 
51
52
  const [exe, dir, operator] = process.argv;
52
53
 
53
- const updateVirtualRoot = async ({ nfsHostPath, IP_ADDRESS, ipaddr }) => {
54
- const steps = [
55
- `apt update`,
56
- `ln -sf /lib/systemd/systemd /sbin/init`,
57
- // `sudo apt install linux-modules-extra-6.8.0-31-generic`,
58
- `apt install -y sudo`,
59
- `apt install -y ntp`,
60
- `apt install -y openssh-server`,
61
- `apt install -y iptables`,
62
- `update-alternatives --set iptables /usr/sbin/iptables-legacy`,
63
- `update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy`,
64
- `apt install -y locales`,
65
- `apt install -y cloud-init`,
66
- `mkdir -p /var/lib/cloud`,
67
- `chown -R root:root /var/lib/cloud`,
68
- `chmod -R 0755 /var/lib/cloud`,
69
- `mkdir -p /home/root/.ssh`,
70
- `echo '${fs.readFileSync(
71
- `/home/dd/engine/engine-private/deploy/id_rsa.pub`,
72
- 'utf8',
73
- )}' >> /home/root/.ssh/authorized_keys`,
74
- `chmod 700 /home/root/.ssh`,
75
- `chmod 600 /home/root/.ssh/authorized_keys`,
76
- `systemctl enable ssh`,
77
- `systemctl enable ntp`,
78
- `apt install -y linux-generic-hwe-24.04`,
79
- `modprobe ip_tables`,
80
- `cat <<EOF_MAAS_CFG > /etc/cloud/cloud.cfg.d/90_maas.cfg
81
- datasource_list: [ MAAS ]
82
- datasource:
83
- MAAS:
84
- metadata_url: http://${IP_ADDRESS}:5248/MAAS/metadata
85
- users:
86
- - name: ${process.env.MAAS_ADMIN_USERNAME}
87
- ssh_authorized_keys:
88
- - ${fs.readFileSync(`/home/dd/engine/engine-private/deploy/id_rsa.pub`, 'utf8')}
89
- sudo: "ALL=(ALL) NOPASSWD:ALL"
90
- groups: sudo
91
- shell: /bin/bash
92
- packages:
93
- - git
94
- - htop
95
- - ufw
96
- # package_update: true
97
- runcmd:
98
- - ufw enable
99
- - ufw allow ssh
100
- resize_rootfs: false
101
- growpart:
102
- mode: off
103
- network:
104
- version: 2
105
- ethernets:
106
- ${process.env.RPI4_INTERFACE_NAME}:
107
- dhcp4: true
108
- addresses:
109
- - ${ipaddr}/24
110
- EOF_MAAS_CFG`,
111
- ];
112
-
113
- shellExec(`sudo chroot ${nfsHostPath} /usr/bin/qemu-aarch64-static /bin/bash <<'EOF'
114
- ${steps
115
- .map(
116
- (s, i) => `echo "step ${i + 1}/${steps.length}: ${s.split('\n')[0]}"
117
- ${s}
118
- `,
119
- )
120
- .join(``)}
121
- EOF`);
122
-
123
- shellExec(`sudo chroot ${nfsHostPath} /usr/bin/qemu-aarch64-static /bin/bash <<'EOF'
124
- echo "nameserver ${process.env.MAAS_DNS}" | tee /etc/resolv.conf > /dev/null
125
- apt update
126
- EOF`);
127
- };
128
-
129
54
  try {
130
55
  switch (operator) {
131
56
  case 'save':
@@ -797,6 +722,13 @@ try {
797
722
  .replaceAll(`engine.version: '${version}'`, `engine.version: '${newVersion}'`),
798
723
  'utf8',
799
724
  );
725
+ fs.writeFileSync(
726
+ `./manifests/deployment/dd-template-development/deployment.yaml`,
727
+ fs
728
+ .readFileSync(`./manifests/deployment/dd-template-development/deployment.yaml`, 'utf8')
729
+ .replaceAll(`underpost:v${version}`, `underpost:v${newVersion}`),
730
+ 'utf8',
731
+ );
800
732
 
801
733
  if (fs.existsSync(`./.github/workflows/docker-image.yml`))
802
734
  fs.writeFileSync(
@@ -822,6 +754,9 @@ try {
822
754
  }
823
755
 
824
756
  case 'version-deploy': {
757
+ shellExec(
758
+ `underpost secret underpost --create-from-file /home/dd/engine/engine-private/conf/dd-cron/.env.production`,
759
+ );
825
760
  shellExec(`node bin/build dd conf`);
826
761
  shellExec(`git add . && cd ./engine-private && git add .`);
827
762
  shellExec(`node bin cmt . ci package-pwa-microservices-template`);
@@ -918,6 +853,16 @@ ${shellExec(`git log | grep Author: | sort -u`, { stdout: true }).split(`\n`).jo
918
853
  };
919
854
  DefaultConf.server[host][path].apiBaseProxyPath = '/';
920
855
  DefaultConf.server[host][path].apiBaseHost = 'www.nexodev.org';
856
+ } else if (confName === 'template') {
857
+ const host = 'default.net';
858
+ const path = '/';
859
+ DefaultConf.server[host][path].valkey = {
860
+ port: 6379,
861
+ host: 'valkey-service.default.svc.cluster.local',
862
+ };
863
+ // mongodb-0.mongodb-service
864
+ DefaultConf.server[host][path].db.host = 'mongodb://mongodb-service:27017';
865
+ confName = '';
921
866
  } else if (confName) {
922
867
  DefaultConf.client = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.client.json`, 'utf8'));
923
868
  DefaultConf.server = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.server.json`, 'utf8'));
@@ -1138,7 +1083,7 @@ EOF`);
1138
1083
  }
1139
1084
 
1140
1085
  case 'cli-docs': {
1141
- buildCliDoc();
1086
+ buildCliDoc(program);
1142
1087
  break;
1143
1088
  }
1144
1089
 
@@ -1190,12 +1135,63 @@ EOF`);
1190
1135
  break;
1191
1136
  }
1192
1137
 
1138
+ case 'postgresql-17': {
1139
+ if (process.argv.includes('install')) {
1140
+ shellExec(`sudo dnf module reset postgresql -y`);
1141
+ shellExec(`sudo dnf -qy module disable postgresql`);
1142
+ shellExec(
1143
+ `sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm`,
1144
+ );
1145
+ shellExec(`sudo dnf -qy module disable postgresql`);
1146
+ shellExec(`sudo dnf install -y postgresql17 postgresql17-server postgresql17-contrib`);
1147
+
1148
+ shellExec(`sudo /usr/pgsql-17/bin/postgresql-17-setup initdb`);
1149
+ }
1150
+ if (process.argv.includes('uninstall')) {
1151
+ shellExec(`sudo systemctl stop postgresql-17`);
1152
+ shellExec(`sudo systemctl disable postgresql-17`);
1153
+
1154
+ // Remove PostgreSQL 17 packages and repo
1155
+ shellExec(`sudo dnf remove -y postgresql17 postgresql17-server postgresql17-contrib`);
1156
+ shellExec(`sudo rpm -e pgdg-redhat-repo-$(rpm -q pgdg-redhat-repo --qf '%{VERSION}-%{RELEASE}') || true`);
1157
+ shellExec(`sudo rm -f /etc/yum.repos.d/pgdg-redhat-*.repo`);
1158
+
1159
+ // Clean up data, logs, config, and the postgres user
1160
+ shellExec(`sudo rm -rf /var/lib/pgsql/17 /var/log/pgsql`);
1161
+ shellExec(`sudo rm -rf /etc/postgresql`);
1162
+ } else {
1163
+ shellExec(`sudo systemctl enable postgresql-17`);
1164
+ shellExec(`sudo systemctl start postgresql-17`);
1165
+ }
1166
+ break;
1167
+ }
1168
+
1193
1169
  case 'postgresql-14': {
1194
- shellExec(`sudo /usr/pgsql-14/bin/postgresql-14-setup initdb`);
1195
- shellExec(`sudo systemctl start postgresql-14`);
1196
- shellExec(`sudo systemctl enable postgresql-14`);
1197
- shellExec(`sudo systemctl status postgresql-14`);
1198
- // sudo dnf install postgresql14-contrib
1170
+ if (process.argv.includes('install')) {
1171
+ shellExec(`sudo dnf module reset postgresql -y`);
1172
+ shellExec(`sudo dnf -qy module disable postgresql`);
1173
+
1174
+ shellExec(`sudo systemctl stop postgresql-14`);
1175
+ shellExec(`sudo systemctl disable postgresql-14`);
1176
+
1177
+ shellExec(`sudo dnf remove -y postgresql14 postgresql14-server postgresql14-contrib`);
1178
+ shellExec(`sudo rm -rf /var/lib/pgsql`);
1179
+
1180
+ shellExec(`sudo dnf install postgresql14 postgresql14-server postgresql14-contrib -y`);
1181
+ }
1182
+ if (process.argv.includes('uninstall')) {
1183
+ shellExec(`sudo systemctl stop postgresql-14`);
1184
+ shellExec(`sudo systemctl disable postgresql-14`);
1185
+ shellExec(`sudo dnf remove -y postgresql14 postgresql14-server postgresql14-contrib`);
1186
+ shellExec(`sudo rm -rf /var/lib/pgsql /var/log/pgsql /etc/postgresql`);
1187
+ } else {
1188
+ shellExec(`sudo /usr/pgsql-14/bin/postgresql-14-setup initdb`);
1189
+ shellExec(`sudo systemctl start postgresql-14`);
1190
+ shellExec(`sudo systemctl enable postgresql-14`);
1191
+ shellExec(`sudo systemctl status postgresql-14`);
1192
+ // sudo dnf install postgresql14-contrib
1193
+ }
1194
+
1199
1195
  break;
1200
1196
  }
1201
1197
 
@@ -1231,592 +1227,6 @@ EOF`);
1231
1227
  break;
1232
1228
  }
1233
1229
 
1234
- case 'maas': {
1235
- dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1236
- const IP_ADDRESS = getLocalIPv4Address();
1237
- const serverip = IP_ADDRESS;
1238
- const tftpRoot = process.env.TFTP_ROOT;
1239
- const ipaddr = process.env.RPI4_IP;
1240
- const netmask = process.env.NETMASK;
1241
- const gatewayip = process.env.GATEWAY_IP;
1242
-
1243
- let resources;
1244
- try {
1245
- resources = JSON.parse(
1246
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-resources read`, {
1247
- silent: true,
1248
- stdout: true,
1249
- }),
1250
- ).map((o) => ({
1251
- id: o.id,
1252
- name: o.name,
1253
- architecture: o.architecture,
1254
- }));
1255
- } catch (error) {
1256
- logger.error(error);
1257
- }
1258
-
1259
- const machineFactory = (m) => ({
1260
- system_id: m.interface_set[0].system_id,
1261
- mac_address: m.interface_set[0].mac_address,
1262
- hostname: m.hostname,
1263
- status_name: m.status_name,
1264
- });
1265
-
1266
- let machines;
1267
- try {
1268
- machines = JSON.parse(
1269
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machines read`, {
1270
- stdout: true,
1271
- silent: true,
1272
- }),
1273
- ).map((m) => machineFactory(m));
1274
- } catch (error) {
1275
- logger.error(error);
1276
- }
1277
-
1278
- if (process.argv.includes('db')) {
1279
- // DROP, ALTER, CREATE, WITH ENCRYPTED
1280
- // sudo -u <user> -h <host> psql <db-name>
1281
- shellExec(`DB_PG_MAAS_NAME=${process.env.DB_PG_MAAS_NAME}`);
1282
- shellExec(`DB_PG_MAAS_PASS=${process.env.DB_PG_MAAS_PASS}`);
1283
- shellExec(`DB_PG_MAAS_USER=${process.env.DB_PG_MAAS_USER}`);
1284
- shellExec(`DB_PG_MAAS_HOST=${process.env.DB_PG_MAAS_HOST}`);
1285
- shellExec(
1286
- `sudo -i -u postgres psql -c "CREATE USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1287
- );
1288
- shellExec(
1289
- `sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1290
- );
1291
- const actions = ['LOGIN', 'SUPERUSER', 'INHERIT', 'CREATEDB', 'CREATEROLE', 'REPLICATION'];
1292
- shellExec(`sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ${actions.join(' ')}"`);
1293
- shellExec(`sudo -i -u postgres psql -c "\\du"`);
1294
-
1295
- shellExec(`sudo -i -u postgres createdb -O "$DB_PG_MAAS_USER" "$DB_PG_MAAS_NAME"`);
1296
-
1297
- shellExec(`sudo -i -u postgres psql -c "\\l"`);
1298
- }
1299
-
1300
- if (process.argv.includes('ls')) {
1301
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-sources read`);
1302
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} commissioning-scripts read`);
1303
- // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-source-selections read 60`);
1304
- console.table(resources);
1305
- console.table(machines);
1306
- process.exit(0);
1307
- }
1308
-
1309
- // TODO: - Disable maas proxy (egress forwarding to public dns)
1310
- // - Configure maas dns forwarding ${process.env.MAAS_DNS}
1311
- // - Enable DNSSEC validation of upstream zones: Automatic (use default root key)
1312
-
1313
- if (process.argv.includes('clear')) {
1314
- for (const machine of machines) {
1315
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine delete ${machine.system_id}`);
1316
- }
1317
- // machines = [];
1318
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries clear all=true`);
1319
- if (process.argv.includes('force')) {
1320
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries scan force=true`);
1321
- }
1322
- process.exit(0);
1323
- }
1324
- if (process.argv.includes('grub-arm64')) {
1325
- shellExec(`sudo dnf install grub2-efi-aa64-modules`);
1326
- shellExec(`sudo dnf install grub2-efi-x64-modules`);
1327
- // sudo grub2-mknetdir --net-directory=${tftpRoot} --subdir=/boot/grub --module-path=/usr/lib/grub/arm64-efi arm64-efi
1328
- process.exit(0);
1329
- }
1330
-
1331
- if (process.argv.includes('psql')) {
1332
- const cmd = `psql -U ${process.env.DB_PG_MAAS_USER} -h ${process.env.DB_PG_MAAS_HOST} -W ${process.env.DB_PG_MAAS_NAME}`;
1333
- pbcopy(cmd);
1334
- process.exit(0);
1335
- }
1336
- if (process.argv.includes('logs')) {
1337
- shellExec(`maas status`);
1338
- const cmd = `journalctl -f -t dhcpd -u snap.maas.pebble.service`;
1339
- pbcopy(cmd);
1340
- process.exit(0);
1341
- }
1342
- if (process.argv.includes('reset')) {
1343
- // shellExec(
1344
- // `maas init region+rack --database-uri "postgres://$DB_PG_MAAS_USER:$DB_PG_MAAS_PASS@$DB_PG_MAAS_HOST/$DB_PG_MAAS_NAME"` +
1345
- // ` --maas-url http://${IP_ADDRESS}:5240/MAAS`,
1346
- // );
1347
- const cmd =
1348
- `maas init region+rack --database-uri "postgres://${process.env.DB_PG_MAAS_USER}:${process.env.DB_PG_MAAS_PASS}@${process.env.DB_PG_MAAS_HOST}/${process.env.DB_PG_MAAS_NAME}"` +
1349
- ` --maas-url http://${IP_ADDRESS}:5240/MAAS`;
1350
- pbcopy(cmd);
1351
- process.exit(0);
1352
- }
1353
- if (process.argv.includes('dhcp')) {
1354
- const snippets = JSON.parse(
1355
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} dhcpsnippets read`, {
1356
- stdout: true,
1357
- silent: true,
1358
- disableLog: true,
1359
- }),
1360
- );
1361
- for (const snippet of snippets) {
1362
- switch (snippet.name) {
1363
- case 'arm64':
1364
- snippet.value = snippet.value.split(`\n`);
1365
- snippet.value[1] = ` filename "http://${IP_ADDRESS}:5248/images/bootloaders/uefi/arm64/grubaa64.efi";`;
1366
- snippet.value[5] = ` filename "http://${IP_ADDRESS}:5248/images/bootloaders/uefi/arm64/grubaa64.efi";`;
1367
- snippet.value = snippet.value.join(`\n`);
1368
- shellExec(
1369
- `maas ${process.env.MAAS_ADMIN_USERNAME} dhcpsnippet update ${snippet.name} value='${snippet.value}'`,
1370
- );
1371
- break;
1372
-
1373
- default:
1374
- break;
1375
- }
1376
- }
1377
-
1378
- console.log(snippets);
1379
-
1380
- process.exit(0);
1381
- }
1382
- // shellExec(`MAAS_ADMIN_USERNAME=${process.env.MAAS_ADMIN_USERNAME}`);
1383
- // shellExec(`MAAS_ADMIN_EMAIL=${process.env.MAAS_ADMIN_EMAIL}`);
1384
- // shellExec(`maas createadmin --username $MAAS_ADMIN_USERNAME --email $MAAS_ADMIN_EMAIL`);
1385
-
1386
- // MaaS admin CLI:
1387
- // maas login <maas-admin-username> http://localhost:5240/MAAS
1388
- // paste GUI API KEY (profile section)
1389
-
1390
- // Import custom image
1391
- // maas <maas-admin-username> boot-resources create name='custom/RockyLinuxRpi4' \
1392
- // title='RockyLinuxRpi4' \
1393
- // architecture='arm64/generic' \
1394
- // filetype='tgz' \
1395
- // content@=/home/RockyLinuxRpi_9-latest.tar.gz
1396
-
1397
- // Image boot resource:
1398
- // /var/snap/maas/current/root/snap/maas
1399
- // /var/snap/maas/common/maas/tftp_root
1400
- // sudo chmod 755 /var/snap/maas/common/maas/tftp_root
1401
-
1402
- // /var/snap/maas/common/maas/dhcpd.conf
1403
- // sudo snap restart maas.pebble
1404
-
1405
- // PXE Linux files:
1406
- // /var/snap/maas/common/maas/image-storage/bootloaders/pxe/i386
1407
- // sudo nmcli con modify <interface-device-name-connection-id> ethtool.feature-rx on ethtool.feature-tx off
1408
- // sudo nmcli connection up <interface-device-name-connection-id>
1409
-
1410
- // man nm-settings |grep feature-tx-checksum
1411
-
1412
- // nmcli c modify <interface-device-name-connection-id> \
1413
- // ethtool.feature-tx-checksum-fcoe-crc off \
1414
- // ethtool.feature-tx-checksum-ip-generic off \
1415
- // ethtool.feature-tx-checksum-ipv4 off \
1416
- // ethtool.feature-tx-checksum-ipv6 off \
1417
- // ethtool.feature-tx-checksum-sctp off
1418
-
1419
- // Ensure Rocky NFS server and /etc/exports configured
1420
- // sudo systemctl restart nfs-server
1421
- // Check mounts: showmount -e <server-ip>
1422
- // Check nfs ports: rpcinfo -p
1423
- // sudo chown -R root:root ${process.env.NFS_EXPORT_PATH}/rpi4mb
1424
- // sudo chmod 755 ${process.env.NFS_EXPORT_PATH}/rpi4mb
1425
-
1426
- // tftp server
1427
- // sudo chown -R root:root /var/snap/maas/common/maas/tftp_root/rpi4mb
1428
-
1429
- // tftp client
1430
- // sudo dnf install tftp
1431
- // tftp <server-ip> -c get <path>
1432
-
1433
- // Check firewall-cmd
1434
- // firewall-cmd --permanent --add-service=rpc-bind
1435
- // firewall-cmd --reload
1436
- // systemctl disable firewalld
1437
- // sudo firewall-cmd --permanent --add-port=10259/tcp --zone=public
1438
-
1439
- // Image extension transform (.img.xz to .tar.gz):
1440
- // tar -cvzf image-name.tar.gz image-name.img.xz
1441
-
1442
- // Rocky network configuration:
1443
- // /etc/NetworkManager/system-connections
1444
-
1445
- // Rocky kernel params update
1446
- // sudo grubby --args="<key>=<value> <key>=<value>" --update-kernel=ALL
1447
- // sudo reboot now
1448
-
1449
- // Temporal:
1450
- // sudo snap install temporal
1451
- // journalctl -u snap.maas.pebble -t maas-regiond
1452
- // journalctl -u snap.maas.pebble -t maas-temporal -n 100 --no-pager -f
1453
-
1454
- // Remove:
1455
- // sudo dnf remove <package> -y; sudo dnf autoremove -y; sudo dnf clean packages
1456
- // check: ~
1457
- // check: ~./cache
1458
- // check: ~./config
1459
-
1460
- // Check file logs
1461
- // grep -i -E -C 1 '<key-a>|<key-b>' /example.log | tail -n 600
1462
-
1463
- // Back into your firmware setup (UEFI or BIOS config screen).
1464
- // grub> fwsetup
1465
-
1466
- // Poweroff:
1467
- // grub > halt
1468
- // initramfs > poweroff
1469
-
1470
- // Check interface
1471
- // ip link show
1472
- // nmcli con show
1473
-
1474
- let firmwarePath,
1475
- tftpSubDir,
1476
- kernelFilesPaths,
1477
- name,
1478
- architecture,
1479
- resource,
1480
- nfsConnectStr,
1481
- etcExports,
1482
- nfsServerRootPath,
1483
- bootConf,
1484
- zipFirmwareFileName,
1485
- zipFirmwareName,
1486
- zipFirmwareUrl,
1487
- interfaceName,
1488
- nfsHost;
1489
-
1490
- switch (process.argv[3]) {
1491
- case 'rpi4mb':
1492
- const resourceId = process.argv[4] ?? '39';
1493
- tftpSubDir = '/rpi4mb';
1494
- zipFirmwareFileName = `RPi4_UEFI_Firmware_v1.41.zip`;
1495
- zipFirmwareName = zipFirmwareFileName.split('.zip')[0];
1496
- zipFirmwareUrl = `https://github.com/pftf/RPi4/releases/download/v1.41/RPi4_UEFI_Firmware_v1.41.zip`;
1497
- firmwarePath = `../${zipFirmwareName}`;
1498
- interfaceName = process.env.RPI4_INTERFACE_NAME;
1499
- nfsHost = 'rpi4mb';
1500
- if (!fs.existsSync(firmwarePath)) {
1501
- await Downloader(zipFirmwareUrl, `../${zipFirmwareFileName}`);
1502
- shellExec(`cd .. && mkdir ${zipFirmwareName} && cd ${zipFirmwareName} && unzip ../${zipFirmwareFileName}`);
1503
- }
1504
- resource = resources.find((o) => o.id == resourceId);
1505
- name = resource.name;
1506
- architecture = resource.architecture;
1507
- resource = resources.find((o) => o.name === name && o.architecture === architecture);
1508
- nfsServerRootPath = `${process.env.NFS_EXPORT_PATH}/rpi4mb`;
1509
- // ,anonuid=1001,anongid=100
1510
- // etcExports = `${nfsServerRootPath} *(rw,all_squash,sync,no_root_squash,insecure)`;
1511
- etcExports = `${nfsServerRootPath} 192.168.1.0/24(${[
1512
- 'rw',
1513
- // 'all_squash',
1514
- 'sync',
1515
- 'no_root_squash',
1516
- 'no_subtree_check',
1517
- 'insecure',
1518
- ]})`;
1519
- const resourceData = JSON.parse(
1520
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-resource read ${resource.id}`, {
1521
- stdout: true,
1522
- silent: true,
1523
- disableLog: true,
1524
- }),
1525
- );
1526
- const bootFiles = resourceData.sets[Object.keys(resourceData.sets)[0]].files;
1527
- const suffix = architecture.match('xgene') ? '.xgene' : '';
1528
-
1529
- kernelFilesPaths = {
1530
- 'vmlinuz-efi': bootFiles['boot-kernel' + suffix].filename_on_disk,
1531
- 'initrd.img': bootFiles['boot-initrd' + suffix].filename_on_disk,
1532
- squashfs: bootFiles['squashfs'].filename_on_disk,
1533
- };
1534
- const protocol = 'tcp'; // v3 -> tcp, v4 -> udp
1535
-
1536
- const mountOptions = [
1537
- protocol,
1538
- 'vers=3',
1539
- 'nfsvers=3',
1540
- 'nolock',
1541
- // 'protocol=tcp',
1542
- // 'hard=true',
1543
- 'port=2049',
1544
- // 'sec=none',
1545
- 'rw',
1546
- 'hard',
1547
- 'intr',
1548
- 'rsize=32768',
1549
- 'wsize=32768',
1550
- 'acregmin=0',
1551
- 'acregmax=0',
1552
- 'acdirmin=0',
1553
- 'acdirmax=0',
1554
- 'noac',
1555
- // 'nodev',
1556
- // 'nosuid',
1557
- ];
1558
- const cmd = [
1559
- `console=serial0,115200`,
1560
- `console=tty1`,
1561
- // `initrd=-1`,
1562
- // `net.ifnames=0`,
1563
- // `dwc_otg.lpm_enable=0`,
1564
- // `elevator=deadline`,
1565
- `root=/dev/nfs`,
1566
- `nfsroot=${serverip}:${process.env.NFS_EXPORT_PATH}/rpi4mb,${mountOptions}`,
1567
- // `nfsroot=${serverip}:${process.env.NFS_EXPORT_PATH}/rpi4mb`,
1568
- `ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${nfsHost}:${interfaceName}:static`,
1569
- `rootfstype=nfs`,
1570
- `rw`,
1571
- `rootwait`,
1572
- `fixrtc`,
1573
- 'initrd=initrd.img',
1574
- // 'boot=casper',
1575
- // 'ro',
1576
- 'netboot=nfs',
1577
- `cloud-config-url=/dev/null`,
1578
- // 'ip=dhcp',
1579
- // 'ip=dfcp',
1580
- // 'autoinstall',
1581
- // 'rd.break',
1582
- ];
1583
-
1584
- nfsConnectStr = cmd.join(' ');
1585
- bootConf = `[all]
1586
- MAC_ADDRESS=00:00:00:00:00:00
1587
- MAC_ADDRESS_OTP=0,1
1588
- BOOT_UART=0
1589
- WAKE_ON_GPIO=1
1590
- POWER_OFF_ON_HALT=0
1591
- ENABLE_SELF_UPDATE=1
1592
- DISABLE_HDMI=0
1593
- TFTP_IP=${serverip}
1594
- TFTP_PREFIX=1
1595
- TFTP_PREFIX_STR=${tftpSubDir.slice(1)}/
1596
- NET_INSTALL_ENABLED=1
1597
- DHCP_TIMEOUT=45000
1598
- DHCP_REQ_TIMEOUT=4000
1599
- TFTP_FILE_TIMEOUT=30000
1600
- BOOT_ORDER=0x21`;
1601
-
1602
- break;
1603
-
1604
- default:
1605
- break;
1606
- }
1607
- shellExec(`sudo chmod 755 ${process.env.NFS_EXPORT_PATH}/${nfsHost}`);
1608
-
1609
- shellExec(`sudo rm -rf ${tftpRoot}${tftpSubDir}`);
1610
- shellExec(`sudo cp -a ${firmwarePath} ${tftpRoot}${tftpSubDir}`);
1611
- shellExec(`mkdir -p ${tftpRoot}${tftpSubDir}/pxe`);
1612
-
1613
- fs.writeFileSync(`/etc/exports`, etcExports, 'utf8');
1614
- if (bootConf) fs.writeFileSync(`${tftpRoot}${tftpSubDir}/boot.conf`, bootConf, 'utf8');
1615
-
1616
- shellExec(`node bin/deploy nfs`);
1617
-
1618
- if (process.argv.includes('restart')) {
1619
- shellExec(`sudo snap restart maas.pebble`);
1620
- let secs = 0;
1621
- while (
1622
- !(
1623
- shellExec(`maas status`, { silent: true, disableLog: true, stdout: true })
1624
- .split(' ')
1625
- .filter((l) => l.match('inactive')).length === 1
1626
- )
1627
- ) {
1628
- await timer(1000);
1629
- console.log(`Waiting... (${++secs}s)`);
1630
- }
1631
- }
1632
-
1633
- switch (process.argv[3]) {
1634
- case 'rpi4mb':
1635
- {
1636
- // subnet DHCP snippets
1637
- // # UEFI ARM64
1638
- // if option arch = 00:0B {
1639
- // filename "rpi4mb/pxe/grubaa64.efi";
1640
- // }
1641
- // elsif option arch = 00:13 {
1642
- // filename "http://<IP_ADDRESS>:5248/images/bootloaders/uefi/arm64/grubaa64.efi";
1643
- // option vendor-class-identifier "HTTPClient";
1644
- // }
1645
- for (const file of ['bootaa64.efi', 'grubaa64.efi']) {
1646
- shellExec(
1647
- `sudo cp -a /var/snap/maas/common/maas/image-storage/bootloaders/uefi/arm64/${file} ${tftpRoot}${tftpSubDir}/pxe/${file}`,
1648
- );
1649
- }
1650
- // const file = 'bcm2711-rpi-4-b.dtb';
1651
- // shellExec(
1652
- // `sudo cp -a ${firmwarePath}/${file} /var/snap/maas/common/maas/image-storage/bootloaders/uefi/arm64/${file}`,
1653
- // );
1654
-
1655
- // const ipxeSrc = fs
1656
- // .readFileSync(`${tftpRoot}/ipxe.cfg`, 'utf8')
1657
- // .replaceAll('amd64', 'arm64')
1658
- // .replaceAll('${next-server}', IP_ADDRESS);
1659
- // fs.writeFileSync(`${tftpRoot}/ipxe.cfg`, ipxeSrc, 'utf8');
1660
-
1661
- {
1662
- for (const file of Object.keys(kernelFilesPaths)) {
1663
- shellExec(
1664
- `sudo cp -a /var/snap/maas/common/maas/image-storage/${kernelFilesPaths[file]} ${tftpRoot}${tftpSubDir}/pxe/${file}`,
1665
- );
1666
- }
1667
- // const configTxtSrc = fs.readFileSync(`${firmwarePath}/config.txt`, 'utf8');
1668
- // fs.writeFileSync(
1669
- // `${tftpRoot}${tftpSubDir}/config.txt`,
1670
- // configTxtSrc
1671
- // .replace(`kernel=kernel8.img`, `kernel=vmlinuz`)
1672
- // .replace(`# max_framebuffers=2`, `max_framebuffers=2`)
1673
- // .replace(`initramfs initramfs8 followkernel`, `initramfs initrd.img followkernel`),
1674
- // 'utf8',
1675
- // );
1676
-
1677
- // grub:
1678
- // set root=(pxe)
1679
-
1680
- // UNDERPOST.NET UEFI/GRUB/MAAS RPi4 commissioning (ARM64)
1681
- const menuentryStr = 'underpost.net rpi4mb maas commissioning (ARM64)';
1682
- const grubCfgPath = `${tftpRoot}/grub/grub.cfg`;
1683
- fs.writeFileSync(
1684
- grubCfgPath,
1685
- `
1686
- insmod gzio
1687
- insmod http
1688
- insmod nfs
1689
- set timeout=5
1690
- set default=0
1691
-
1692
- menuentry '${menuentryStr}' {
1693
- set root=(tftp,${serverip})
1694
- linux ${tftpSubDir}/pxe/vmlinuz-efi ${nfsConnectStr}
1695
- initrd ${tftpSubDir}/pxe/initrd.img
1696
- boot
1697
- }
1698
-
1699
- `,
1700
- 'utf8',
1701
- );
1702
- }
1703
- const arm64EfiPath = `${tftpRoot}/grub/arm64-efi`;
1704
- if (fs.existsSync(arm64EfiPath)) shellExec(`sudo rm -rf ${arm64EfiPath}`);
1705
- shellExec(`sudo cp -a /usr/lib/grub/arm64-efi ${arm64EfiPath}`);
1706
- }
1707
-
1708
- break;
1709
-
1710
- default:
1711
- break;
1712
- }
1713
-
1714
- logger.info('succes maas deploy', {
1715
- resource,
1716
- kernelFilesPaths,
1717
- tftpRoot,
1718
- tftpSubDir,
1719
- firmwarePath,
1720
- etcExports,
1721
- nfsServerRootPath,
1722
- nfsConnectStr,
1723
- });
1724
- if (process.argv.includes('restart')) {
1725
- if (fs.existsSync(`node engine-private/r.js`)) shellExec(`node engine-private/r`);
1726
- shellExec(`node bin/deploy maas dhcp`);
1727
- shellExec(`sudo chown -R root:root ${tftpRoot}`);
1728
- shellExec(`sudo sudo chmod 755 ${tftpRoot}`);
1729
- }
1730
- // for (const machine of machines) {
1731
- // // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine delete ${machine.system_id}`);
1732
- // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine commission ${machine.system_id}`, {
1733
- // silent: true,
1734
- // });
1735
- // }
1736
- // machines = [];
1737
-
1738
- const monitor = async () => {
1739
- // discoveries Query observed discoveries.
1740
- // discovery Read or delete an observed discovery.
1741
-
1742
- const discoveries = JSON.parse(
1743
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries read`, {
1744
- silent: true,
1745
- stdout: true,
1746
- }),
1747
- ).filter(
1748
- (o) => o.ip !== IP_ADDRESS && o.ip !== gatewayip && !machines.find((_o) => _o.mac_address === o.mac_address),
1749
- );
1750
-
1751
- // {
1752
- // "discovery_id": "",
1753
- // "ip": "192.168.1.189",
1754
- // "mac_address": "00:00:00:00:00:00",
1755
- // "last_seen": "2025-05-05T14:17:37.354",
1756
- // "hostname": null,
1757
- // "fabric_name": "",
1758
- // "vid": null,
1759
- // "mac_organization": "",
1760
- // "observer": {
1761
- // "system_id": "",
1762
- // "hostname": "",
1763
- // "interface_id": 1,
1764
- // "interface_name": ""
1765
- // },
1766
- // "resource_uri": "/MAAS/api/2.0/discovery/MTkyLjE2OC4xLjE4OSwwMDowMDowMDowMDowMDowMA==/"
1767
- // },
1768
-
1769
- for (const discovery of discoveries) {
1770
- const machine = {
1771
- architecture: architecture.match('amd') ? 'amd64/generic' : 'arm64/generic',
1772
- mac_address: discovery.mac_address,
1773
- hostname: discovery.hostname ?? discovery.mac_organization ?? discovery.domain ?? `generic-host-${s4()}`,
1774
- // discovery.ip.match(ipaddr)
1775
- // ? nfsHost
1776
- // : `unknown-${s4()}`,
1777
- // description: '',
1778
- // https://maas.io/docs/reference-power-drivers
1779
- power_type: 'manual', // manual
1780
- // power_parameters_power_address: discovery.ip,
1781
- mac_addresses: discovery.mac_address,
1782
- };
1783
- machine.hostname = machine.hostname.replaceAll(' ', '').replaceAll('.', '');
1784
-
1785
- try {
1786
- let newMachine = shellExec(
1787
- `maas ${process.env.MAAS_ADMIN_USERNAME} machines create ${Object.keys(machine)
1788
- .map((k) => `${k}="${machine[k]}"`)
1789
- .join(' ')}`,
1790
- {
1791
- silent: true,
1792
- stdout: true,
1793
- },
1794
- );
1795
- newMachine = machineFactory(JSON.parse(newMachine));
1796
- machines.push(newMachine);
1797
- console.log(newMachine);
1798
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine commission ${newMachine.system_id}`, {
1799
- silent: true,
1800
- });
1801
- } catch (error) {
1802
- logger.error(error, error.stack);
1803
- }
1804
- }
1805
- // if (discoveries.length > 0) {
1806
- // shellExec(
1807
- // `maas ${process.env.MAAS_ADMIN_USERNAME} machines read | jq '.[] | {system_id: .interface_set[0].system_id, hostname, status_name, mac_address: .interface_set[0].mac_address}'`,
1808
- // );
1809
- // }
1810
- await timer(1000);
1811
- monitor();
1812
- };
1813
- // shellExec(`node bin/deploy open-virtual-root ${architecture.match('amd') ? 'amd64' : 'arm64'} ${nfsHost}`);
1814
- machines = [];
1815
- shellExec(`node bin/deploy maas clear`);
1816
- monitor();
1817
- break;
1818
- }
1819
-
1820
1230
  case 'nfs': {
1821
1231
  // Daemon RPC NFSv3. ports:
1822
1232
 
@@ -1892,129 +1302,6 @@ udp-port = 32766
1892
1302
  shellExec(`sudo systemctl restart nfs-server`);
1893
1303
  break;
1894
1304
  }
1895
- case 'update-virtual-root': {
1896
- dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1897
- const IP_ADDRESS = getLocalIPv4Address();
1898
- const architecture = process.argv[3];
1899
- const host = process.argv[4];
1900
- const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
1901
- const ipaddr = process.env.RPI4_IP;
1902
- await updateVirtualRoot({
1903
- IP_ADDRESS,
1904
- architecture,
1905
- host,
1906
- nfsHostPath,
1907
- ipaddr,
1908
- });
1909
- break;
1910
- }
1911
- case 'open-virtual-root': {
1912
- dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1913
- const IP_ADDRESS = getLocalIPv4Address();
1914
- const architecture = process.argv[3];
1915
- const host = process.argv[4];
1916
- const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
1917
- shellExec(`sudo dnf install -y iptables-legacy`);
1918
- shellExec(`sudo dnf install -y debootstrap`);
1919
- shellExec(`sudo dnf install kernel-modules-extra-$(uname -r)`);
1920
- switch (architecture) {
1921
- case 'arm64':
1922
- shellExec(`sudo podman run --rm --privileged multiarch/qemu-user-static --reset -p yes`);
1923
-
1924
- break;
1925
-
1926
- default:
1927
- break;
1928
- }
1929
-
1930
- shellExec(`sudo modprobe binfmt_misc`);
1931
- shellExec(`sudo mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc`);
1932
-
1933
- if (process.argv.includes('build')) {
1934
- // shellExec(`depmod -a`);
1935
- shellExec(`mkdir -p ${nfsHostPath}`);
1936
- let cmd;
1937
- switch (host) {
1938
- case 'rpi4mb':
1939
- shellExec(`sudo rm -rf ${nfsHostPath}/*`);
1940
- shellExec(`sudo chown -R root:root ${nfsHostPath}`);
1941
- cmd = [
1942
- `sudo debootstrap`,
1943
- `--arch=arm64`,
1944
- `--variant=minbase`,
1945
- `--foreign`, // arm64 on amd64
1946
- `noble`,
1947
- nfsHostPath,
1948
- `http://ports.ubuntu.com/ubuntu-ports/`,
1949
- ];
1950
- break;
1951
-
1952
- default:
1953
- break;
1954
- }
1955
- shellExec(cmd.join(' '));
1956
-
1957
- shellExec(`sudo podman create --name extract multiarch/qemu-user-static`);
1958
- shellExec(`podman ps -a`);
1959
- shellExec(`sudo podman cp extract:/usr/bin/qemu-aarch64-static ${nfsHostPath}/usr/bin/`);
1960
- shellExec(`sudo podman rm extract`);
1961
- shellExec(`podman ps -a`);
1962
-
1963
- switch (host) {
1964
- case 'rpi4mb':
1965
- shellExec(`file ${nfsHostPath}/bin/bash`); // expected: ELF 64-bit LSB pie executable, ARM aarch64 …
1966
- break;
1967
-
1968
- default:
1969
- break;
1970
- }
1971
-
1972
- shellExec(`sudo chroot ${nfsHostPath} /usr/bin/qemu-aarch64-static /bin/bash <<'EOF'
1973
- /debootstrap/debootstrap --second-stage
1974
- EOF`);
1975
- }
1976
- if (process.argv.includes('mount')) {
1977
- shellExec(`sudo mount --bind /proc ${nfsHostPath}/proc`);
1978
- shellExec(`sudo mount --bind /sys ${nfsHostPath}/sys`);
1979
- shellExec(`sudo mount --rbind /dev ${nfsHostPath}/dev`);
1980
- }
1981
-
1982
- if (process.argv.includes('build')) {
1983
- switch (host) {
1984
- case 'rpi4mb':
1985
- const ipaddr = process.env.RPI4_IP;
1986
-
1987
- await updateVirtualRoot({
1988
- IP_ADDRESS,
1989
- architecture,
1990
- host,
1991
- nfsHostPath,
1992
- ipaddr,
1993
- });
1994
-
1995
- break;
1996
-
1997
- default:
1998
- break;
1999
- }
2000
- }
2001
- // if (process.argv.includes('mount')) {
2002
- // shellExec(`sudo mount --bind /lib/modules ${nfsHostPath}/lib/modules`);
2003
- // }
2004
-
2005
- break;
2006
- }
2007
-
2008
- case 'close-virtual-root': {
2009
- const architecture = process.argv[3];
2010
- const host = process.argv[4];
2011
- const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
2012
- shellExec(`sudo umount ${nfsHostPath}/proc`);
2013
- shellExec(`sudo umount ${nfsHostPath}/sys`);
2014
- shellExec(`sudo umount ${nfsHostPath}/dev`);
2015
- // shellExec(`sudo umount ${nfsHostPath}/lib/modules`);
2016
- break;
2017
- }
2018
1305
 
2019
1306
  case 'mount': {
2020
1307
  const mounts = shellExec(`mount`).split(`\n`);
@@ -2037,10 +1324,10 @@ EOF`);
2037
1324
 
2038
1325
  case 'create-ports': {
2039
1326
  const cmd = [];
2040
- const ipaddr = getLocalIPv4Address();
1327
+ const commissioningDeviceIp = getLocalIPv4Address();
2041
1328
  for (const port of ['5240']) {
2042
1329
  const name = 'maas';
2043
- cmd.push(`${name}:${port}-${port}:${ipaddr}`);
1330
+ cmd.push(`${name}:${port}-${port}:${commissioningDeviceIp}`);
2044
1331
  }
2045
1332
  pbcopy(`node engine-private/r create-port ${cmd}`);
2046
1333
  break;
@@ -2220,7 +1507,7 @@ EOF`);
2220
1507
  const args = [
2221
1508
  `node bin dockerfile-image-build --path ${path}/backend/`,
2222
1509
  `--image-name=${imageName} --image-path=${path}`,
2223
- `--podman-save --${process.argv.includes('kubeadm') ? 'kubeadm' : 'kind'}-load --no-cache`,
1510
+ `--podman-save --${process.argv.includes('kubeadm') ? 'kubeadm' : 'kind'}-load --reset`,
2224
1511
  ];
2225
1512
  shellExec(args.join(' '));
2226
1513
  }
@@ -2232,7 +1519,7 @@ EOF`);
2232
1519
  const args = [
2233
1520
  `node bin dockerfile-image-build --path ${path}/frontend/`,
2234
1521
  `--image-name=${imageName} --image-path=${path}`,
2235
- `--podman-save --${process.argv.includes('kubeadm') ? 'kubeadm' : 'kind'}-load --no-cache`,
1522
+ `--podman-save --${process.argv.includes('kubeadm') ? 'kubeadm' : 'kind'}-load --reset`,
2236
1523
  ];
2237
1524
  shellExec(args.join(' '));
2238
1525
  }
@@ -2405,6 +1692,14 @@ nvidia/gpu-operator \
2405
1692
  // sudo yum install sbt
2406
1693
  break;
2407
1694
  }
1695
+
1696
+ case 'chrony': {
1697
+ shellExec(`sudo dnf install chrony -y`);
1698
+ // debian chroot: sudo apt install chrony
1699
+ for (const cmd of chronySetUp(`/etc/chrony.conf`)) shellExec(cmd);
1700
+
1701
+ break;
1702
+ }
2408
1703
  }
2409
1704
  } catch (error) {
2410
1705
  logger.error(error, error.stack);