underpost 2.8.818 → 2.8.832

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/bin/deploy.js CHANGED
@@ -51,585 +51,6 @@ logger.info('argv', process.argv);
51
51
 
52
52
  const [exe, dir, operator] = process.argv;
53
53
 
54
- const chronyConfPath = `/etc/chrony/chrony.conf`;
55
-
56
- const timezone = 'America/New_York';
57
-
58
- const timeZoneSteps = [
59
- `export DEBIAN_FRONTEND=noninteractive`,
60
-
61
- `ln -fs /usr/share/zoneinfo/${timezone} /etc/localtime`,
62
-
63
- `sudo dpkg-reconfigure --frontend noninteractive tzdata`,
64
- ];
65
- const keyboardSteps = [
66
- `sudo locale-gen en_US.UTF-8`,
67
- `sudo update-locale LANG=en_US.UTF-8`,
68
- `sudo sed -i 's/XKBLAYOUT="us"/XKBLAYOUT="es"/' /etc/default/keyboard`,
69
- `sudo dpkg-reconfigure --frontend noninteractive keyboard-configuration`,
70
- `sudo systemctl restart keyboard-setup.service`,
71
- ];
72
-
73
- const kernelLibVersion = `6.8.0-41-generic`;
74
-
75
- const installSteps = [
76
- `cat <<EOF | tee /etc/apt/sources.list
77
- deb http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse
78
- deb http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse
79
- deb http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse
80
- EOF`,
81
-
82
- `apt update -qq`,
83
- `apt -y full-upgrade`,
84
- `apt install -y build-essential xinput x11-xkb-utils usbutils`,
85
- 'apt install -y linux-image-generic',
86
- `apt install -y linux-modules-${kernelLibVersion} linux-modules-extra-${kernelLibVersion}`,
87
-
88
- `depmod -a ${kernelLibVersion}`,
89
- // `apt install -y cloud-init=25.1.2-0ubuntu0~24.04.1`,
90
- `apt install -y cloud-init systemd-sysv openssh-server sudo locales udev util-linux systemd-sysv iproute2 netplan.io ca-certificates curl wget chrony`,
91
- `ln -sf /lib/systemd/systemd /sbin/init`,
92
-
93
- `apt-get update`,
94
- `DEBIAN_FRONTEND=noninteractive apt-get install -y apt-utils`,
95
- `DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata kmod keyboard-configuration console-setup iputils-ping`,
96
- ];
97
-
98
- const bootCmdSteps = [
99
- `/underpost/dns.sh`,
100
- `/underpost/host.sh`,
101
- // `/underpost/date.sh`,
102
- `/underpost/keys_import.sh`,
103
- `/underpost/mac.sh`,
104
- `cat /underpost/mac`,
105
- ];
106
-
107
- const cloudInitReset = `sudo cloud-init clean --logs --seed --configs all --machine-id
108
- sudo rm -rf /var/lib/cloud/*`;
109
-
110
- const cloudConfigCmdRunFactory = (steps = []) =>
111
- steps
112
- .map(
113
- (step, i, a) =>
114
- ' - echo "\\$(date) | ' + (i + 1) + '/' + a.length + ' - ' + step.split('\n')[0] + '"' + `\n` + ` - ${step}`,
115
- )
116
- .join('\n');
117
-
118
- const cloudConfigFactory = (
119
- { controlServerIp, architecture, host, nfsHostPath, commissioningDeviceIp, update, gatewayip, auth },
120
- { consumer_key, consumer_secret, token_key, token_secret },
121
- path = '/etc/cloud/cloud.cfg.d/90_maas.cfg',
122
- ) => [
123
- // Configure cloud-init for MAAS
124
- `cat <<EOF_MAAS_CFG > ${path}
125
- #cloud-config
126
-
127
- hostname: ${host}
128
- # fqdn: server01.midominio.cl
129
- # prefer_fqdn_over_hostname: true
130
- # metadata_url: http://${controlServerIp}:5240/MAAS/metadata
131
- # metadata_url: http://${controlServerIp}:5248/MAAS/metadata
132
-
133
- # Check:
134
- # /MAAS/metadata/latest/enlist-preseed/?op=get_enlist_preseed
135
-
136
- # Debug:
137
- # https://maas.io/docs/how-to-use-logging
138
-
139
- datasource_list: [ MAAS ]
140
- datasource:
141
- MAAS:
142
- metadata_url: http://${controlServerIp}:5240/MAAS/metadata/
143
- ${
144
- !auth
145
- ? ''
146
- : `consumer_key: ${consumer_key}
147
- consumer_secret: ${consumer_secret}
148
- token_key: ${token_key}
149
- token_secret: ${token_secret}`
150
- }
151
-
152
-
153
- users:
154
- - name: ${process.env.MAAS_ADMIN_USERNAME}
155
- sudo: ['ALL=(ALL) NOPASSWD:ALL']
156
- shell: /bin/bash
157
- lock_passwd: false
158
- groups: sudo,users,admin,wheel,lxd
159
- plain_text_passwd: '${process.env.MAAS_ADMIN_USERNAME}'
160
- ssh_authorized_keys:
161
- - ${fs.readFileSync(`/home/dd/engine/engine-private/deploy/id_rsa.pub`, 'utf8')}
162
-
163
- # manage_resolv_conf: true
164
- # resolv_conf:
165
- # nameservers: [8.8.8.8]
166
-
167
- # keyboard:
168
- # layout: es
169
-
170
- # check timedatectl on host
171
- # timezone: America/Santiago
172
- timezone: ${timezone}
173
-
174
- ntp:
175
- enabled: true
176
- servers:
177
- - ${process.env.MAAS_NTP_SERVER}
178
- ntp_client: chrony
179
- config:
180
- confpath: ${chronyConfPath}
181
-
182
- # ssh:
183
- # allow-pw: false
184
- # install-server: true
185
-
186
- # ssh_pwauth: false
187
-
188
- package_update: true
189
- package_upgrade: true
190
- packages:
191
- - git
192
- - htop
193
- - snapd
194
- - chrony
195
- resize_rootfs: false
196
- growpart:
197
- mode: false
198
- network:
199
- version: 2
200
- ethernets:
201
- ${process.env.RPI4_INTERFACE_NAME}:
202
- match:
203
- macaddress: "${process.env.RPI4_MAC_ADDRESS}"
204
- mtu: 1500
205
- set-name: ${process.env.RPI4_INTERFACE_NAME}
206
- dhcp4: false
207
- addresses:
208
- - ${commissioningDeviceIp}/24
209
- gateway4: ${gatewayip}
210
- nameservers:
211
- addresses:
212
- - ${process.env.MAAS_DNS}
213
-
214
-
215
- final_message: "====== Cloud init finished ======"
216
-
217
- # power_state:
218
- # mode: reboot
219
- # message: Rebooting after initial setup
220
- # timeout: 30
221
- # condition: True
222
-
223
- bootcmd:
224
- - echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
225
- - echo "Init bootcmd"
226
- - echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
227
- ${cloudConfigCmdRunFactory(bootCmdSteps)}
228
- runcmd:
229
- - echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
230
- - echo "Init runcmd"
231
- - echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
232
-
233
- # If this is set, 'root' will not be able to ssh in and they
234
- # will get a message to login instead as the default $user
235
- disable_root: true
236
-
237
- # This will cause the set+update hostname module to not operate (if true)
238
- preserve_hostname: false
239
-
240
- # The modules that run in the 'init' stage
241
- cloud_init_modules:
242
- - migrator
243
- - bootcmd
244
- - write-files
245
- - growpart
246
- - resizefs
247
- - set_hostname
248
- - update_etc_hosts
249
- - rsyslog
250
- - users-groups
251
- - ssh
252
-
253
- cloud_config_modules:
254
- - mounts
255
- - locale
256
- - set-passwords
257
- - package-update-upgrade-install
258
- - timezone
259
- - runcmd
260
- - ssh-import-id
261
- - ntp
262
-
263
- cloud_final_modules:
264
- - rightscale_userdata
265
- - scripts-per-once
266
- - scripts-per-boot
267
- - scripts-per-instance
268
- - scripts-user
269
- - ssh-authkey-fingerprints
270
- - keys-to-console
271
- - phone-home
272
- - final-message
273
-
274
- EOF_MAAS_CFG`,
275
- ];
276
-
277
- const runSteps = (nfsHostPath, steps = []) => {
278
- const script = steps
279
- .map(
280
- (s, i) => `echo "step ${i + 1}/${steps.length}: ${s.split('\n')[0]}"
281
- ${s}`,
282
- )
283
- .join('\n');
284
-
285
- const cmd = `sudo chroot ${nfsHostPath} /usr/bin/qemu-aarch64-static /bin/bash <<'EOF_OUTER'
286
- ${script}
287
- EOF_OUTER`;
288
-
289
- shellExec(cmd);
290
- };
291
-
292
- const chronySetUp = (path, alias = 'chrony') => {
293
- // use alias = 'chronyd' for RHEL
294
- // use alias = 'chrony' for Ubuntu
295
- return [
296
- `echo '
297
- # Use public servers from the pool.ntp.org project.
298
- # Please consider joining the pool (http://www.pool.ntp.org/join.html).
299
- # pool 2.pool.ntp.org iburst
300
- server ${process.env.MAAS_NTP_SERVER} iburst
301
-
302
- # Record the rate at which the system clock gains/losses time.
303
- driftfile /var/lib/chrony/drift
304
-
305
- # Allow the system clock to be stepped in the first three updates
306
- # if its offset is larger than 1 second.
307
- makestep 1.0 3
308
-
309
- # Enable kernel synchronization of the real-time clock (RTC).
310
- rtcsync
311
-
312
- # Enable hardware timestamping on all interfaces that support it.
313
- #hwtimestamp *
314
-
315
- # Increase the minimum number of selectable sources required to adjust
316
- # the system clock.
317
- #minsources 2
318
-
319
- # Allow NTP client access from local network.
320
- #allow 192.168.0.0/16
321
-
322
- # Serve time even if not synchronized to a time source.
323
- #local stratum 10
324
-
325
- # Specify file containing keys for NTP authentication.
326
- keyfile /etc/chrony.keys
327
-
328
- # Get TAI-UTC offset and leap seconds from the system tz database.
329
- leapsectz right/UTC
330
-
331
- # Specify directory for log files.
332
- logdir /var/log/chrony
333
-
334
- # Select which information is logged.
335
- #log measurements statistics tracking
336
- ' > ${path} `,
337
- `systemctl stop ${alias}`,
338
-
339
- `${alias}d -q 'server ntp.ubuntu.com iburst'`,
340
-
341
- // `chronyd -q 'server 0.europe.pool.ntp.org iburst'`,
342
-
343
- `sudo systemctl enable --now ${alias}`,
344
- `sudo systemctl restart ${alias}`,
345
- `sudo systemctl status ${alias}`,
346
-
347
- `chronyc sources`,
348
- `chronyc tracking`,
349
-
350
- `chronyc sourcestats -v`,
351
- `timedatectl status`,
352
- ];
353
- };
354
-
355
- const installUbuntuUnderpostTools = ({ nfsHostPath, host }) => {
356
- fs.mkdirSync(`${nfsHostPath}/underpost`, { recursive: true });
357
-
358
- logger.info('Build', `${nfsHostPath}/underpost/date.sh`);
359
- fs.writeFileSync(
360
- `${nfsHostPath}/underpost/date.sh`,
361
- `${timeZoneSteps.join('\n')}
362
- ${chronySetUp(chronyConfPath).join('\n')}
363
- `,
364
- 'utf8',
365
- );
366
-
367
- logger.info('Build', `${nfsHostPath}/underpost/host.sh`);
368
- fs.writeFileSync(
369
- `${nfsHostPath}/underpost/host.sh`,
370
- `echo -e "127.0.0.1 localhost\n127.0.1.1 ${host}" | tee -a /etc/hosts`,
371
- 'utf8',
372
- );
373
-
374
- logger.info('Build', `${nfsHostPath}/underpost/keys_current.sh`);
375
- fs.writeFileSync(
376
- `${nfsHostPath}/underpost/keys_current.sh`,
377
- `cat /etc/cloud/cloud.cfg.d/90_maas.cfg | grep -C 5 'metadata'`,
378
- 'utf8',
379
- );
380
-
381
- logger.info('Build', `${nfsHostPath}/underpost/keys_remove.sh`);
382
- fs.writeFileSync(
383
- `${nfsHostPath}/underpost/keys_remove.sh`,
384
- `cp -a /underpost/90_maas_no_keys.cfg /etc/cloud/cloud.cfg.d/90_maas.cfg
385
- /underpost/keys_current.sh`,
386
- 'utf8',
387
- );
388
-
389
- logger.info('Build', `${nfsHostPath}/underpost/keys_import.sh`);
390
- fs.writeFileSync(
391
- `${nfsHostPath}/underpost/keys_import.sh`,
392
- `cp -a /underpost/90_maas_keys.cfg /etc/cloud/cloud.cfg.d/90_maas.cfg
393
- /underpost/keys_current.sh`,
394
- 'utf8',
395
- );
396
-
397
- logger.info('Build', `${nfsHostPath}/underpost/keyboard.sh`);
398
- fs.writeFileSync(
399
- `${nfsHostPath}/underpost/keyboard.sh`,
400
- `${keyboardSteps.join('\n')}
401
- `,
402
- 'utf8',
403
- );
404
-
405
- logger.info('Build', `${nfsHostPath}/underpost/dns.sh`);
406
- // echo "nameserver ${process.env.MAAS_DNS}" | tee /etc/resolv.conf > /dev/null
407
- fs.writeFileSync(
408
- `${nfsHostPath}/underpost/dns.sh`,
409
- `rm /etc/resolv.conf
410
- echo 'nameserver 8.8.8.8' > /run/systemd/resolve/stub-resolv.conf
411
- ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf`,
412
- 'utf8',
413
- );
414
-
415
- logger.info('Build', `${nfsHostPath}/underpost/start.sh`);
416
- fs.writeFileSync(
417
- `${nfsHostPath}/underpost/start.sh`,
418
- `#!/bin/bash
419
- set -x
420
- sudo cloud-init --all-stages
421
- `,
422
- 'utf8',
423
- );
424
-
425
- logger.info('Build', `${nfsHostPath}/underpost/reset.sh`);
426
- fs.writeFileSync(
427
- `${nfsHostPath}/underpost/reset.sh`,
428
- `${cloudInitReset}
429
- ${bootCmdSteps.join('\n')}`,
430
- 'utf8',
431
- );
432
-
433
- logger.info('Build', `${nfsHostPath}/underpost/help.sh`);
434
- fs.writeFileSync(
435
- `${nfsHostPath}/underpost/help.sh`,
436
- `echo "=== Cloud init utils ==="
437
- echo "sudo cloud-init --all-stages"
438
- echo "sudo cloud-init clean --logs --seed --configs all --machine-id --reboot"
439
- echo "sudo cloud-init init --local"
440
- echo "sudo cloud-init init"
441
- echo "sudo cloud-init modules --mode=config"
442
- echo "sudo cloud-init modules --mode=final"`,
443
- 'utf8',
444
- );
445
-
446
- logger.info('Build', `${nfsHostPath}/underpost/test.sh`);
447
- fs.writeFileSync(
448
- `${nfsHostPath}/underpost/test.sh`,
449
- `echo -e "\n=== Current date/time ==="
450
- date '+%Y-%m-%d %H:%M:%S'
451
- echo -e "\n=== Keyboard layout ==="
452
- cat /etc/default/keyboard
453
- echo -e "\n=== Registered users ==="
454
- cut -d: -f1 /etc/passwd
455
- `,
456
- 'utf8',
457
- );
458
-
459
- logger.info('Build', `${nfsHostPath}/underpost/shutdown.sh`);
460
- fs.writeFileSync(
461
- `${nfsHostPath}/underpost/shutdown.sh`,
462
- `cp -a /underpost/90_maas_no_keys.cfg /etc/cloud/cloud.cfg.d/90_maas.cfg
463
- sudo shutdown -h now`,
464
- 'utf8',
465
- );
466
-
467
- logger.info('Build', `${nfsHostPath}/underpost/mac.sh`);
468
- fs.writeFileSync(
469
- `${nfsHostPath}/underpost/mac.sh`,
470
- `echo "$(cat /sys/class/net/${process.env.RPI4_INTERFACE_NAME}/address)" > /underpost/mac`,
471
- 'utf8',
472
- );
473
-
474
- logger.info('Build', `${nfsHostPath}/underpost/device_scan.sh`);
475
- fs.copySync(`./manifests/maas/device-scan.sh`, `${nfsHostPath}/underpost/device_scan.sh`);
476
-
477
- logger.info('Build', `${nfsHostPath}/underpost/config-path.sh`);
478
- fs.writeFileSync(`${nfsHostPath}/underpost/config-path.sh`, `echo "/etc/cloud/cloud.cfg.d/90_maas.cfg"`, 'utf8');
479
-
480
- shellExec(`sudo rm -rf ${nfsHostPath}/root/.ssh`);
481
- shellExec(`sudo rm -rf ${nfsHostPath}/home/root/.ssh`);
482
-
483
- fs.copySync(`/root/.ssh`, `${nfsHostPath}/root/.ssh`);
484
-
485
- logger.info('Run', `${nfsHostPath}/underpost/test.sh`);
486
- runSteps(nfsHostPath, [
487
- `chmod +x /underpost/date.sh`,
488
- `chmod +x /underpost/keyboard.sh`,
489
- `chmod +x /underpost/dns.sh`,
490
- `chmod +x /underpost/help.sh`,
491
- `chmod +x /underpost/config-path.sh`,
492
- `chmod +x /underpost/host.sh`,
493
- `chmod +x /underpost/keys_current.sh`,
494
- `chmod +x /underpost/keys_import.sh`,
495
- `chmod +x /underpost/keys_remove.sh`,
496
- `chmod +x /underpost/test.sh`,
497
- `chmod +x /underpost/start.sh`,
498
- `chmod +x /underpost/reset.sh`,
499
- `chmod +x /underpost/shutdown.sh`,
500
- `chmod +x /underpost/device_scan.sh`,
501
- `chmod +x /underpost/mac.sh`,
502
- chronySetUp(chronyConfPath)[0],
503
- `sudo chmod 700 ~/.ssh/`,
504
- `sudo chmod 600 ~/.ssh/authorized_keys`,
505
- `sudo chmod 644 ~/.ssh/known_hosts`,
506
- `sudo chmod 600 ~/.ssh/id_rsa`,
507
- `sudo chmod 600 /etc/ssh/ssh_host_ed25519_key`,
508
- `chown -R root:root ~/.ssh`,
509
- `/underpost/test.sh`,
510
- ]);
511
- };
512
-
513
- const updateVirtualRoot = async ({
514
- controlServerIp,
515
- architecture,
516
- host,
517
- nfsHostPath,
518
- commissioningDeviceIp,
519
- update,
520
- gatewayip,
521
- }) => {
522
- // <consumer_key>:<consumer_token>:<secret>
523
- // <consumer_key>:<consumer_secret>:<token_key>:<token_secret>
524
- // maas apikey --with-names --username ${process.env.MAAS_ADMIN_USERNAME}
525
- // maas ${process.env.MAAS_ADMIN_USERNAME} account create-authorisation-token
526
- // maas apikey --generate --username ${process.env.MAAS_ADMIN_USERNAME}
527
- // https://github.com/CanonicalLtd/maas-docs/issues/647
528
-
529
- const parts = shellExec(`maas apikey --with-names --username ${process.env.MAAS_ADMIN_USERNAME}`, {
530
- stdout: true,
531
- })
532
- .trim()
533
- .split(`\n`)[0]
534
- .split(':');
535
-
536
- let consumer_key, consumer_secret, token_key, token_secret;
537
-
538
- if (parts.length === 4) {
539
- [consumer_key, consumer_secret, token_key, token_secret] = parts;
540
- } else if (parts.length === 3) {
541
- [consumer_key, token_key, token_secret] = parts;
542
- consumer_secret = '""';
543
- token_secret = token_secret.split(' MAAS consumer')[0].trim();
544
- } else {
545
- throw new Error('Invalid token format');
546
- }
547
-
548
- logger.info('Maas api token generated', { consumer_key, consumer_secret, token_key, token_secret });
549
-
550
- if (update) {
551
- // --reboot
552
- if (process.argv.includes('reset'))
553
- shellExec(`sudo chroot ${nfsHostPath} /usr/bin/qemu-aarch64-static /bin/bash <<'EOF'
554
- ${cloudInitReset}
555
- EOF`);
556
-
557
- if (fs.existsSync(`${nfsHostPath}/var/log/`)) {
558
- fs.writeFileSync(`${nfsHostPath}/var/log/cloud-init.log`, '', 'utf8');
559
- fs.writeFileSync(`${nfsHostPath}/var/log/cloud-init-output.log`, '', 'utf8');
560
- }
561
- } else {
562
- runSteps(nfsHostPath, installSteps);
563
- runSteps(nfsHostPath, [
564
- `useradd -m -s /bin/bash -G sudo root`,
565
- `echo 'root:root' | chpasswd`,
566
- `mkdir -p /home/root/.ssh`,
567
- `echo '${fs.readFileSync(
568
- `/home/dd/engine/engine-private/deploy/id_rsa.pub`,
569
- 'utf8',
570
- )}' > /home/root/.ssh/authorized_keys`,
571
- `chown -R root /home/root/.ssh`,
572
- `chmod 700 /home/root/.ssh`,
573
- `chmod 600 /home/root/.ssh/authorized_keys`,
574
- ]);
575
- runSteps(nfsHostPath, [
576
- // `date -s "${shellExec(`date '+%Y-%m-%d %H:%M:%S'`, { stdout: true }).trim()}"`,
577
- // `date`,
578
- ...timeZoneSteps,
579
- ...chronySetUp(chronyConfPath),
580
- ...keyboardSteps,
581
- ]);
582
- }
583
-
584
- runSteps(
585
- nfsHostPath,
586
- cloudConfigFactory(
587
- {
588
- auth: true,
589
- controlServerIp,
590
- architecture,
591
- host,
592
- nfsHostPath,
593
- commissioningDeviceIp,
594
- update,
595
- gatewayip,
596
- },
597
- { consumer_key, consumer_secret, token_key, token_secret },
598
- '/underpost/90_maas_keys.cfg',
599
- ),
600
- );
601
-
602
- runSteps(
603
- nfsHostPath,
604
- cloudConfigFactory(
605
- {
606
- auth: false,
607
- controlServerIp,
608
- architecture,
609
- host,
610
- nfsHostPath,
611
- commissioningDeviceIp,
612
- update,
613
- gatewayip,
614
- },
615
- { consumer_key, consumer_secret, token_key, token_secret },
616
- '/underpost/90_maas_no_keys.cfg',
617
- ),
618
- );
619
-
620
- if (process.argv.includes('auth')) {
621
- shellExec(`cp ${nfsHostPath}/underpost/90_maas_keys.cfg ${nfsHostPath}/etc/cloud/cloud.cfg.d/90_maas.cfg`);
622
- } else {
623
- shellExec(`cp ${nfsHostPath}/underpost/90_maas_no_keys.cfg ${nfsHostPath}/etc/cloud/cloud.cfg.d/90_maas.cfg`);
624
- }
625
-
626
- installUbuntuUnderpostTools({ nfsHostPath, host });
627
-
628
- shellExec(`./manifests/maas/nat-iptables.sh`, { silent: true });
629
-
630
- shellExec(`cat ${nfsHostPath}/etc/cloud/cloud.cfg.d/90_maas.cfg`);
631
- };
632
-
633
54
  try {
634
55
  switch (operator) {
635
56
  case 'save':
@@ -1593,6 +1014,29 @@ EOF`);
1593
1014
  break;
1594
1015
  }
1595
1016
 
1017
+ case 'maas-db': {
1018
+ // DROP, ALTER, CREATE, WITH ENCRYPTED
1019
+ // sudo -u <user> -h <host> psql <db-name>
1020
+ shellExec(`DB_PG_MAAS_NAME=${process.env.DB_PG_MAAS_NAME}`);
1021
+ shellExec(`DB_PG_MAAS_PASS=${process.env.DB_PG_MAAS_PASS}`);
1022
+ shellExec(`DB_PG_MAAS_USER=${process.env.DB_PG_MAAS_USER}`);
1023
+ shellExec(`DB_PG_MAAS_HOST=${process.env.DB_PG_MAAS_HOST}`);
1024
+ shellExec(
1025
+ `sudo -i -u postgres psql -c "CREATE USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1026
+ );
1027
+ shellExec(
1028
+ `sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1029
+ );
1030
+ const actions = ['LOGIN', 'SUPERUSER', 'INHERIT', 'CREATEDB', 'CREATEROLE', 'REPLICATION'];
1031
+ shellExec(`sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ${actions.join(' ')}"`);
1032
+ shellExec(`sudo -i -u postgres psql -c "\\du"`);
1033
+
1034
+ shellExec(`sudo -i -u postgres createdb -O "$DB_PG_MAAS_USER" "$DB_PG_MAAS_NAME"`);
1035
+
1036
+ shellExec(`sudo -i -u postgres psql -c "\\l"`);
1037
+ break;
1038
+ }
1039
+
1596
1040
  case 'valkey': {
1597
1041
  if (!process.argv.includes('server')) {
1598
1042
  if (process.argv.includes('rocky')) {
@@ -1806,616 +1250,6 @@ EOF`);
1806
1250
  break;
1807
1251
  }
1808
1252
 
1809
- case 'maas': {
1810
- dotenv.config({ path: `/home/dd/engine/engine-private/conf/dd-cron/.env.production`, override: true });
1811
- const controlServerIp = getLocalIPv4Address();
1812
- const tftpRoot = process.argv.includes('v3.0')
1813
- ? `/var/snap/maas/common/maas/boot-resources/snapshot-20250720-162718`
1814
- : process.env.TFTP_ROOT;
1815
- const commissioningDeviceIp = process.env.RPI4_IP;
1816
- const netmask = process.env.NETMASK;
1817
- const gatewayip = process.env.GATEWAY_IP;
1818
- let commissioningMac = '00:00:00:00:00:00';
1819
-
1820
- const removeMachines = () => {
1821
- for (const machine of machines) {
1822
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine delete ${machine.system_id}`);
1823
- }
1824
- machines = [];
1825
- };
1826
-
1827
- const clearDiscoveries = () => {
1828
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries clear all=true`);
1829
- if (process.argv.includes('force')) {
1830
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries scan force=true`);
1831
- }
1832
- };
1833
-
1834
- const macMonitor = async (nfsServerRootPath) => {
1835
- if (fs.existsSync(`${nfsServerRootPath}/underpost/mac`)) {
1836
- commissioningMac = fs.readFileSync(`${nfsServerRootPath}/underpost/mac`, 'utf8').trim();
1837
- logger.info('Commissioning MAC', commissioningMac);
1838
- return;
1839
- }
1840
- await timer(1000);
1841
- await macMonitor(nfsServerRootPath);
1842
- };
1843
-
1844
- let resources;
1845
- if (!process.argv.includes('machines'))
1846
- try {
1847
- resources = JSON.parse(
1848
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-resources read`, {
1849
- silent: true,
1850
- stdout: true,
1851
- }),
1852
- ).map((o) => ({
1853
- id: o.id,
1854
- name: o.name,
1855
- architecture: o.architecture,
1856
- }));
1857
- if (process.argv.includes('images')) {
1858
- console.table(resources);
1859
- process.exit(0);
1860
- }
1861
- } catch (error) {
1862
- logger.error(error);
1863
- }
1864
-
1865
- let machines;
1866
- try {
1867
- machines = JSON.parse(
1868
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machines read`, {
1869
- stdout: true,
1870
- silent: true,
1871
- }),
1872
- ).map((m) => ({
1873
- system_id: m.interface_set[0].system_id,
1874
- mac_address: m.interface_set[0].mac_address,
1875
- hostname: m.hostname,
1876
- status_name: m.status_name,
1877
- }));
1878
- if (process.argv.includes('machines')) {
1879
- console.table(machines);
1880
- process.exit(0);
1881
- }
1882
- } catch (error) {
1883
- logger.error(error);
1884
- }
1885
-
1886
- if (process.argv.includes('journald')) {
1887
- shellExec(`sudo sed -i 's/^#Storage=auto/Storage=volatile/' /etc/systemd/journald.conf`);
1888
- shellExec(`sudo systemctl daemon-reload`);
1889
- shellExec(`sudo systemctl restart systemd-journald`);
1890
- shellExec(`journalctl --disk-usage`);
1891
- process.exit(0);
1892
- }
1893
-
1894
- if (process.argv.includes('db')) {
1895
- // DROP, ALTER, CREATE, WITH ENCRYPTED
1896
- // sudo -u <user> -h <host> psql <db-name>
1897
- shellExec(`DB_PG_MAAS_NAME=${process.env.DB_PG_MAAS_NAME}`);
1898
- shellExec(`DB_PG_MAAS_PASS=${process.env.DB_PG_MAAS_PASS}`);
1899
- shellExec(`DB_PG_MAAS_USER=${process.env.DB_PG_MAAS_USER}`);
1900
- shellExec(`DB_PG_MAAS_HOST=${process.env.DB_PG_MAAS_HOST}`);
1901
- shellExec(
1902
- `sudo -i -u postgres psql -c "CREATE USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1903
- );
1904
- shellExec(
1905
- `sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1906
- );
1907
- const actions = ['LOGIN', 'SUPERUSER', 'INHERIT', 'CREATEDB', 'CREATEROLE', 'REPLICATION'];
1908
- shellExec(`sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ${actions.join(' ')}"`);
1909
- shellExec(`sudo -i -u postgres psql -c "\\du"`);
1910
-
1911
- shellExec(`sudo -i -u postgres createdb -O "$DB_PG_MAAS_USER" "$DB_PG_MAAS_NAME"`);
1912
-
1913
- shellExec(`sudo -i -u postgres psql -c "\\l"`);
1914
- process.exit(0);
1915
- }
1916
-
1917
- // TODO: - Disable maas proxy (egress forwarding to public dns)
1918
- // - Configure maas dhcp control server
1919
- // - Configure maas dns forwarding ${process.env.MAAS_DNS}
1920
- // - Disable DNSSEC validation to No (Disable DNSSEC; useful when upstream DNS is misconfigured)
1921
-
1922
- if (process.argv.includes('clear')) {
1923
- removeMachines();
1924
- clearDiscoveries();
1925
- process.exit(0);
1926
- }
1927
- if (process.argv.includes('grub-arm64')) {
1928
- shellExec(`sudo dnf install grub2-efi-aa64-modules`);
1929
- shellExec(`sudo dnf install grub2-efi-x64-modules`);
1930
- // sudo grub2-mknetdir --net-directory=${tftpRoot} --subdir=/boot/grub --module-path=/usr/lib/grub/arm64-efi arm64-efi
1931
- process.exit(0);
1932
- }
1933
-
1934
- if (process.argv.includes('psql')) {
1935
- const cmd = `psql -U ${process.env.DB_PG_MAAS_USER} -h ${process.env.DB_PG_MAAS_HOST} -W ${process.env.DB_PG_MAAS_NAME}`;
1936
- pbcopy(cmd);
1937
- process.exit(0);
1938
- }
1939
- if (process.argv.includes('logs')) {
1940
- shellExec(`maas status`);
1941
- const cmd = `journalctl -f -t dhcpd -u snap.maas.pebble.service`;
1942
- pbcopy(cmd);
1943
- process.exit(0);
1944
- }
1945
-
1946
- // shellExec(`MAAS_ADMIN_USERNAME=${process.env.MAAS_ADMIN_USERNAME}`);
1947
- // shellExec(`MAAS_ADMIN_EMAIL=${process.env.MAAS_ADMIN_EMAIL}`);
1948
- // shellExec(`maas createadmin --username $MAAS_ADMIN_USERNAME --email $MAAS_ADMIN_EMAIL`);
1949
- // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-sources read`);
1950
- // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} commissioning-scripts read`);
1951
- // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-source-selections read 60`);
1952
-
1953
- // MaaS admin CLI:
1954
- // maas login <maas-admin-username> http://localhost:5240/MAAS
1955
- // paste GUI API KEY (profile section)
1956
-
1957
- // Import custom image
1958
- // maas <maas-admin-username> boot-resources create name='custom/RockyLinuxRpi4' \
1959
- // title='RockyLinuxRpi4' \
1960
- // architecture='arm64/generic' \
1961
- // filetype='tgz' \
1962
- // content@=/home/RockyLinuxRpi_9-latest.tar.gz
1963
-
1964
- // Image boot resource:
1965
- // /var/snap/maas/current/root/snap/maas
1966
- // /var/snap/maas/common/maas/tftp_root
1967
- // sudo chmod 755 /var/snap/maas/common/maas/tftp_root
1968
-
1969
- // /var/snap/maas/common/maas/dhcpd.conf
1970
- // sudo snap restart maas.pebble
1971
-
1972
- // PXE Linux files:
1973
- // /var/snap/maas/common/maas/image-storage/bootloaders/pxe/i386
1974
- // sudo nmcli con modify <interface-device-name-connection-id> ethtool.feature-rx on ethtool.feature-tx off
1975
- // sudo nmcli connection up <interface-device-name-connection-id>
1976
-
1977
- // man nm-settings |grep feature-tx-checksum
1978
-
1979
- // nmcli c modify <interface-device-name-connection-id> \
1980
- // ethtool.feature-tx-checksum-fcoe-crc off \
1981
- // ethtool.feature-tx-checksum-ip-generic off \
1982
- // ethtool.feature-tx-checksum-ipv4 off \
1983
- // ethtool.feature-tx-checksum-ipv6 off \
1984
- // ethtool.feature-tx-checksum-sctp off
1985
-
1986
- // Ensure Rocky NFS server and /etc/exports configured
1987
- // sudo systemctl restart nfs-server
1988
- // Check mounts: showmount -e <server-ip>
1989
- // Check nfs ports: rpcinfo -p
1990
- // sudo chown -R root:root ${process.env.NFS_EXPORT_PATH}/rpi4mb
1991
- // sudo chmod 755 ${process.env.NFS_EXPORT_PATH}/rpi4mb
1992
-
1993
- // tftp server
1994
- // sudo chown -R root:root /var/snap/maas/common/maas/tftp_root/rpi4mb
1995
-
1996
- // tftp client
1997
- // sudo dnf install tftp
1998
- // tftp <server-ip> -c get <path>
1999
-
2000
- // Check firewall-cmd
2001
- // firewall-cmd --permanent --add-service=rpc-bind
2002
- // firewall-cmd --reload
2003
- // systemctl disable firewalld
2004
- // sudo firewall-cmd --permanent --add-port=10259/tcp --zone=public
2005
-
2006
- // Image extension transform (.img.xz to .tar.gz):
2007
- // tar -cvzf image-name.tar.gz image-name.img.xz
2008
-
2009
- // Rocky network configuration:
2010
- // /etc/NetworkManager/system-connections
2011
-
2012
- // Rocky kernel params update
2013
- // sudo grubby --args="<key>=<value> <key>=<value>" --update-kernel=ALL
2014
- // sudo reboot now
2015
-
2016
- // Temporal:
2017
- // sudo snap install temporal
2018
- // journalctl -u snap.maas.pebble -t maas-regiond
2019
- // journalctl -u snap.maas.pebble -t maas-temporal -n 100 --no-pager -f
2020
-
2021
- // Remove:
2022
- // sudo dnf remove <package> -y; sudo dnf autoremove -y; sudo dnf clean packages
2023
- // check: ~
2024
- // check: ~./cache
2025
- // check: ~./config
2026
-
2027
- // Check file logs
2028
- // grep -i -E -C 1 '<key-a>|<key-b>' /example.log | tail -n 600
2029
-
2030
- // Back into your firmware setup (UEFI or BIOS config screen).
2031
- // grub> fwsetup
2032
-
2033
- // Poweroff:
2034
- // grub > halt
2035
- // initramfs > poweroff
2036
-
2037
- // Check interface
2038
- // ip link show
2039
- // nmcli con show
2040
-
2041
- let firmwarePath,
2042
- tftpSubDir,
2043
- kernelFilesPaths,
2044
- name,
2045
- architecture,
2046
- resource,
2047
- nfsConnectStr,
2048
- etcExports,
2049
- nfsServerRootPath,
2050
- bootConf,
2051
- zipFirmwareFileName,
2052
- zipFirmwareName,
2053
- zipFirmwareUrl,
2054
- interfaceName,
2055
- nfsHost,
2056
- bootResourcesPath,
2057
- bootKernelPath;
2058
-
2059
- switch (process.argv[3]) {
2060
- case 'rpi4mb':
2061
- tftpSubDir = '/rpi4mb';
2062
- zipFirmwareFileName = `RPi4_UEFI_Firmware_v1.41.zip`;
2063
- zipFirmwareName = zipFirmwareFileName.split('.zip')[0];
2064
- zipFirmwareUrl = `https://github.com/pftf/RPi4/releases/download/v1.41/RPi4_UEFI_Firmware_v1.41.zip`;
2065
- firmwarePath = `../${zipFirmwareName}`;
2066
- interfaceName = process.env.RPI4_INTERFACE_NAME;
2067
- nfsHost = 'rpi4mb';
2068
- if (!fs.existsSync(firmwarePath)) {
2069
- await Downloader(zipFirmwareUrl, `../${zipFirmwareFileName}`);
2070
- shellExec(`cd .. && mkdir ${zipFirmwareName} && cd ${zipFirmwareName} && unzip ../${zipFirmwareFileName}`);
2071
- }
2072
- resource = resources.find((o) => o.architecture === 'arm64/ga-24.04' && o.name === 'ubuntu/noble');
2073
- name = resource.name;
2074
- architecture = resource.architecture;
2075
- // resource = resources.find((o) => o.name === name && o.architecture === architecture);
2076
- nfsServerRootPath = `${process.env.NFS_EXPORT_PATH}/rpi4mb`;
2077
- // ,anonuid=1001,anongid=100
2078
- // etcExports = `${nfsServerRootPath} *(rw,all_squash,sync,no_root_squash,insecure)`;
2079
- etcExports = `${nfsServerRootPath} 192.168.1.0/24(${[
2080
- 'rw',
2081
- // 'all_squash',
2082
- 'sync',
2083
- 'no_root_squash',
2084
- 'no_subtree_check',
2085
- 'insecure',
2086
- ]})`;
2087
- if (process.argv.includes('v3.0')) {
2088
- bootResourcesPath = `/var/snap/maas/common/maas/boot-resources/snapshot-20250720-162718`;
2089
- bootKernelPath = `/var/snap/maas/common/maas/boot-resources/snapshot-20250720-162718/ubuntu/arm64/hwe-24.04/noble/stable`;
2090
- kernelFilesPaths = {
2091
- 'vmlinuz-efi': `${bootKernelPath}/boot-kernel`,
2092
- 'initrd.img': `${bootKernelPath}/boot-initrd`,
2093
- squashfs: `${bootKernelPath}/squashfs`,
2094
- };
2095
- } else {
2096
- const resourceData = JSON.parse(
2097
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-resource read ${resource.id}`, {
2098
- stdout: true,
2099
- silent: true,
2100
- disableLog: true,
2101
- }),
2102
- );
2103
- const bootFiles = resourceData.sets[Object.keys(resourceData.sets)[0]].files;
2104
- const suffix = architecture.match('xgene') ? '.xgene' : '';
2105
- bootResourcesPath = `/var/snap/maas/common/maas/image-storage/bootloaders/uefi/arm64`;
2106
- bootKernelPath = `/var/snap/maas/common/maas/image-storage`;
2107
- kernelFilesPaths = {
2108
- 'vmlinuz-efi': `${bootKernelPath}/${bootFiles['boot-kernel' + suffix].filename_on_disk}`,
2109
- 'initrd.img': `${bootKernelPath}/${bootFiles['boot-initrd' + suffix].filename_on_disk}`,
2110
- squashfs: `${bootKernelPath}/${bootFiles['squashfs'].filename_on_disk}`,
2111
- };
2112
- }
2113
-
2114
- const protocol = 'tcp'; // v3 -> tcp, v4 -> udp
2115
-
2116
- const mountOptions = [
2117
- protocol,
2118
- 'vers=3',
2119
- 'nfsvers=3',
2120
- 'nolock',
2121
- // 'protocol=tcp',
2122
- // 'hard=true',
2123
- 'port=2049',
2124
- // 'sec=none',
2125
- 'rw',
2126
- 'hard',
2127
- 'intr',
2128
- 'rsize=32768',
2129
- 'wsize=32768',
2130
- 'acregmin=0',
2131
- 'acregmax=0',
2132
- 'acdirmin=0',
2133
- 'acdirmax=0',
2134
- 'noac',
2135
- // 'nodev',
2136
- // 'nosuid',
2137
- ];
2138
- const cmd = [
2139
- `console=serial0,115200`,
2140
- // `console=ttyAMA0,115200`,
2141
- `console=tty1`,
2142
- // `initrd=-1`,
2143
- // `net.ifnames=0`,
2144
- // `dwc_otg.lpm_enable=0`,
2145
- // `elevator=deadline`,
2146
- `root=/dev/nfs`,
2147
- `nfsroot=${controlServerIp}:${process.env.NFS_EXPORT_PATH}/rpi4mb,${mountOptions}`,
2148
- // `nfsroot=${controlServerIp}:${process.env.NFS_EXPORT_PATH}/rpi4mb`,
2149
- `ip=${commissioningDeviceIp}:${controlServerIp}:${gatewayip}:${netmask}:${nfsHost}:${interfaceName}:static`,
2150
- `rootfstype=nfs`,
2151
- `rw`,
2152
- `rootwait`,
2153
- `fixrtc`,
2154
- 'initrd=initrd.img',
2155
- // 'boot=casper',
2156
- // 'ro',
2157
- 'netboot=nfs',
2158
- `init=/sbin/init`,
2159
- // `cloud-config-url=/dev/null`,
2160
- // 'ip=dhcp',
2161
- // 'ip=dfcp',
2162
- // 'autoinstall',
2163
- // 'rd.break',
2164
-
2165
- // Disable services that not apply over nfs
2166
- `systemd.mask=systemd-network-generator.service`,
2167
- `systemd.mask=systemd-networkd.service`,
2168
- `systemd.mask=systemd-fsck-root.service`,
2169
- `systemd.mask=systemd-udev-trigger.service`,
2170
- ];
2171
-
2172
- // TODO: use autoinstall cloud-config-url=http://<MAAS_IP>:5240/MAAS/metadata/latest
2173
- // #cloud-config
2174
- // autoinstall:
2175
- // version: 1
2176
-
2177
- // keyboard:
2178
- // layout: es
2179
- // variant: latinamerican
2180
-
2181
- // identity:
2182
- // hostname: rpi4
2183
- // username: root
2184
- // password: "{{PASSWORD}}"
2185
-
2186
- // ssh:
2187
- // install-server: true
2188
- // allow-pw: true
2189
- // authorized-keys:
2190
- // - "{{SSH_KEY}}"
2191
-
2192
- // locale: es_ES.UTF-8
2193
- // timezone: America/Santiago
2194
-
2195
- // packages:
2196
- // - cloud-init
2197
- // - systemd-sysv
2198
- // - openssh-server
2199
- // - sudo
2200
- // - udev
2201
- // - netplan.io
2202
-
2203
- // late-commands:
2204
- // - curtin in-target --target=/target ln -sf /lib/systemd/systemd /sbin/init
2205
-
2206
- nfsConnectStr = cmd.join(' ');
2207
- bootConf = `[all]
2208
- BOOT_UART=0
2209
- WAKE_ON_GPIO=1
2210
- POWER_OFF_ON_HALT=0
2211
- ENABLE_SELF_UPDATE=1
2212
- DISABLE_HDMI=0
2213
- NET_INSTALL_ENABLED=1
2214
- DHCP_TIMEOUT=45000
2215
- DHCP_REQ_TIMEOUT=4000
2216
- TFTP_FILE_TIMEOUT=30000
2217
- BOOT_ORDER=0x21
2218
-
2219
- # ─────────────────────────────────────────────────────────────
2220
- # TFTP configuration
2221
- # ─────────────────────────────────────────────────────────────
2222
-
2223
- # Custom TFTP prefix string (e.g., based on MAC address, no colons)
2224
- #TFTP_PREFIX_STR=AA-BB-CC-DD-EE-FF/
2225
-
2226
- # Optional PXE Option43 override (leave commented if unused)
2227
- #PXE_OPTION43="Raspberry Pi Boot"
2228
-
2229
- # DHCP client GUID (Option 97); 0x34695052 is the FourCC for Raspberry Pi 4
2230
- #DHCP_OPTION97=0x34695052
2231
-
2232
- TFTP_IP=${controlServerIp}
2233
- TFTP_PREFIX=1
2234
- TFTP_PREFIX_STR=${tftpSubDir.slice(1)}/
2235
-
2236
- # ─────────────────────────────────────────────────────────────
2237
- # Manually override Ethernet MAC address
2238
- # ─────────────────────────────────────────────────────────────
2239
-
2240
- MAC_ADDRESS=${process.env.RPI4_MAC_ADDRESS}
2241
-
2242
- # OTP MAC address override
2243
- #MAC_ADDRESS_OTP=0,1
2244
-
2245
- # ─────────────────────────────────────────────────────────────
2246
- # Static IP configuration (bypasses DHCP completely)
2247
- # ─────────────────────────────────────────────────────────────
2248
- CLIENT_IP=${commissioningDeviceIp}
2249
- SUBNET=255.255.255.0
2250
- GATEWAY=192.168.1.1
2251
-
2252
- `;
2253
- break;
2254
-
2255
- default:
2256
- break;
2257
- }
2258
-
2259
- shellExec(`sudo chmod 755 ${process.env.NFS_EXPORT_PATH}/${nfsHost}`);
2260
- shellExec(`sudo rm -rf ${tftpRoot}${tftpSubDir}`);
2261
- shellExec(`sudo cp -a ${firmwarePath} ${tftpRoot}${tftpSubDir}`);
2262
- shellExec(`mkdir -p ${tftpRoot}${tftpSubDir}/pxe`);
2263
-
2264
- fs.writeFileSync(`/etc/exports`, etcExports, 'utf8');
2265
- if (bootConf) fs.writeFileSync(`${tftpRoot}${tftpSubDir}/boot.conf`, bootConf, 'utf8');
2266
-
2267
- shellExec(`node bin/deploy nfs`);
2268
-
2269
- switch (process.argv[3]) {
2270
- case 'rpi4mb':
2271
- {
2272
- for (const file of ['bootaa64.efi', 'grubaa64.efi']) {
2273
- shellExec(`sudo cp -a ${bootResourcesPath}/${file} ${tftpRoot}${tftpSubDir}/pxe/${file}`);
2274
- }
2275
-
2276
- {
2277
- for (const file of Object.keys(kernelFilesPaths)) {
2278
- shellExec(`sudo cp -a ${kernelFilesPaths[file]} ${tftpRoot}${tftpSubDir}/pxe/${file}`);
2279
- }
2280
-
2281
- fs.mkdirSync(`${tftpRoot}/grub`, { recursive: true });
2282
-
2283
- const menuentryStr = 'UNDERPOST.NET UEFI/GRUB/MAAS RPi4 commissioning (ARM64)';
2284
- const grubCfgPath = `${tftpRoot}/grub/grub.cfg`;
2285
- fs.writeFileSync(
2286
- grubCfgPath,
2287
- `
2288
- insmod gzio
2289
- insmod http
2290
- insmod nfs
2291
- set timeout=5
2292
- set default=0
2293
-
2294
- menuentry '${menuentryStr}' {
2295
- set root=(tftp,${controlServerIp})
2296
- linux ${tftpSubDir}/pxe/vmlinuz-efi ${nfsConnectStr}
2297
- initrd ${tftpSubDir}/pxe/initrd.img
2298
- boot
2299
- }
2300
-
2301
- `,
2302
- 'utf8',
2303
- );
2304
- }
2305
- const arm64EfiPath = `${tftpRoot}/grub/arm64-efi`;
2306
- if (fs.existsSync(arm64EfiPath)) shellExec(`sudo rm -rf ${arm64EfiPath}`);
2307
- shellExec(`sudo cp -a /usr/lib/grub/arm64-efi ${arm64EfiPath}`);
2308
- }
2309
-
2310
- break;
2311
-
2312
- default:
2313
- break;
2314
- }
2315
-
2316
- logger.info('succes maas deploy', {
2317
- resource,
2318
- kernelFilesPaths,
2319
- tftpRoot,
2320
- tftpSubDir,
2321
- firmwarePath,
2322
- etcExports,
2323
- nfsServerRootPath,
2324
- nfsConnectStr,
2325
- });
2326
- shellExec(`sudo chown -R root:root ${tftpRoot}`);
2327
- shellExec(`sudo sudo chmod 755 ${tftpRoot}`);
2328
-
2329
- logger.info('Waiting for MAC assignment...');
2330
- fs.removeSync(`${nfsServerRootPath}/underpost/mac`);
2331
- await macMonitor(nfsServerRootPath);
2332
-
2333
- const monitor = async () => {
2334
- // discoveries Query observed discoveries.
2335
- // discovery Read or delete an observed discovery.
2336
-
2337
- const discoveries = JSON.parse(
2338
- shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries read`, {
2339
- silent: true,
2340
- stdout: true,
2341
- }),
2342
- );
2343
-
2344
- // {
2345
- // "discovery_id": "",
2346
- // "ip": "192.168.1.189",
2347
- // "mac_address": "00:00:00:00:00:00",
2348
- // "last_seen": "2025-05-05T14:17:37.354",
2349
- // "hostname": null,
2350
- // "fabric_name": "",
2351
- // "vid": null,
2352
- // "mac_organization": "",
2353
- // "observer": {
2354
- // "system_id": "",
2355
- // "hostname": "",
2356
- // "interface_id": 1,
2357
- // "interface_name": ""
2358
- // },
2359
- // "resource_uri": "/MAAS/api/2.0/discovery/MTkyLjE2OC4xLjE4OSwwMDowMDowMDowMDowMDowMA==/"
2360
- // },
2361
-
2362
- console.log(discoveries.map((d) => d.ip).join(' | '));
2363
-
2364
- for (const discovery of discoveries) {
2365
- const machine = {
2366
- architecture: architecture.match('amd') ? 'amd64/generic' : 'arm64/generic',
2367
- mac_address: discovery.mac_address,
2368
- hostname: discovery.hostname ?? discovery.mac_organization ?? discovery.domain ?? `generic-host-${s4()}`,
2369
- // discovery.ip.match(commissioningDeviceIp)
2370
- // ? nfsHost
2371
- // : `unknown-${s4()}`,
2372
- // description: '',
2373
- // https://maas.io/docs/reference-power-drivers
2374
- power_type: 'manual', // manual
2375
- // power_parameters_power_address: discovery.ip,
2376
- mac_addresses: discovery.mac_address,
2377
- ip: discovery.ip,
2378
- };
2379
- machine.hostname = machine.hostname.replaceAll(' ', '').replaceAll('.', '');
2380
-
2381
- if (machine.mac_addresses === commissioningMac)
2382
- try {
2383
- machine.hostname = nfsHost;
2384
- machine.mac_address = commissioningMac;
2385
- let newMachine = shellExec(
2386
- `maas ${process.env.MAAS_ADMIN_USERNAME} machines create ${Object.keys(machine)
2387
- .map((k) => `${k}="${machine[k]}"`)
2388
- .join(' ')}`,
2389
- {
2390
- silent: true,
2391
- stdout: true,
2392
- },
2393
- );
2394
- newMachine = { discovery, machine: JSON.parse(newMachine) };
2395
- machines.push(newMachine);
2396
- console.log(newMachine);
2397
- // commissioning_scripts=90-verify-user.sh
2398
- shellExec(
2399
- `maas ${process.env.MAAS_ADMIN_USERNAME} machine commission ${newMachine.machine.boot_interface.system_id} enable_ssh=1 skip_bmc_config=1 skip_networking=1 skip_storage=1`,
2400
- {
2401
- silent: true,
2402
- },
2403
- );
2404
- } catch (error) {
2405
- logger.error(error, error.stack);
2406
- } finally {
2407
- process.exit(0);
2408
- }
2409
- }
2410
- await timer(1000);
2411
- monitor();
2412
- };
2413
- // clearDiscoveries();
2414
- removeMachines();
2415
- monitor();
2416
- break;
2417
- }
2418
-
2419
1253
  case 'nfs': {
2420
1254
  // Daemon RPC NFSv3. ports:
2421
1255
 
@@ -2491,137 +1325,6 @@ udp-port = 32766
2491
1325
  shellExec(`sudo systemctl restart nfs-server`);
2492
1326
  break;
2493
1327
  }
