underpost 2.8.64 → 2.8.67

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 (51) hide show
  1. package/.vscode/extensions.json +3 -2
  2. package/.vscode/settings.json +2 -0
  3. package/CHANGELOG.md +24 -4
  4. package/README.md +39 -2
  5. package/bin/deploy.js +1205 -131
  6. package/bin/file.js +8 -0
  7. package/bin/index.js +1 -233
  8. package/cli.md +451 -0
  9. package/docker-compose.yml +1 -1
  10. package/jsdoc.json +1 -1
  11. package/manifests/calico-custom-resources.yaml +25 -0
  12. package/manifests/deployment/adminer/deployment.yaml +32 -0
  13. package/manifests/deployment/adminer/kustomization.yaml +7 -0
  14. package/manifests/deployment/adminer/service.yaml +13 -0
  15. package/manifests/mongodb-4.4/service-deployment.yaml +1 -1
  16. package/manifests/postgresql/configmap.yaml +9 -0
  17. package/manifests/postgresql/kustomization.yaml +10 -0
  18. package/manifests/postgresql/pv.yaml +15 -0
  19. package/manifests/postgresql/pvc.yaml +13 -0
  20. package/manifests/postgresql/service.yaml +10 -0
  21. package/manifests/postgresql/statefulset.yaml +37 -0
  22. package/manifests/valkey/statefulset.yaml +6 -4
  23. package/package.json +3 -9
  24. package/src/api/user/user.service.js +13 -10
  25. package/src/cli/cluster.js +113 -11
  26. package/src/cli/db.js +18 -8
  27. package/src/cli/deploy.js +157 -58
  28. package/src/cli/fs.js +14 -3
  29. package/src/cli/image.js +0 -68
  30. package/src/cli/index.js +312 -0
  31. package/src/cli/monitor.js +170 -26
  32. package/src/cli/repository.js +5 -2
  33. package/src/client/components/core/Account.js +3 -3
  34. package/src/client/components/core/CalendarCore.js +0 -1
  35. package/src/client/components/core/Css.js +0 -1
  36. package/src/client/components/core/CssCore.js +2 -0
  37. package/src/client/components/core/EventsUI.js +1 -1
  38. package/src/client/components/core/JoyStick.js +2 -2
  39. package/src/client/components/core/Modal.js +1 -0
  40. package/src/client/components/core/RichText.js +1 -11
  41. package/src/index.js +9 -8
  42. package/src/mailer/MailerProvider.js +3 -0
  43. package/src/server/client-build.js +13 -0
  44. package/src/server/conf.js +48 -0
  45. package/src/server/dns.js +47 -17
  46. package/src/server/json-schema.js +77 -0
  47. package/src/server/peer.js +2 -2
  48. package/src/server/proxy.js +4 -4
  49. package/src/server/runtime.js +24 -9
  50. package/src/server/start.js +122 -0
  51. package/src/server/valkey.js +25 -11
package/bin/deploy.js CHANGED
@@ -26,15 +26,23 @@ import {
26
26
  fixDependencies,
27
27
  setUpProxyMaintenanceServer,
28
28
  writeEnv,
29
+ getUnderpostRootPath,
29
30
  } from '../src/server/conf.js';
30
31
  import { buildClient } from '../src/server/client-build.js';
31
- import { range, setPad, timer, uniqueArray } from '../src/client/components/core/CommonJs.js';
32
+ import { range, s4, setPad, timer, uniqueArray } from '../src/client/components/core/CommonJs.js';
32
33
  import { MongooseDB } from '../src/db/mongo/MongooseDB.js';
33
34
  import { Lampp } from '../src/runtime/lampp/Lampp.js';
34
35
  import { DefaultConf } from '../conf.js';
35
36
  import { JSONweb } from '../src/server/client-formatted.js';
36
- import ejs from 'easy-json-schema';
37
+
37
38
  import { Xampp } from '../src/runtime/xampp/Xampp.js';
39
+ import { ejs } from '../src/server/json-schema.js';
40
+ import { buildCliDoc } from '../src/cli/index.js';
41
+ import { getLocalIPv4Address, ip } from '../src/server/dns.js';
42
+ import { Downloader } from '../src/server/downloader.js';
43
+ import colors from 'colors';
44
+
45
+ colors.enable();
38
46
 
39
47
  const logger = loggerFactory(import.meta);
40
48
 
@@ -42,6 +50,82 @@ logger.info('argv', process.argv);
42
50
 
43
51
  const [exe, dir, operator] = process.argv;
44
52
 
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
+
45
129
  try {
46
130
  switch (operator) {
47
131
  case 'save':
@@ -728,12 +812,12 @@ try {
728
812
  fs.readFileSync(`./src/index.js`, 'utf8').replaceAll(`${version}`, `${newVersion}`),
729
813
  'utf8',
730
814
  );
731
-
815
+ shellExec(`node bin/deploy cli-docs`);
732
816
  shellExec(`node bin/deploy update-dependencies`);
733
817
  shellExec(`auto-changelog`);
734
818
  shellExec(`node bin/build dd`);
735
- shellExec(`node bin deploy dd --build-manifest --sync --info-router`);
736
- shellExec(`node bin deploy dd production --build-manifest --sync --info-router`);
819
+ shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd`);
820
+ shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd production`);
737
821
  break;
738
822
  }
739
823
 
@@ -863,188 +947,1178 @@ ${shellExec(`git log | grep Author: | sort -u`, { stdout: true }).split(`\n`).jo
863
947
 
864
948
  break;
865
949
  }
866
- case 'ssh-export-server-keys': {
867
- fs.copyFile('/etc/ssh/ssh_host_rsa_key', './engine-private/deploy/ssh_host_rsa_key');
868
- fs.copyFile('/etc/ssh/ssh_host_rsa_key.pub', './engine-private/deploy/ssh_host_rsa_key.pub');
950
+
951
+ case 'ssh': {
952
+ const host = process.argv[3] ?? `root@${await ip.public.ipv4()}`;
953
+ const domain = host.split('@')[1];
954
+ const user = 'root'; // host.split('@')[0];
955
+ const password = process.argv[4] ?? '';
956
+ const port = 22;
957
+
958
+ const setUpSSH = () => {
959
+ // Required port forwarding mapping
960
+ // ssh TCP 2222 22 <local-server-ip>
961
+ // ssh UDP 2222 22 <local-server-ip>
962
+
963
+ // Remote connect via public key
964
+ // ssh -i <key-path> <user>@<host>:2222
965
+
966
+ shellExec(`cat ./engine-private/deploy/id_rsa.pub > ~/.ssh/authorized_keys`);
967
+
968
+ // local trust on first use validator
969
+ // check ~/.ssh/known_hosts
970
+
971
+ // shellExec(`sudo sed -i -e "s@#PasswordAuthentication yes@PasswordAuthentication no@g" /etc/ssh/sshd_config`);
972
+ // shellExec(`sudo sed -i -e "s@#UsePAM no@UsePAM yes@g" /etc/ssh/sshd_config`);
973
+
974
+ // Include /etc/ssh/sshd_config.d/*.conf
975
+ // sudo tee /etc/ssh/sshd_config.d/99-custom.conf
976
+ shellExec(`sudo tee /etc/ssh/sshd_config <<EOF
977
+ PasswordAuthentication no
978
+ ChallengeResponseAuthentication yes
979
+ UsePAM yes
980
+ PubkeyAuthentication Yes
981
+ RSAAuthentication Yes
982
+ PermitRootLogin Yes
983
+ X11Forwarding yes
984
+ X11DisplayOffset 10
985
+ LoginGraceTime 120
986
+ StrictModes yes
987
+ SyslogFacility AUTH
988
+ LogLevel INFO
989
+ #HostKey /etc/ssh/ssh_host_ecdsa_key
990
+ HostKey /etc/ssh/ssh_host_ed25519_key
991
+ #HostKey /etc/ssh/ssh_host_rsa_key
992
+ AuthorizedKeysFile ~/.ssh/authorized_keys
993
+ Subsystem sftp /usr/libexec/openssh/sftp-server
994
+ ListenAddress 0.0.0.0
995
+ ListenAddress ::
996
+ ListenAddress ${domain}
997
+ ListenAddress ${domain}:22
998
+ EOF`);
999
+
1000
+ shellExec(`sudo chmod 700 ~/.ssh/`);
1001
+ shellExec(`sudo chmod 600 ~/.ssh/authorized_keys`);
1002
+ shellExec(`sudo chmod 644 ~/.ssh/known_hosts`);
1003
+ shellExec(`chown -R ${user}:${user} ~/.ssh`);
1004
+
1005
+ shellExec(`ufw allow ${port}/tcp`);
1006
+ shellExec(`ufw allow ${port}/udp`);
1007
+ shellExec(`ufw allow ssh`);
1008
+ shellExec(`ufw allow from 192.168.0.0/16 to any port 22`);
1009
+
1010
+ // active ssh-agent
1011
+ shellExec('eval `ssh-agent -s`' + ` && ssh-add ~/.ssh/id_rsa` + ` && ssh-add -l`);
1012
+ // remove all
1013
+ // shellExec(`ssh-add -D`);
1014
+ // remove single
1015
+ // shellExec(`ssh-add -d ~/.ssh/id_rsa`);
1016
+
1017
+ // shellExec(`echo "@${host.split(`@`)[1]} * $(cat ~/.ssh/id_rsa.pub)" > ~/.ssh/known_hosts`);
1018
+ shellExec('eval `ssh-agent -s`' + `&& ssh-keyscan -H -t ed25519 ${host.split(`@`)[1]} > ~/.ssh/known_hosts`);
1019
+ // shellExec(`sudo echo "" > ~/.ssh/known_hosts`);
1020
+
1021
+ // ssh-copy-id -i ~/.ssh/id_rsa.pub -p <port_number> <username>@<host>
1022
+ shellExec(`ssh-copy-id -i ~/.ssh/id_rsa.pub -p ${port} ${host}`);
1023
+ // debug:
1024
+ // shellExec(`ssh -vvv ${host}`);
1025
+
1026
+ shellExec(`sudo cp ./engine-private/deploy/id_rsa ~/.ssh/id_rsa`);
1027
+ shellExec(`sudo cp ./engine-private/deploy/id_rsa.pub ~/.ssh/id_rsa.pub`);
1028
+
1029
+ shellExec(`sudo echo "" > /etc/ssh/ssh_host_ecdsa_key`);
1030
+ shellExec(`sudo cp ./engine-private/deploy/id_rsa /etc/ssh/ssh_host_ed25519_key`);
1031
+ shellExec(`sudo echo "" > /etc/ssh/ssh_host_rsa_key`);
1032
+
1033
+ shellExec(`sudo echo "" > /etc/ssh/ssh_host_ecdsa_key.pub`);
1034
+ shellExec(`sudo cp ./engine-private/deploy/id_rsa.pub /etc/ssh/ssh_host_ed25519_key.pub`);
1035
+ shellExec(`sudo echo "" > /etc/ssh/ssh_host_rsa_key.pub`);
1036
+
1037
+ shellExec(`sudo systemctl enable sshd`);
1038
+ shellExec(`sudo systemctl restart sshd`);
1039
+
1040
+ const status = shellExec(`sudo systemctl status sshd`, { silent: true, stdout: true });
1041
+ console.log(
1042
+ status.match('running') ? status.replaceAll(`running`, `running`.green) : `ssh service not running`.red,
1043
+ );
1044
+ };
1045
+
1046
+ if (process.argv.includes('import')) {
1047
+ setUpSSH();
1048
+ break;
1049
+ }
1050
+
1051
+ shellExec(`sudo rm -rf ./id_rsa`);
1052
+ shellExec(`sudo rm -rf ./id_rsa.pub`);
1053
+
1054
+ if (process.argv.includes('legacy'))
1055
+ shellExec(`ssh-keygen -t rsa -b 4096 -f id_rsa -N "${password}" -q -C "${host}"`);
1056
+ else shellExec(`ssh-keygen -t ed25519 -f id_rsa -N "${password}" -q -C "${host}"`);
1057
+
1058
+ shellExec(`sudo cp ./id_rsa ~/.ssh/id_rsa`);
1059
+ shellExec(`sudo cp ./id_rsa.pub ~/.ssh/id_rsa.pub`);
1060
+
1061
+ shellExec(`sudo cp ./id_rsa ./engine-private/deploy/id_rsa`);
1062
+ shellExec(`sudo cp ./id_rsa.pub ./engine-private/deploy/id_rsa.pub`);
1063
+
1064
+ shellExec(`sudo rm -rf ./id_rsa`);
1065
+ shellExec(`sudo rm -rf ./id_rsa.pub`);
1066
+ setUpSSH();
869
1067
  break;
870
1068
  }
871
- case 'ssh-import-server-keys': {
872
- fs.copyFile('./engine-private/deploy/ssh_host_rsa_key', '/etc/ssh/ssh_host_rsa_key');
873
- fs.copyFile('./engine-private/deploy/ssh_host_rsa_key.pub', '/etc/ssh/ssh_host_rsa_key.pub');
1069
+
1070
+ case 'valkey': {
1071
+ if (!process.argv.includes('server')) {
1072
+ if (process.argv.includes('rocky')) {
1073
+ // shellExec(`yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm`);
1074
+ // shellExec(`sudo percona-release enable valkey experimental`);
1075
+ shellExec(`sudo dnf install valkey`);
1076
+ shellExec(`chown -R valkey:valkey /etc/valkey`);
1077
+ shellExec(`chown -R valkey:valkey /var/lib/valkey`);
1078
+ shellExec(`chown -R valkey:valkey /var/log/valkey`);
1079
+ shellExec(`sudo systemctl enable valkey.service`);
1080
+ shellExec(`sudo systemctl start valkey`);
1081
+ shellExec(`valkey-cli ping`);
1082
+ } else {
1083
+ shellExec(`cd /home/dd && git clone https://github.com/valkey-io/valkey.git`);
1084
+ shellExec(`cd /home/dd/valkey && make`);
1085
+ shellExec(`apt install valkey-tools`); // valkey-cli
1086
+ }
1087
+ }
1088
+ if (process.argv.includes('rocky')) {
1089
+ shellExec(`sudo systemctl stop valkey`);
1090
+ shellExec(`sudo systemctl start valkey`);
1091
+ } else shellExec(`cd /home/dd/valkey && ./src/valkey-server`);
1092
+
874
1093
  break;
875
1094
  }
876
- case 'ssh-import-client-keys': {
877
- const host = process.argv[3];
1095
+
1096
+ case 'valkey-service': {
1097
+ shellExec(`pm2 start bin/deploy.js --node-args=\"--max-old-space-size=8192\" --name valkey -- valkey server`);
1098
+ break;
1099
+ }
1100
+
1101
+ case 'update-instances': {
1102
+ shellExec(`node bin deploy dd production --sync --build-manifest --info-router --dashboard-update`);
1103
+ shellExec(`node bin cron --dashboard-update --init`);
1104
+ const deployId = 'dd-core';
1105
+ const host = 'www.nexodev.org';
1106
+ const path = '/';
1107
+
1108
+ {
1109
+ const outputPath = './engine-private/instances';
1110
+ if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
1111
+ const collection = 'instances';
1112
+ if (process.argv.includes('export'))
1113
+ shellExec(
1114
+ `node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
1115
+ );
1116
+ if (process.argv.includes('import'))
1117
+ shellExec(
1118
+ `node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
1119
+ );
1120
+ }
1121
+ {
1122
+ const outputPath = './engine-private/crons';
1123
+ if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
1124
+ const collection = 'crons';
1125
+ if (process.argv.includes('export'))
1126
+ shellExec(
1127
+ `node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
1128
+ );
1129
+ if (process.argv.includes('import'))
1130
+ shellExec(
1131
+ `node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
1132
+ );
1133
+ }
1134
+
1135
+ break;
1136
+ }
1137
+
1138
+ case 'cli-docs': {
1139
+ buildCliDoc();
1140
+ break;
1141
+ }
1142
+
1143
+ case 'monitor': {
878
1144
  shellExec(
879
- `node bin/deploy set-ssh-keys ./engine-private/deploy/ssh_host_rsa_key ${host ? ` ${host}` : ``} ${
880
- process.argv.includes('clean') ? 'clean' : ''
1145
+ `node bin monitor ${process.argv[6] === 'sync' ? '--sync ' : ''}--type ${process.argv[3]} ${process.argv[4]} ${
1146
+ process.argv[5]
881
1147
  }`,
1148
+ {
1149
+ async: true,
1150
+ },
882
1151
  );
883
1152
  break;
884
1153
  }
885
- case 'ssh-keys': {
886
- // create ssh keys
887
- const sshAccount = process.argv[3]; // [sudo username]@[host/ip]
888
- const destPath = process.argv[4];
889
- // shellExec(`ssh-keygen -t ed25519 -C "${sshAccount}" -f ${destPath}`);
890
- if (fs.existsSync(destPath)) {
891
- fs.removeSync(destPath);
892
- fs.removeSync(destPath + '.pub');
1154
+
1155
+ case 'postgresql': {
1156
+ if (process.argv.includes('install')) {
1157
+ shellExec(`sudo dnf install -y postgresql-server postgresql`);
1158
+ shellExec(`sudo postgresql-setup --initdb`);
1159
+ shellExec(`chown postgres /var/lib/pgsql/data`);
1160
+ shellExec(`sudo systemctl enable postgresql.service`);
1161
+ shellExec(`sudo systemctl start postgresql.service`);
1162
+ } else {
1163
+ shellExec(`sudo systemctl enable postgresql.service`);
1164
+ shellExec(`sudo systemctl restart postgresql.service`);
893
1165
  }
894
- shellExec(`ssh-keygen -t rsa -b 4096 -C "${sshAccount}" -f ${destPath}`);
895
- // add host to keyscan
896
- // shellExec(`ssh-keyscan -t rsa ${sshAccount.split(`@`)[1]} >> ~/.ssh/known_hosts`);
1166
+
1167
+ shellExec(`sudo systemctl status postgresql.service`);
1168
+
1169
+ // sudo systemctl stop postgresql
1170
+ // sudo systemctl disable postgresql
1171
+
1172
+ // psql login
1173
+ // psql -U <user> -h 127.0.0.1 -W <db-name>
1174
+
1175
+ // gedit /var/lib/pgsql/data/pg_hba.conf
1176
+ // host <db-name> <db-user> <db-host> md5
1177
+ // local all postgres trust
1178
+ // # "local" is for Unix domain socket connections only
1179
+ // local all all md5
1180
+ // # IPv4 local connections:
1181
+ // host all all 127.0.0.1/32 md5
1182
+ // # IPv6 local connections:
1183
+ // host all all ::1/128 md5
1184
+
1185
+ // gedit /var/lib/pgsql/data/postgresql.conf
1186
+ // listen_addresses = '*'
1187
+
1188
+ break;
1189
+ }
1190
+
1191
+ case 'postgresql-14': {
1192
+ shellExec(`sudo /usr/pgsql-14/bin/postgresql-14-setup initdb`);
1193
+ shellExec(`sudo systemctl start postgresql-14`);
1194
+ shellExec(`sudo systemctl enable postgresql-14`);
1195
+ shellExec(`sudo systemctl status postgresql-14`);
1196
+ // sudo dnf install postgresql14-contrib
1197
+ break;
1198
+ }
1199
+
1200
+ case 'pg-list-db': {
1201
+ shellExec(`sudo -i -u postgres psql -c "\\l"`);
1202
+ break;
1203
+ }
1204
+
1205
+ case 'pg-list-table': {
1206
+ shellExec(`sudo -i -u postgres psql -c "\\dt *.*"`);
1207
+ // schema_name.*
1208
+ break;
1209
+ }
1210
+ case 'pg-drop-db': {
1211
+ shellExec(`sudo -i -u postgres psql -c "DROP DATABASE ${process.argv[3]} WITH (FORCE)"`);
1212
+ shellExec(`sudo -i -u postgres psql -c "DROP USER ${process.argv[4]}"`);
897
1213
  break;
898
1214
  }
899
1215
 
900
- case 'set-ssh-keys': {
901
- const files = ['authorized_keys', 'id_rsa', 'id_rsa.pub', 'known_hosts ', 'known_hosts.old'];
1216
+ case 'maas': {
1217
+ dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1218
+ const IP_ADDRESS = getLocalIPv4Address();
1219
+ const serverip = IP_ADDRESS;
1220
+ const tftpRoot = process.env.TFTP_ROOT;
1221
+ const ipaddr = process.env.RPI4_IP;
1222
+ const netmask = process.env.NETMASK;
1223
+ const gatewayip = process.env.GATEWAY_IP;
1224
+
1225
+ let resources;
1226
+ try {
1227
+ resources = JSON.parse(
1228
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-resources read`, {
1229
+ silent: true,
1230
+ stdout: true,
1231
+ }),
1232
+ ).map((o) => ({
1233
+ id: o.id,
1234
+ name: o.name,
1235
+ architecture: o.architecture,
1236
+ }));
1237
+ } catch (error) {
1238
+ logger.error(error);
1239
+ }
1240
+
1241
+ const machineFactory = (m) => ({
1242
+ system_id: m.interface_set[0].system_id,
1243
+ mac_address: m.interface_set[0].mac_address,
1244
+ hostname: m.hostname,
1245
+ status_name: m.status_name,
1246
+ });
1247
+
1248
+ let machines;
1249
+ try {
1250
+ machines = JSON.parse(
1251
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machines read`, {
1252
+ stdout: true,
1253
+ silent: true,
1254
+ }),
1255
+ ).map((m) => machineFactory(m));
1256
+ } catch (error) {
1257
+ logger.error(error);
1258
+ }
1259
+
1260
+ if (process.argv.includes('db')) {
1261
+ // DROP, ALTER, CREATE, WITH ENCRYPTED
1262
+ // sudo -u <user> -h <host> psql <db-name>
1263
+ shellExec(`DB_PG_MAAS_NAME=${process.env.DB_PG_MAAS_NAME}`);
1264
+ shellExec(`DB_PG_MAAS_PASS=${process.env.DB_PG_MAAS_PASS}`);
1265
+ shellExec(`DB_PG_MAAS_USER=${process.env.DB_PG_MAAS_USER}`);
1266
+ shellExec(`DB_PG_MAAS_HOST=${process.env.DB_PG_MAAS_HOST}`);
1267
+ shellExec(
1268
+ `sudo -i -u postgres psql -c "CREATE USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1269
+ );
1270
+ shellExec(
1271
+ `sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1272
+ );
1273
+ const actions = ['LOGIN', 'SUPERUSER', 'INHERIT', 'CREATEDB', 'CREATEROLE', 'REPLICATION'];
1274
+ shellExec(`sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ${actions.join(' ')}"`);
1275
+ shellExec(`sudo -i -u postgres psql -c "\\du"`);
902
1276
 
903
- // > write
904
- // >> append
1277
+ shellExec(`sudo -i -u postgres createdb -O "$DB_PG_MAAS_USER" "$DB_PG_MAAS_NAME"`);
905
1278
 
906
- // /root/.ssh/id_rsa
907
- // /root/.ssh/id_rsa.pub
908
- if (process.argv.includes('clean')) {
909
- for (const file of files) {
910
- if (fs.existsSync(`/root/.ssh/${file}`)) {
911
- logger.info('remove', `/root/.ssh/${file}`);
912
- fs.removeSync(`/root/.ssh/${file}`);
1279
+ shellExec(`sudo -i -u postgres psql -c "\\l"`);
1280
+ }
1281
+
1282
+ if (process.argv.includes('ls')) {
1283
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-sources read`);
1284
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} commissioning-scripts read`);
1285
+ // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-source-selections read 60`);
1286
+ console.table(resources);
1287
+ console.table(machines);
1288
+ process.exit(0);
1289
+ }
1290
+
1291
+ // TODO: - Disable maas proxy (egress forwarding to public dns)
1292
+ // - Configure maas dns forwarding ${process.env.MAAS_DNS}
1293
+ // - Enable DNSSEC validation of upstream zones: Automatic (use default root key)
1294
+
1295
+ if (process.argv.includes('clear')) {
1296
+ for (const machine of machines) {
1297
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine delete ${machine.system_id}`);
1298
+ }
1299
+ // machines = [];
1300
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries clear all=true`);
1301
+ if (process.argv.includes('force')) {
1302
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries scan force=true`);
1303
+ }
1304
+ process.exit(0);
1305
+ }
1306
+ if (process.argv.includes('grub-arm64')) {
1307
+ shellExec(`sudo dnf install grub2-efi-aa64-modules`);
1308
+ shellExec(`sudo dnf install grub2-efi-x64-modules`);
1309
+ // sudo grub2-mknetdir --net-directory=${tftpRoot} --subdir=/boot/grub --module-path=/usr/lib/grub/arm64-efi arm64-efi
1310
+ process.exit(0);
1311
+ }
1312
+
1313
+ if (process.argv.includes('psql')) {
1314
+ const cmd = `psql -U ${process.env.DB_PG_MAAS_USER} -h ${process.env.DB_PG_MAAS_HOST} -W ${process.env.DB_PG_MAAS_NAME}`;
1315
+ pbcopy(cmd);
1316
+ process.exit(0);
1317
+ }
1318
+ if (process.argv.includes('logs')) {
1319
+ shellExec(`maas status`);
1320
+ const cmd = `journalctl -f -t dhcpd -u snap.maas.pebble.service`;
1321
+ pbcopy(cmd);
1322
+ process.exit(0);
1323
+ }
1324
+ if (process.argv.includes('reset')) {
1325
+ // shellExec(
1326
+ // `maas init region+rack --database-uri "postgres://$DB_PG_MAAS_USER:$DB_PG_MAAS_PASS@$DB_PG_MAAS_HOST/$DB_PG_MAAS_NAME"` +
1327
+ // ` --maas-url http://${IP_ADDRESS}:5240/MAAS`,
1328
+ // );
1329
+ const cmd =
1330
+ `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}"` +
1331
+ ` --maas-url http://${IP_ADDRESS}:5240/MAAS`;
1332
+ pbcopy(cmd);
1333
+ process.exit(0);
1334
+ }
1335
+ if (process.argv.includes('dhcp')) {
1336
+ const snippets = JSON.parse(
1337
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} dhcpsnippets read`, {
1338
+ stdout: true,
1339
+ silent: true,
1340
+ disableLog: true,
1341
+ }),
1342
+ );
1343
+ for (const snippet of snippets) {
1344
+ switch (snippet.name) {
1345
+ case 'arm64':
1346
+ snippet.value = snippet.value.split(`\n`);
1347
+ snippet.value[1] = ` filename "http://${IP_ADDRESS}:5248/images/bootloaders/uefi/arm64/grubaa64.efi";`;
1348
+ snippet.value[5] = ` filename "http://${IP_ADDRESS}:5248/images/bootloaders/uefi/arm64/grubaa64.efi";`;
1349
+ snippet.value = snippet.value.join(`\n`);
1350
+ shellExec(
1351
+ `maas ${process.env.MAAS_ADMIN_USERNAME} dhcpsnippet update ${snippet.name} value='${snippet.value}'`,
1352
+ );
1353
+ break;
1354
+
1355
+ default:
1356
+ break;
913
1357
  }
914
- fs.writeFileSync(`/root/.ssh/${file}`, '', 'utf8');
915
1358
  }
916
- shellExec('eval `ssh-agent -s`' + ` && ssh-add -D`);
1359
+
1360
+ console.log(snippets);
1361
+
1362
+ process.exit(0);
917
1363
  }
1364
+ // shellExec(`MAAS_ADMIN_USERNAME=${process.env.MAAS_ADMIN_USERNAME}`);
1365
+ // shellExec(`MAAS_ADMIN_EMAIL=${process.env.MAAS_ADMIN_EMAIL}`);
1366
+ // shellExec(`maas createadmin --username $MAAS_ADMIN_USERNAME --email $MAAS_ADMIN_EMAIL`);
1367
+
1368
+ // MaaS admin CLI:
1369
+ // maas login <maas-admin-username> http://localhost:5240/MAAS
1370
+ // paste GUI API KEY (profile section)
1371
+
1372
+ // Import custom image
1373
+ // maas <maas-admin-username> boot-resources create name='custom/RockyLinuxRpi4' \
1374
+ // title='RockyLinuxRpi4' \
1375
+ // architecture='arm64/generic' \
1376
+ // filetype='tgz' \
1377
+ // content@=/home/RockyLinuxRpi_9-latest.tar.gz
1378
+
1379
+ // Image boot resource:
1380
+ // /var/snap/maas/current/root/snap/maas
1381
+ // /var/snap/maas/common/maas/tftp_root
1382
+ // sudo chmod 755 /var/snap/maas/common/maas/tftp_root
1383
+
1384
+ // /var/snap/maas/common/maas/dhcpd.conf
1385
+ // sudo snap restart maas.pebble
1386
+
1387
+ // PXE Linux files:
1388
+ // /var/snap/maas/common/maas/image-storage/bootloaders/pxe/i386
1389
+ // sudo nmcli con modify <interface-device-name-connection-id> ethtool.feature-rx on ethtool.feature-tx off
1390
+ // sudo nmcli connection up <interface-device-name-connection-id>
1391
+
1392
+ // man nm-settings |grep feature-tx-checksum
1393
+
1394
+ // nmcli c modify <interface-device-name-connection-id> \
1395
+ // ethtool.feature-tx-checksum-fcoe-crc off \
1396
+ // ethtool.feature-tx-checksum-ip-generic off \
1397
+ // ethtool.feature-tx-checksum-ipv4 off \
1398
+ // ethtool.feature-tx-checksum-ipv6 off \
1399
+ // ethtool.feature-tx-checksum-sctp off
1400
+
1401
+ // Ensure Rocky NFS server and /etc/exports configured
1402
+ // sudo systemctl restart nfs-server
1403
+ // Check mounts: showmount -e <server-ip>
1404
+ // Check nfs ports: rpcinfo -p
1405
+ // sudo chown -R root:root ${process.env.NFS_EXPORT_PATH}/rpi4mb
1406
+ // sudo chmod 755 ${process.env.NFS_EXPORT_PATH}/rpi4mb
1407
+
1408
+ // tftp server
1409
+ // sudo chown -R root:root /var/snap/maas/common/maas/tftp_root/rpi4mb
1410
+
1411
+ // tftp client
1412
+ // sudo dnf install tftp
1413
+ // tftp <server-ip> -c get <path>
1414
+
1415
+ // Check firewall-cmd
1416
+ // firewall-cmd --permanent --add-service=rpc-bind
1417
+ // firewall-cmd --reload
1418
+
1419
+ // Image extension transform (.img.xz to .tar.gz):
1420
+ // tar -cvzf image-name.tar.gz image-name.img.xz
1421
+
1422
+ // Rocky network configuration:
1423
+ // /etc/NetworkManager/system-connections
1424
+
1425
+ // Rocky kernel params update
1426
+ // sudo grubby --args="<key>=<value> <key>=<value>" --update-kernel=ALL
1427
+ // sudo reboot now
1428
+
1429
+ // Temporal:
1430
+ // sudo snap install temporal
1431
+ // journalctl -u snap.maas.pebble -t maas-regiond
1432
+ // journalctl -u snap.maas.pebble -t maas-temporal -n 100 --no-pager -f
1433
+
1434
+ // Remove:
1435
+ // sudo dnf remove <package> -y; sudo dnf autoremove -y; sudo dnf clean packages
1436
+ // check: ~
1437
+ // check: ~./cache
1438
+ // check: ~./config
1439
+
1440
+ // Check file logs
1441
+ // grep -i -E -C 1 '<key-a>|<key-b>' /example.log | tail -n 600
1442
+
1443
+ // Back into your firmware setup (UEFI or BIOS config screen).
1444
+ // grub> fwsetup
1445
+
1446
+ // Poweroff:
1447
+ // grub > halt
1448
+ // initramfs > poweroff
1449
+
1450
+ // Check interface
1451
+ // ip link show
1452
+ // nmcli con show
1453
+
1454
+ let firmwarePath,
1455
+ tftpSubDir,
1456
+ kernelFilesPaths,
1457
+ name,
1458
+ architecture,
1459
+ resource,
1460
+ nfsConnectStr,
1461
+ etcExports,
1462
+ nfsServerRootPath,
1463
+ bootConf,
1464
+ zipFirmwareFileName,
1465
+ zipFirmwareName,
1466
+ zipFirmwareUrl,
1467
+ interfaceName,
1468
+ nfsHost;
1469
+
1470
+ switch (process.argv[3]) {
1471
+ case 'rpi4mb':
1472
+ const resourceId = process.argv[4] ?? '39';
1473
+ tftpSubDir = '/rpi4mb';
1474
+ zipFirmwareFileName = `RPi4_UEFI_Firmware_v1.41.zip`;
1475
+ zipFirmwareName = zipFirmwareFileName.split('.zip')[0];
1476
+ zipFirmwareUrl = `https://github.com/pftf/RPi4/releases/download/v1.41/RPi4_UEFI_Firmware_v1.41.zip`;
1477
+ firmwarePath = `../${zipFirmwareName}`;
1478
+ interfaceName = process.env.RPI4_INTERFACE_NAME;
1479
+ nfsHost = 'rpi4mb';
1480
+ if (!fs.existsSync(firmwarePath)) {
1481
+ await Downloader(zipFirmwareUrl, `../${zipFirmwareFileName}`);
1482
+ shellExec(`cd .. && mkdir ${zipFirmwareName} && cd ${zipFirmwareName} && unzip ../${zipFirmwareFileName}`);
1483
+ }
1484
+ resource = resources.find((o) => o.id == resourceId);
1485
+ name = resource.name;
1486
+ architecture = resource.architecture;
1487
+ resource = resources.find((o) => o.name === name && o.architecture === architecture);
1488
+ nfsServerRootPath = `${process.env.NFS_EXPORT_PATH}/rpi4mb`;
1489
+ // ,anonuid=1001,anongid=100
1490
+ // etcExports = `${nfsServerRootPath} *(rw,all_squash,sync,no_root_squash,insecure)`;
1491
+ etcExports = `${nfsServerRootPath} 192.168.1.0/24(${[
1492
+ 'rw',
1493
+ // 'all_squash',
1494
+ 'sync',
1495
+ 'no_root_squash',
1496
+ 'no_subtree_check',
1497
+ 'insecure',
1498
+ ]})`;
1499
+ const resourceData = JSON.parse(
1500
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-resource read ${resource.id}`, {
1501
+ stdout: true,
1502
+ silent: true,
1503
+ disableLog: true,
1504
+ }),
1505
+ );
1506
+ const bootFiles = resourceData.sets[Object.keys(resourceData.sets)[0]].files;
1507
+ const suffix = architecture.match('xgene') ? '.xgene' : '';
1508
+
1509
+ kernelFilesPaths = {
1510
+ 'vmlinuz-efi': bootFiles['boot-kernel' + suffix].filename_on_disk,
1511
+ 'initrd.img': bootFiles['boot-initrd' + suffix].filename_on_disk,
1512
+ squashfs: bootFiles['squashfs'].filename_on_disk,
1513
+ };
1514
+ const protocol = 'tcp'; // v3 -> tcp, v4 -> udp
1515
+
1516
+ const mountOptions = [
1517
+ protocol,
1518
+ 'vers=3',
1519
+ 'nfsvers=3',
1520
+ 'nolock',
1521
+ // 'protocol=tcp',
1522
+ // 'hard=true',
1523
+ 'port=2049',
1524
+ // 'sec=none',
1525
+ 'rw',
1526
+ 'hard',
1527
+ 'intr',
1528
+ 'rsize=32768',
1529
+ 'wsize=32768',
1530
+ 'acregmin=0',
1531
+ 'acregmax=0',
1532
+ 'acdirmin=0',
1533
+ 'acdirmax=0',
1534
+ 'noac',
1535
+ // 'nodev',
1536
+ // 'nosuid',
1537
+ ];
1538
+ const cmd = [
1539
+ `console=serial0,115200`,
1540
+ `console=tty1`,
1541
+ // `initrd=-1`,
1542
+ // `net.ifnames=0`,
1543
+ // `dwc_otg.lpm_enable=0`,
1544
+ // `elevator=deadline`,
1545
+ `root=/dev/nfs`,
1546
+ `nfsroot=${serverip}:${process.env.NFS_EXPORT_PATH}/rpi4mb,${mountOptions}`,
1547
+ // `nfsroot=${serverip}:${process.env.NFS_EXPORT_PATH}/rpi4mb`,
1548
+ `ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${nfsHost}:${interfaceName}:static`,
1549
+ `rootfstype=nfs`,
1550
+ `rw`,
1551
+ `rootwait`,
1552
+ `fixrtc`,
1553
+ 'initrd=initrd.img',
1554
+ // 'boot=casper',
1555
+ // 'ro',
1556
+ 'netboot=nfs',
1557
+ `cloud-config-url=/dev/null`,
1558
+ // 'ip=dhcp',
1559
+ // 'ip=dfcp',
1560
+ // 'autoinstall',
1561
+ // 'rd.break',
1562
+ ];
1563
+
1564
+ nfsConnectStr = cmd.join(' ');
1565
+ bootConf = `[all]
1566
+ MAC_ADDRESS=00:00:00:00:00:00
1567
+ MAC_ADDRESS_OTP=0,1
1568
+ BOOT_UART=0
1569
+ WAKE_ON_GPIO=1
1570
+ POWER_OFF_ON_HALT=0
1571
+ ENABLE_SELF_UPDATE=1
1572
+ DISABLE_HDMI=0
1573
+ TFTP_IP=${serverip}
1574
+ TFTP_PREFIX=1
1575
+ TFTP_PREFIX_STR=${tftpSubDir.slice(1)}/
1576
+ NET_INSTALL_ENABLED=1
1577
+ DHCP_TIMEOUT=45000
1578
+ DHCP_REQ_TIMEOUT=4000
1579
+ TFTP_FILE_TIMEOUT=30000
1580
+ BOOT_ORDER=0x21`;
918
1581
 
919
- const destPath = process.argv[3];
920
- const sshAuthKeyTarget = '/root/.ssh/authorized_keys';
921
- if (!fs.existsSync(sshAuthKeyTarget)) shellExec(`touch ${sshAuthKeyTarget}`);
922
- shellExec(`cat ${destPath}.pub > ${sshAuthKeyTarget}`);
923
- shellExec(`cat ${destPath} >> ${sshAuthKeyTarget}`);
1582
+ break;
924
1583
 
925
- if (!fs.existsSync('/root/.ssh/id_rsa')) shellExec(`touch ${'/root/.ssh/id_rsa'}`);
926
- shellExec(`cat ${destPath} > ${'/root/.ssh/id_rsa'}`);
1584
+ default:
1585
+ break;
1586
+ }
1587
+ shellExec(`sudo chmod 755 ${process.env.NFS_EXPORT_PATH}/${nfsHost}`);
1588
+
1589
+ shellExec(`sudo rm -rf ${tftpRoot}${tftpSubDir}`);
1590
+ shellExec(`sudo cp -a ${firmwarePath} ${tftpRoot}${tftpSubDir}`);
1591
+ shellExec(`mkdir -p ${tftpRoot}${tftpSubDir}/pxe`);
1592
+
1593
+ fs.writeFileSync(`/etc/exports`, etcExports, 'utf8');
1594
+ if (bootConf) fs.writeFileSync(`${tftpRoot}${tftpSubDir}/boot.conf`, bootConf, 'utf8');
1595
+
1596
+ shellExec(`node bin/deploy nfs`);
1597
+
1598
+ if (process.argv.includes('restart')) {
1599
+ shellExec(`sudo snap restart maas.pebble`);
1600
+ let secs = 0;
1601
+ while (
1602
+ !(
1603
+ shellExec(`maas status`, { silent: true, disableLog: true, stdout: true })
1604
+ .split(' ')
1605
+ .filter((l) => l.match('inactive')).length === 1
1606
+ )
1607
+ ) {
1608
+ await timer(1000);
1609
+ console.log(`Waiting... (${++secs}s)`);
1610
+ }
1611
+ }
927
1612
 
928
- if (!fs.existsSync('/root/.ssh/id_rsa.pub')) shellExec(`touch ${'/root/.ssh/id_rsa.pub'}`);
929
- shellExec(`cat ${destPath}.pub > ${'/root/.ssh/id_rsa.pub'}`);
1613
+ switch (process.argv[3]) {
1614
+ case 'rpi4mb':
1615
+ {
1616
+ // subnet DHCP snippets
1617
+ // # UEFI ARM64
1618
+ // if option arch = 00:0B {
1619
+ // filename "rpi4mb/pxe/grubaa64.efi";
1620
+ // }
1621
+ // elsif option arch = 00:13 {
1622
+ // filename "http://<IP_ADDRESS>:5248/images/bootloaders/uefi/arm64/grubaa64.efi";
1623
+ // option vendor-class-identifier "HTTPClient";
1624
+ // }
1625
+ for (const file of ['bootaa64.efi', 'grubaa64.efi']) {
1626
+ shellExec(
1627
+ `sudo cp -a /var/snap/maas/common/maas/image-storage/bootloaders/uefi/arm64/${file} ${tftpRoot}${tftpSubDir}/pxe/${file}`,
1628
+ );
1629
+ }
1630
+ // const file = 'bcm2711-rpi-4-b.dtb';
1631
+ // shellExec(
1632
+ // `sudo cp -a ${firmwarePath}/${file} /var/snap/maas/common/maas/image-storage/bootloaders/uefi/arm64/${file}`,
1633
+ // );
1634
+
1635
+ // const ipxeSrc = fs
1636
+ // .readFileSync(`${tftpRoot}/ipxe.cfg`, 'utf8')
1637
+ // .replaceAll('amd64', 'arm64')
1638
+ // .replaceAll('${next-server}', IP_ADDRESS);
1639
+ // fs.writeFileSync(`${tftpRoot}/ipxe.cfg`, ipxeSrc, 'utf8');
1640
+
1641
+ {
1642
+ for (const file of Object.keys(kernelFilesPaths)) {
1643
+ shellExec(
1644
+ `sudo cp -a /var/snap/maas/common/maas/image-storage/${kernelFilesPaths[file]} ${tftpRoot}${tftpSubDir}/pxe/${file}`,
1645
+ );
1646
+ }
1647
+ // const configTxtSrc = fs.readFileSync(`${firmwarePath}/config.txt`, 'utf8');
1648
+ // fs.writeFileSync(
1649
+ // `${tftpRoot}${tftpSubDir}/config.txt`,
1650
+ // configTxtSrc
1651
+ // .replace(`kernel=kernel8.img`, `kernel=vmlinuz`)
1652
+ // .replace(`# max_framebuffers=2`, `max_framebuffers=2`)
1653
+ // .replace(`initramfs initramfs8 followkernel`, `initramfs initrd.img followkernel`),
1654
+ // 'utf8',
1655
+ // );
1656
+
1657
+ // grub:
1658
+ // set root=(pxe)
1659
+
1660
+ // UNDERPOST.NET UEFI/GRUB/MAAS RPi4 commissioning (ARM64)
1661
+ const menuentryStr = 'underpost.net rpi4mb maas commissioning (ARM64)';
1662
+ const grubCfgPath = `${tftpRoot}/grub/grub.cfg`;
1663
+ fs.writeFileSync(
1664
+ grubCfgPath,
1665
+ `
1666
+ insmod gzio
1667
+ insmod http
1668
+ insmod nfs
1669
+ set timeout=5
1670
+ set default=0
1671
+
1672
+ menuentry '${menuentryStr}' {
1673
+ set root=(tftp,${serverip})
1674
+ linux ${tftpSubDir}/pxe/vmlinuz-efi ${nfsConnectStr}
1675
+ initrd ${tftpSubDir}/pxe/initrd.img
1676
+ boot
1677
+ }
1678
+
1679
+ `,
1680
+ 'utf8',
1681
+ );
1682
+ }
1683
+ const arm64EfiPath = `${tftpRoot}/grub/arm64-efi`;
1684
+ if (fs.existsSync(arm64EfiPath)) shellExec(`sudo rm -rf ${arm64EfiPath}`);
1685
+ shellExec(`sudo cp -a /usr/lib/grub/arm64-efi ${arm64EfiPath}`);
1686
+ }
1687
+
1688
+ break;
930
1689
 
931
- shellExec(`chmod 700 /root/.ssh/`);
932
- for (const file of files) {
933
- shellExec(`chmod 600 /root/.ssh/${file}`);
1690
+ default:
1691
+ break;
934
1692
  }
935
- const host = process.argv[4];
936
- // add key
937
- shellExec('eval `ssh-agent -s`' + ' && ssh-add /root/.ssh/id_rsa' + ' && ssh-add -l');
938
- if (host) shellExec(`ssh-keyscan -H ${host} >> ~/.ssh/known_hosts`);
939
- shellExec(`sudo systemctl enable ssh`);
940
- shellExec(`sudo systemctl restart ssh`);
941
- shellExec(`sudo systemctl status ssh`);
942
1693
 
1694
+ logger.info('succes maas deploy', {
1695
+ resource,
1696
+ kernelFilesPaths,
1697
+ tftpRoot,
1698
+ tftpSubDir,
1699
+ firmwarePath,
1700
+ etcExports,
1701
+ nfsServerRootPath,
1702
+ nfsConnectStr,
1703
+ });
1704
+ if (process.argv.includes('restart')) {
1705
+ if (fs.existsSync(`node engine-private/r.js`)) shellExec(`node engine-private/r`);
1706
+ shellExec(`node bin/deploy maas dhcp`);
1707
+ shellExec(`sudo chown -R root:root ${tftpRoot}`);
1708
+ shellExec(`sudo sudo chmod 755 ${tftpRoot}`);
1709
+ }
1710
+ // for (const machine of machines) {
1711
+ // // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine delete ${machine.system_id}`);
1712
+ // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine commission ${machine.system_id}`, {
1713
+ // silent: true,
1714
+ // });
1715
+ // }
1716
+ // machines = [];
1717
+
1718
+ const monitor = async () => {
1719
+ // discoveries Query observed discoveries.
1720
+ // discovery Read or delete an observed discovery.
1721
+
1722
+ const discoveries = JSON.parse(
1723
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries read`, {
1724
+ silent: true,
1725
+ stdout: true,
1726
+ }),
1727
+ ).filter(
1728
+ (o) => o.ip !== IP_ADDRESS && o.ip !== gatewayip && !machines.find((_o) => _o.mac_address === o.mac_address),
1729
+ );
1730
+
1731
+ // {
1732
+ // "discovery_id": "",
1733
+ // "ip": "192.168.1.189",
1734
+ // "mac_address": "00:00:00:00:00:00",
1735
+ // "last_seen": "2025-05-05T14:17:37.354",
1736
+ // "hostname": null,
1737
+ // "fabric_name": "",
1738
+ // "vid": null,
1739
+ // "mac_organization": "",
1740
+ // "observer": {
1741
+ // "system_id": "",
1742
+ // "hostname": "",
1743
+ // "interface_id": 1,
1744
+ // "interface_name": ""
1745
+ // },
1746
+ // "resource_uri": "/MAAS/api/2.0/discovery/MTkyLjE2OC4xLjE4OSwwMDowMDowMDowMDowMDowMA==/"
1747
+ // },
1748
+
1749
+ for (const discovery of discoveries) {
1750
+ const machine = {
1751
+ architecture: architecture.match('amd') ? 'amd64/generic' : 'arm64/generic',
1752
+ mac_address: discovery.mac_address,
1753
+ hostname: discovery.hostname ?? discovery.mac_organization ?? discovery.domain ?? `generic-host-${s4()}`,
1754
+ // discovery.ip.match(ipaddr)
1755
+ // ? nfsHost
1756
+ // : `unknown-${s4()}`,
1757
+ // description: '',
1758
+ // https://maas.io/docs/reference-power-drivers
1759
+ power_type: 'manual', // manual
1760
+ // power_parameters_power_address: discovery.ip,
1761
+ mac_addresses: discovery.mac_address,
1762
+ };
1763
+ machine.hostname = machine.hostname.replaceAll(' ', '').replaceAll('.', '');
1764
+
1765
+ try {
1766
+ let newMachine = shellExec(
1767
+ `maas ${process.env.MAAS_ADMIN_USERNAME} machines create ${Object.keys(machine)
1768
+ .map((k) => `${k}="${machine[k]}"`)
1769
+ .join(' ')}`,
1770
+ {
1771
+ silent: true,
1772
+ stdout: true,
1773
+ },
1774
+ );
1775
+ newMachine = machineFactory(JSON.parse(newMachine));
1776
+ machines.push(newMachine);
1777
+ console.log(newMachine);
1778
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine commission ${newMachine.system_id}`, {
1779
+ silent: true,
1780
+ });
1781
+ } catch (error) {
1782
+ logger.error(error, error.stack);
1783
+ }
1784
+ }
1785
+ // if (discoveries.length > 0) {
1786
+ // shellExec(
1787
+ // `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}'`,
1788
+ // );
1789
+ // }
1790
+ await timer(1000);
1791
+ monitor();
1792
+ };
1793
+ // shellExec(`node bin/deploy open-virtual-root ${architecture.match('amd') ? 'amd64' : 'arm64'} ${nfsHost}`);
1794
+ machines = [];
1795
+ shellExec(`node bin/deploy maas clear`);
1796
+ monitor();
943
1797
  break;
944
1798
  }
945
1799
 
946
- case 'ssh': {
947
- if (process.argv.includes('rocky')) {
948
- shellExec(`sudo systemctl enable sshd`);
1800
+ case 'nfs': {
1801
+ // Daemon RPC NFSv3. ports:
949
1802
 
950
- shellExec(`sudo systemctl start sshd`);
1803
+ // 2049 (TCP/UDP) nfsd standard port.
1804
+ // 111 (TCP/UDP) – rpcbind/portmapper.
1805
+ // 20048 (TCP/UDP) – rpc.mountd.
1806
+ // 32765 (TCP/UDP) – rpc.statd.
1807
+ // 32766 (TCP/UDP) – lockd (NLM).
951
1808
 
952
- shellExec(`sudo systemctl status sshd`);
1809
+ // Configure export and permissions:
1810
+ // /etc/exports
953
1811
 
954
- shellExec(`sudo ss -lt`);
955
- } else {
956
- if (!process.argv.includes('server')) {
957
- shellExec(`sudo apt update`);
958
- shellExec(`sudo apt install openssh-server -y`);
959
- shellExec(`sudo apt install ssh-askpass`);
960
- }
961
- shellExec(`sudo systemctl enable ssh`);
962
- shellExec(`sudo systemctl restart ssh`);
963
- shellExec(`sudo systemctl status ssh`);
964
- }
965
- // sudo service ssh restart
966
- shellExec(`ip a`);
1812
+ // Configure ports:
1813
+ // /etc/nfs.conf
1814
+
1815
+ fs.writeFileSync(
1816
+ `/etc/nfs.conf`,
1817
+ `
1818
+ [mountd]
1819
+ port = 20048
1820
+
1821
+ [statd]
1822
+ port = 32765
1823
+ outgoing-port = 32765
1824
+
1825
+ [nfsd]
1826
+ rdma=y
1827
+ rdma-port=20049
1828
+
1829
+ [lockd]
1830
+ port = 32766
1831
+ udp-port = 32766
1832
+ `,
1833
+ 'utf8',
1834
+ );
1835
+
1836
+ // Client users have read-only access to resources and are identified as anonymous on the server.
1837
+ // /share ip-client(ro,all_squash)
967
1838
 
968
- // adduser newuser
969
- // usermod -aG sudo newuser
1839
+ // Client users can modify resources and keep their UID on the server. Only root is identified as anonymous.
1840
+ // /share ip-client(rw)
970
1841
 
971
- // ssh -i '/path/to/keyfile' username@server
1842
+ // Users on client workstation 1 can modify resources, while those on client workstation 2 have read-only access.
1843
+ // UIDs are kept on the server, and only root is identified as anonymous.
1844
+ // /share ip-client1(rw) ip-client2(ro)
972
1845
 
973
- // ssh-keygen -t ed25519 -C "your_email@example.com" -f $HOME/.ssh/id_rsa
1846
+ // Client1 users can modify resources. Their UID is changed to 1001 and their GID to 100 on the server.
1847
+ // /share ip-client(rw,all_squash,anonuid=1001,anongid=100)
974
1848
 
975
- // legacy: ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -f $HOME/.ssh/id_rsa
1849
+ // sudo dnf install nfs-utils
1850
+ // sudo systemctl enable --now rpcbind // RPC map service
1851
+ // sudo systemctl enable --now nfs-server // nfs domains nfsd
976
1852
 
977
- // vi .ssh/authorized_keys
978
- // chmod 700 .ssh
979
- // chmod 600 authorized_keys
1853
+ // Update exports:
1854
+ // shellExec(`sudo exportfs -a -r`);
1855
+ // shellExec(`sudo exportfs -v`);
980
1856
 
981
- // cat id_rsa.pub > .ssh/authorized_keys
1857
+ // Active nfs
1858
+ shellExec(`sudo exportfs -s`);
982
1859
 
983
- // add public key to authorized keys
984
- // cat .ssh/id_ed25519.pub | ssh [sudo username]@[host/ip] 'cat >> .ssh/authorized_keys'
1860
+ shellExec(`sudo exportfs -rav`);
985
1861
 
986
- // 2. Open /etc/ssh/sshd_config file
987
- // nano /etc/ssh/sshd_config
1862
+ // Rocky enable virt_use_nfs
1863
+ // sudo setsebool -P virt_use_nfs 1
988
1864
 
989
- // 3. add example code to last line of file
990
- // Match User newuser
991
- // PasswordAuthentication yes
1865
+ // Disable share:
1866
+ // sudo exportfs -u <client-ip>:${process.env.NFS_EXPORT_PATH}/rpi4mb
992
1867
 
993
- // ssh [sudo username]@[host/ip]
994
- // open port 22
1868
+ // Nfs client:
1869
+ // mount -t nfs <server-ip>:/server-mnt /mnt
1870
+ // umount /mnt
1871
+
1872
+ shellExec(`sudo systemctl restart nfs-server`);
1873
+ break;
1874
+ }
1875
+ case 'update-virtual-root': {
1876
+ dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1877
+ const IP_ADDRESS = getLocalIPv4Address();
1878
+ const architecture = process.argv[3];
1879
+ const host = process.argv[4];
1880
+ const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
1881
+ const ipaddr = process.env.RPI4_IP;
1882
+ await updateVirtualRoot({
1883
+ IP_ADDRESS,
1884
+ architecture,
1885
+ host,
1886
+ nfsHostPath,
1887
+ ipaddr,
1888
+ });
1889
+ break;
1890
+ }
1891
+ case 'open-virtual-root': {
1892
+ dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1893
+ const IP_ADDRESS = getLocalIPv4Address();
1894
+ const architecture = process.argv[3];
1895
+ const host = process.argv[4];
1896
+ const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
1897
+ shellExec(`sudo dnf install -y iptables-legacy`);
1898
+ shellExec(`sudo dnf install -y debootstrap`);
1899
+ shellExec(`sudo dnf install kernel-modules-extra-$(uname -r)`);
1900
+ switch (architecture) {
1901
+ case 'arm64':
1902
+ shellExec(`sudo podman run --rm --privileged multiarch/qemu-user-static --reset -p yes`);
1903
+
1904
+ break;
1905
+
1906
+ default:
1907
+ break;
1908
+ }
1909
+
1910
+ shellExec(`sudo modprobe binfmt_misc`);
1911
+ shellExec(`sudo mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc`);
1912
+
1913
+ if (process.argv.includes('build')) {
1914
+ // shellExec(`depmod -a`);
1915
+ shellExec(`mkdir -p ${nfsHostPath}`);
1916
+ let cmd;
1917
+ switch (host) {
1918
+ case 'rpi4mb':
1919
+ shellExec(`sudo rm -rf ${nfsHostPath}/*`);
1920
+ shellExec(`sudo chown -R root:root ${nfsHostPath}`);
1921
+ cmd = [
1922
+ `sudo debootstrap`,
1923
+ `--arch=arm64`,
1924
+ `--variant=minbase`,
1925
+ `--foreign`, // arm64 on amd64
1926
+ `noble`,
1927
+ nfsHostPath,
1928
+ `http://ports.ubuntu.com/ubuntu-ports/`,
1929
+ ];
1930
+ break;
1931
+
1932
+ default:
1933
+ break;
1934
+ }
1935
+ shellExec(cmd.join(' '));
1936
+
1937
+ shellExec(`sudo podman create --name extract multiarch/qemu-user-static`);
1938
+ shellExec(`podman ps -a`);
1939
+ shellExec(`sudo podman cp extract:/usr/bin/qemu-aarch64-static ${nfsHostPath}/usr/bin/`);
1940
+ shellExec(`sudo podman rm extract`);
1941
+ shellExec(`podman ps -a`);
1942
+
1943
+ switch (host) {
1944
+ case 'rpi4mb':
1945
+ shellExec(`file ${nfsHostPath}/bin/bash`); // expected: ELF 64-bit LSB pie executable, ARM aarch64 …
1946
+ break;
1947
+
1948
+ default:
1949
+ break;
1950
+ }
1951
+
1952
+ shellExec(`sudo chroot ${nfsHostPath} /usr/bin/qemu-aarch64-static /bin/bash <<'EOF'
1953
+ /debootstrap/debootstrap --second-stage
1954
+ EOF`);
1955
+ }
1956
+ if (process.argv.includes('mount')) {
1957
+ shellExec(`sudo mount --bind /proc ${nfsHostPath}/proc`);
1958
+ shellExec(`sudo mount --bind /sys ${nfsHostPath}/sys`);
1959
+ shellExec(`sudo mount --rbind /dev ${nfsHostPath}/dev`);
1960
+ }
995
1961
 
996
- // init ssh agent service
997
- // eval `ssh-agent -s`
1962
+ if (process.argv.includes('build')) {
1963
+ switch (host) {
1964
+ case 'rpi4mb':
1965
+ const ipaddr = process.env.RPI4_IP;
998
1966
 
999
- // list keys
1000
- // ssh-add -l
1967
+ await updateVirtualRoot({
1968
+ IP_ADDRESS,
1969
+ architecture,
1970
+ host,
1971
+ nfsHostPath,
1972
+ ipaddr,
1973
+ });
1001
1974
 
1002
- // add key
1003
- // ssh-add /root/.ssh/id_rsa
1975
+ break;
1004
1976
 
1005
- // remove
1006
- // ssh-add -d /path/to/private/key
1977
+ default:
1978
+ break;
1979
+ }
1980
+ }
1981
+ // if (process.argv.includes('mount')) {
1982
+ // shellExec(`sudo mount --bind /lib/modules ${nfsHostPath}/lib/modules`);
1983
+ // }
1007
1984
 
1008
- // remove all
1009
- // ssh-add -D
1985
+ break;
1986
+ }
1010
1987
 
1011
- // sshpass -p ${{ secrets.PSWD }} ssh -o StrictHostKeyChecking=no -p 22 ${{ secrets.USER}}@${{ secrets.VPS_IP }} 'cd /home/adam && ./deploy.sh'
1988
+ case 'close-virtual-root': {
1989
+ const architecture = process.argv[3];
1990
+ const host = process.argv[4];
1991
+ const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
1992
+ shellExec(`sudo umount ${nfsHostPath}/proc`);
1993
+ shellExec(`sudo umount ${nfsHostPath}/sys`);
1994
+ shellExec(`sudo umount ${nfsHostPath}/dev`);
1995
+ // shellExec(`sudo umount ${nfsHostPath}/lib/modules`);
1996
+ break;
1997
+ }
1012
1998
 
1013
- // copies the public key of your default identity (use -i identity_file for other identities) to the remote host.
1014
- // ssh-copy-id user@hostname.example.com
1015
- // ssh-copy-id "user@hostname.example.com -p <port-number>"
1999
+ case 'mount': {
2000
+ const mounts = shellExec(`mount`).split(`\n`);
2001
+ console.table(
2002
+ mounts
2003
+ .filter((l) => l.trim())
2004
+ .map(
2005
+ (o) => (
2006
+ (o = o.split(' ')),
2007
+ {
2008
+ path: o[2],
2009
+ type: o[4],
2010
+ permissions: o[5],
2011
+ }
2012
+ ),
2013
+ ),
2014
+ );
2015
+ break;
2016
+ }
1016
2017
 
2018
+ case 'create-ports': {
2019
+ const cmd = [];
2020
+ const ipaddr = getLocalIPv4Address();
2021
+ for (const port of ['5240']) {
2022
+ const name = 'maas';
2023
+ cmd.push(`${name}:${port}-${port}:${ipaddr}`);
2024
+ }
2025
+ pbcopy(`node engine-private/r create-port ${cmd}`);
1017
2026
  break;
1018
2027
  }
1019
2028
 
1020
- case 'valkey': {
1021
- if (!process.argv.includes('server')) {
1022
- if (process.argv.includes('rocky')) {
1023
- // shellExec(`yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm`);
1024
- // shellExec(`sudo percona-release enable valkey experimental`);
1025
- shellExec(`sudo dnf install valkey`);
1026
- shellExec(`chown -R valkey:valkey /etc/valkey`);
1027
- shellExec(`chown -R valkey:valkey /var/lib/valkey`);
1028
- shellExec(`chown -R valkey:valkey /var/log/valkey`);
1029
- shellExec(`sudo systemctl enable valkey.service`);
1030
- shellExec(`sudo systemctl start valkey`);
1031
- shellExec(`valkey-cli ping`);
1032
- } else {
1033
- shellExec(`cd /home/dd && git clone https://github.com/valkey-io/valkey.git`);
1034
- shellExec(`cd /home/dd/valkey && make`);
1035
- shellExec(`apt install valkey-tools`); // valkey-cli
1036
- }
2029
+ case 'maas-ports': {
2030
+ // Configure firewall:
2031
+
2032
+ // systemctl stop firewalld
2033
+ // systemctl mask firewalld
2034
+
2035
+ // ufw disable
2036
+ // ufw enable
2037
+
2038
+ // sudo snap install ufw
2039
+ // const ports = ['80', '443', '22', '3000-3100'];
2040
+ const ports = [
2041
+ '43',
2042
+ '53',
2043
+ '60',
2044
+ '66',
2045
+ '67',
2046
+ '69',
2047
+ '4011',
2048
+ '111',
2049
+ '2049',
2050
+ '20048',
2051
+ '20049',
2052
+ '32765',
2053
+ '32766',
2054
+ '5248',
2055
+ '5240',
2056
+ ];
2057
+ for (const port of ports) {
2058
+ shellExec(`ufw allow ${port}/tcp`);
2059
+ shellExec(`ufw allow ${port}/udp`);
1037
2060
  }
1038
- if (process.argv.includes('rocky')) {
1039
- shellExec(`sudo systemctl stop valkey`);
1040
- shellExec(`sudo systemctl start valkey`);
1041
- } else shellExec(`cd /home/dd/valkey && ./src/valkey-server`);
2061
+
2062
+ shellExec(`sudo systemctl mask firewalld`);
1042
2063
 
1043
2064
  break;
1044
2065
  }
1045
2066
 
1046
- case 'valkey-service': {
1047
- shellExec(`pm2 start bin/deploy.js --node-args=\"--max-old-space-size=8192\" --name valkey -- valkey server`);
2067
+ case 'iptables': {
2068
+ shellExec(`sudo systemctl enable nftables`);
2069
+ shellExec(`sudo systemctl restart nftables`);
2070
+
2071
+ shellExec(`sudo tee /etc/nftables.conf <<EOF
2072
+ table inet filter {
2073
+ chain input {
2074
+ type filter hook input priority 0;
2075
+ policy drop;
2076
+ tcp dport 22 accept
2077
+ }
2078
+ }
2079
+ EOF`);
2080
+ shellExec(`sudo nft -f /etc/nftables.conf`);
2081
+
2082
+ // sudo systemctl stop nftables
2083
+ // sudo systemctl disable nftables
2084
+
2085
+ break;
2086
+ }
2087
+
2088
+ case 'rpi4': {
2089
+ // Rpi4 Run Bootloader:
2090
+
2091
+ // 1) create boot.conf
2092
+
2093
+ // 2) Run lite RPiOs from rpi-imager
2094
+ // with boot.conf files in root disk path
2095
+
2096
+ // 3) cd /boot/firmware && sudo rpi-eeprom-config --apply boot.conf
2097
+
2098
+ // 4) sudo reboot
2099
+
2100
+ // 5) check: 'vcgencmd bootloader_version'
2101
+ // 6) check: 'vcgencmd bootloader_config'
2102
+
2103
+ // 7) shutdown and restart without sd card
2104
+
2105
+ // sudo apt update
2106
+ // sudo apt install git
2107
+
2108
+ break;
2109
+ }
2110
+
2111
+ case 'blue': {
2112
+ // lsusb | grep blue -i
2113
+ // rfkill list
2114
+ // sudo service bluetooth start
2115
+ // bluetoothctl show
2116
+ // sudo rfkill unblock bluetooth
2117
+ // dmesg | grep -i bluetooth
2118
+ // journalctl -u bluetooth -f
2119
+ // sudo dnf update bluez bluez-libs bluez-utils
2120
+ // sudo rmmod btusb
2121
+ // sudo modprobe btusb
1048
2122
  break;
1049
2123
  }
1050
2124