2494
- case 'update-virtual-root': {
2495
- dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
2496
- const controlServerIp = getLocalIPv4Address();
2497
- const architecture = process.argv[3];
2498
- const host = process.argv[4];
2499
- const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
2500
- const commissioningDeviceIp = process.env.RPI4_IP;
2501
- const gatewayip = process.env.GATEWAY_IP;
2502
- await updateVirtualRoot({
2503
- controlServerIp,
2504
- architecture,
2505
- host,
2506
- nfsHostPath,
2507
- commissioningDeviceIp,
2508
- update: true,
2509
- gatewayip,
2510
- });
2511
- break;
2512
- }
2513
- case 'open-virtual-root': {
2514
- dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
2515
- const controlServerIp = getLocalIPv4Address();
2516
- const architecture = process.argv[3];
2517
- const host = process.argv[4];
2518
- const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
2519
- const gatewayip = process.env.GATEWAY_IP;
2520
- shellExec(`sudo dnf install -y iptables-legacy`);
2521
- shellExec(`sudo dnf install -y debootstrap`);
2522
- shellExec(`sudo dnf install kernel-modules-extra-$(uname -r)`);
2523
- switch (architecture) {
2524
- case 'arm64':
2525
- shellExec(`sudo podman run --rm --privileged multiarch/qemu-user-static --reset -p yes`);
2526
-
2527
- break;
2528
-
2529
- default:
2530
- break;
2531
- }
2532
-
2533
- shellExec(`sudo modprobe binfmt_misc`);
2534
- shellExec(`sudo mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc`);
2535
-
2536
- if (process.argv.includes('build')) {
2537
- shellExec(`mkdir -p ${nfsHostPath}`);
2538
- let cmd;
2539
- switch (host) {
2540
- case 'rpi4mb':
2541
- shellExec(`sudo rm -rf ${nfsHostPath}/*`);
2542
- shellExec(`sudo chown -R root:root ${nfsHostPath}`);
2543
- cmd = [
2544
- `sudo debootstrap`,
2545
- `--arch=arm64`,
2546
- `--variant=minbase`,
2547
- `--foreign`, // arm64 on amd64
2548
- [`noble`, `jammy`][0],
2549
- nfsHostPath,
2550
- `http://ports.ubuntu.com/ubuntu-ports/`,
2551
- ];
2552
- break;
2553
-
2554
- default:
2555
- break;
2556
- }
2557
- shellExec(cmd.join(' '));
2558
-
2559
- shellExec(`sudo podman create --name extract multiarch/qemu-user-static`);
2560
- shellExec(`podman ps -a`);
2561
- shellExec(`sudo podman cp extract:/usr/bin/qemu-aarch64-static ${nfsHostPath}/usr/bin/`);
2562
- shellExec(`sudo podman rm extract`);
2563
- shellExec(`podman ps -a`);
2564
-
2565
- switch (host) {
2566
- case 'rpi4mb':
2567
- shellExec(`file ${nfsHostPath}/bin/bash`); // expected: ELF 64-bit LSB pie executable, ARM aarch64 …
2568
- break;
2569
-
2570
- default:
2571
- break;
2572
- }
2573
-
2574
- shellExec(`sudo chroot ${nfsHostPath} /usr/bin/qemu-aarch64-static /bin/bash <<'EOF'
2575
- /debootstrap/debootstrap --second-stage
2576
- EOF`);
2577
- }
2578
- if (process.argv.includes('mount')) {
2579
- shellExec(`sudo mount --bind /proc ${nfsHostPath}/proc`);
2580
- shellExec(`sudo mount --bind /sys ${nfsHostPath}/sys`);
2581
- shellExec(`sudo mount --rbind /dev ${nfsHostPath}/dev`);
2582
- shellExec(`sudo mount --rbind /dev/pts ${nfsHostPath}/dev/pts`);
2583
- shellExec(`sudo mount --bind /run ${nfsHostPath}/run`);
2584
- }
2585
-
2586
- if (process.argv.includes('build')) {
2587
- switch (host) {
2588
- case 'rpi4mb':
2589
- const commissioningDeviceIp = process.env.RPI4_IP;
2590
-
2591
- await updateVirtualRoot({
2592
- controlServerIp,
2593
- architecture,
2594
- host,
2595
- nfsHostPath,
2596
- commissioningDeviceIp,
2597
- gatewayip,
2598
- });
2599
-
2600
- break;
2601
-
2602
- default:
2603
- break;
2604
- }
2605
- }
2606
- // if (process.argv.includes('mount')) {
2607
- // shellExec(`sudo mount --bind /lib/modules ${nfsHostPath}/lib/modules`);
2608
- // }
2609
-
2610
- break;
2611
- }
2612
-
2613
- case 'close-virtual-root': {
2614
- const architecture = process.argv[3];
2615
- const host = process.argv[4];
2616
- const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
2617
- shellExec(`sudo umount ${nfsHostPath}/proc`);
2618
- shellExec(`sudo umount ${nfsHostPath}/sys`);
2619
- shellExec(`sudo umount ${nfsHostPath}/dev/pts`);
2620
- shellExec(`sudo umount ${nfsHostPath}/dev`);
2621
- shellExec(`sudo umount ${nfsHostPath}/run`);
2622
- // shellExec(`sudo umount ${nfsHostPath}/lib/modules`);
2623
- break;
2624
- }
2625
1328
 
2626
1329
  case 'mount': {
2627
1330
  const mounts = shellExec(`mount`).split(`\n`);
@@ -3012,14 +1715,6 @@ nvidia/gpu-operator \
3012
1715
  // sudo yum install sbt
3013
1716
  break;
3014
1717
  }
3015
-
3016
- case 'chrony': {
3017
- shellExec(`sudo dnf install chrony -y`);
3018
- // debian chroot: sudo apt install chrony
3019
- for (const cmd of chronySetUp(`/etc/chrony.conf`)) shellExec(cmd);
3020
-
3021
- break;
3022
- }
3023
1718
  }
3024
1719
  } catch (error) {
3025
1720
  logger.error(error, error.stack);