underpost 2.8.1 → 2.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.dockerignore +1 -0
  2. package/.github/workflows/ghpkg.yml +19 -49
  3. package/.github/workflows/npmpkg.yml +67 -0
  4. package/.github/workflows/publish.yml +5 -5
  5. package/.github/workflows/pwa-microservices-template.page.yml +12 -4
  6. package/.github/workflows/pwa-microservices-template.test.yml +2 -2
  7. package/.vscode/extensions.json +18 -71
  8. package/.vscode/settings.json +20 -3
  9. package/AUTHORS.md +16 -5
  10. package/CHANGELOG.md +123 -3
  11. package/Dockerfile +27 -70
  12. package/README.md +39 -29
  13. package/bin/build.js +186 -0
  14. package/bin/db.js +2 -24
  15. package/bin/deploy.js +1467 -236
  16. package/bin/file.js +67 -16
  17. package/bin/hwt.js +0 -10
  18. package/bin/index.js +1 -77
  19. package/bin/ssl.js +19 -11
  20. package/bin/util.js +9 -104
  21. package/bin/vs.js +26 -2
  22. package/cli.md +451 -0
  23. package/conf.js +29 -138
  24. package/docker-compose.yml +1 -1
  25. package/jsdoc.json +1 -1
  26. package/manifests/calico-custom-resources.yaml +25 -0
  27. package/manifests/deployment/adminer/deployment.yaml +32 -0
  28. package/manifests/deployment/adminer/kustomization.yaml +7 -0
  29. package/manifests/deployment/adminer/service.yaml +13 -0
  30. package/manifests/deployment/fastapi/backend-deployment.yml +120 -0
  31. package/manifests/deployment/fastapi/backend-service.yml +19 -0
  32. package/manifests/deployment/fastapi/frontend-deployment.yml +54 -0
  33. package/manifests/deployment/fastapi/frontend-service.yml +15 -0
  34. package/manifests/deployment/kafka/deployment.yaml +69 -0
  35. package/manifests/deployment/mongo-express/deployment.yaml +60 -0
  36. package/manifests/deployment/phpmyadmin/deployment.yaml +54 -0
  37. package/manifests/kind-config-dev.yaml +12 -0
  38. package/manifests/kind-config.yaml +12 -0
  39. package/manifests/kubeadm-calico-config.yaml +119 -0
  40. package/manifests/letsencrypt-prod.yaml +15 -0
  41. package/manifests/mariadb/config.yaml +10 -0
  42. package/manifests/mariadb/kustomization.yaml +9 -0
  43. package/manifests/mariadb/pv.yaml +12 -0
  44. package/manifests/mariadb/pvc.yaml +10 -0
  45. package/manifests/mariadb/secret.yaml +8 -0
  46. package/manifests/mariadb/service.yaml +10 -0
  47. package/manifests/mariadb/statefulset.yaml +55 -0
  48. package/manifests/mongodb/backup-access.yaml +16 -0
  49. package/manifests/mongodb/backup-cronjob.yaml +42 -0
  50. package/manifests/mongodb/backup-pv-pvc.yaml +22 -0
  51. package/manifests/mongodb/configmap.yaml +26 -0
  52. package/manifests/mongodb/headless-service.yaml +10 -0
  53. package/manifests/mongodb/kustomization.yaml +11 -0
  54. package/manifests/mongodb/pv-pvc.yaml +23 -0
  55. package/manifests/mongodb/statefulset.yaml +125 -0
  56. package/manifests/mongodb-4.4/kustomization.yaml +7 -0
  57. package/manifests/mongodb-4.4/pv-pvc.yaml +23 -0
  58. package/manifests/mongodb-4.4/service-deployment.yaml +63 -0
  59. package/manifests/postgresql/configmap.yaml +9 -0
  60. package/manifests/postgresql/kustomization.yaml +10 -0
  61. package/manifests/postgresql/pv.yaml +15 -0
  62. package/manifests/postgresql/pvc.yaml +13 -0
  63. package/manifests/postgresql/service.yaml +10 -0
  64. package/manifests/postgresql/statefulset.yaml +37 -0
  65. package/manifests/valkey/kustomization.yaml +7 -0
  66. package/manifests/valkey/service.yaml +17 -0
  67. package/manifests/valkey/statefulset.yaml +41 -0
  68. package/package.json +127 -136
  69. package/src/api/core/core.service.js +1 -1
  70. package/src/api/default/default.service.js +1 -1
  71. package/src/api/user/user.model.js +16 -3
  72. package/src/api/user/user.service.js +15 -12
  73. package/src/cli/cluster.js +389 -0
  74. package/src/cli/cron.js +121 -0
  75. package/src/cli/db.js +222 -0
  76. package/src/cli/deploy.js +487 -0
  77. package/src/cli/env.js +58 -0
  78. package/src/cli/fs.js +161 -0
  79. package/src/cli/image.js +66 -0
  80. package/src/cli/index.js +312 -0
  81. package/src/cli/monitor.js +236 -0
  82. package/src/cli/repository.js +128 -0
  83. package/src/cli/script.js +53 -0
  84. package/src/cli/secrets.js +37 -0
  85. package/src/cli/test.js +118 -0
  86. package/src/client/components/core/Account.js +28 -24
  87. package/src/client/components/core/Auth.js +22 -4
  88. package/src/client/components/core/Blockchain.js +1 -1
  89. package/src/client/components/core/CalendarCore.js +128 -121
  90. package/src/client/components/core/CommonJs.js +283 -19
  91. package/src/client/components/core/CssCore.js +16 -4
  92. package/src/client/components/core/Docs.js +1 -2
  93. package/src/client/components/core/DropDown.js +5 -1
  94. package/src/client/components/core/EventsUI.js +3 -3
  95. package/src/client/components/core/FileExplorer.js +86 -78
  96. package/src/client/components/core/Input.js +22 -6
  97. package/src/client/components/core/JoyStick.js +2 -2
  98. package/src/client/components/core/LoadingAnimation.js +3 -12
  99. package/src/client/components/core/LogIn.js +3 -3
  100. package/src/client/components/core/LogOut.js +1 -1
  101. package/src/client/components/core/Modal.js +54 -20
  102. package/src/client/components/core/Panel.js +109 -90
  103. package/src/client/components/core/PanelForm.js +23 -30
  104. package/src/client/components/core/Recover.js +3 -3
  105. package/src/client/components/core/RichText.js +1 -11
  106. package/src/client/components/core/Router.js +3 -1
  107. package/src/client/components/core/Scroll.js +1 -0
  108. package/src/client/components/core/SignUp.js +2 -2
  109. package/src/client/components/core/Translate.js +47 -9
  110. package/src/client/components/core/Validator.js +9 -1
  111. package/src/client/components/core/VanillaJs.js +0 -9
  112. package/src/client/components/core/Worker.js +34 -31
  113. package/src/client/components/default/RoutesDefault.js +3 -2
  114. package/src/client/services/core/core.service.js +15 -10
  115. package/src/client/services/default/default.management.js +46 -37
  116. package/src/client/ssr/Render.js +6 -1
  117. package/src/client/ssr/body/CacheControl.js +2 -3
  118. package/src/client/sw/default.sw.js +3 -3
  119. package/src/db/mongo/MongooseDB.js +29 -1
  120. package/src/index.js +101 -19
  121. package/src/mailer/MailerProvider.js +3 -0
  122. package/src/runtime/lampp/Dockerfile +65 -0
  123. package/src/runtime/lampp/Lampp.js +1 -13
  124. package/src/runtime/xampp/Xampp.js +0 -13
  125. package/src/server/auth.js +3 -3
  126. package/src/server/backup.js +49 -93
  127. package/src/server/client-build.js +49 -46
  128. package/src/server/client-formatted.js +6 -3
  129. package/src/server/conf.js +297 -55
  130. package/src/server/dns.js +75 -62
  131. package/src/server/downloader.js +0 -8
  132. package/src/server/json-schema.js +77 -0
  133. package/src/server/logger.js +15 -10
  134. package/src/server/network.js +20 -161
  135. package/src/server/peer.js +2 -2
  136. package/src/server/process.js +25 -2
  137. package/src/server/proxy.js +7 -29
  138. package/src/server/runtime.js +53 -40
  139. package/src/server/ssl.js +1 -1
  140. package/src/server/start.js +122 -0
  141. package/src/server/valkey.js +27 -11
  142. package/test/api.test.js +0 -8
  143. package/src/dns.js +0 -22
  144. package/src/server/prompt-optimizer.js +0 -28
  145. package/startup.js +0 -11
package/bin/deploy.js CHANGED
@@ -2,9 +2,8 @@ import fs from 'fs-extra';
2
2
  import axios from 'axios';
3
3
 
4
4
  import dotenv from 'dotenv';
5
- import plantuml from 'plantuml';
6
5
 
7
- import { shellCd, shellExec } from '../src/server/process.js';
6
+ import { pbcopy, shellCd, shellExec } from '../src/server/process.js';
8
7
  import { loggerFactory } from '../src/server/logger.js';
9
8
  import {
10
9
  Config,
@@ -26,16 +25,24 @@ import {
26
25
  restoreMacroDb,
27
26
  fixDependencies,
28
27
  setUpProxyMaintenanceServer,
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 simpleGit from 'simple-git';
32
+ import { range, s4, setPad, timer, uniqueArray } from '../src/client/components/core/CommonJs.js';
33
33
  import { MongooseDB } from '../src/db/mongo/MongooseDB.js';
34
34
  import { Lampp } from '../src/runtime/lampp/Lampp.js';
35
35
  import { DefaultConf } from '../conf.js';
36
36
  import { JSONweb } from '../src/server/client-formatted.js';
37
- import ejs from 'easy-json-schema';
37
+
38
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();
39
46
 
40
47
  const logger = loggerFactory(import.meta);
41
48
 
@@ -43,6 +50,82 @@ logger.info('argv', process.argv);
43
50
 
44
51
  const [exe, dir, operator] = process.argv;
45
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
+
46
129
  try {
47
130
  switch (operator) {
48
131
  case 'save':
@@ -139,32 +222,10 @@ try {
139
222
  }
140
223
  break;
141
224
  case 'conf': {
142
- loadConf(process.argv[3]);
143
- if (process.argv[4]) fs.writeFileSync(`.env`, fs.readFileSync(`.env.${process.argv[4]}`, 'utf8'), 'utf8');
225
+ loadConf(process.argv[3], process.argv[4]);
144
226
  break;
145
227
  }
146
- case 'run':
147
- {
148
- if (process.argv.includes('replicas')) {
149
- const deployGroupId = getDeployGroupId();
150
- const dataDeploy = getDataDeploy({
151
- deployId: process.argv[3],
152
- buildSingleReplica: true,
153
- deployGroupId,
154
- });
155
- if (fs.existsSync(`./tmp/await-deploy`)) fs.remove(`./tmp/await-deploy`);
156
- await deployRun(dataDeploy);
157
- } else {
158
- loadConf(process.argv[3]);
159
- shellExec(`npm start ${process.argv.includes('maintenance') ? 'maintenance' : ''}`);
160
- }
161
- }
162
- break;
163
228
 
164
- case 'remove-await-deploy': {
165
- if (fs.existsSync(`./tmp/await-deploy`)) fs.remove(`./tmp/await-deploy`);
166
- break;
167
- }
168
229
  case 'new-nodejs-app':
169
230
  {
170
231
  const deployId = process.argv[3];
@@ -231,6 +292,7 @@ try {
231
292
  break;
232
293
  case 'build-full-client':
233
294
  {
295
+ dotenv.config({ override: true });
234
296
  if (!process.argv[3]) process.argv[3] = 'default';
235
297
  const { deployId, folder } = loadConf(process.argv[3]);
236
298
 
@@ -256,20 +318,7 @@ try {
256
318
  serverConf[host][path].replicas.map((replica) => buildReplicaId({ deployId, replica })),
257
319
  );
258
320
 
259
- shellExec(Cmd.replica(deployId, host, path));
260
- }
261
- if (serverConf[host][path].db) {
262
- switch (serverConf[host][path].db.provider) {
263
- case 'mariadb':
264
- {
265
- shellExec(`node bin/db ${host}${path} create ${deployId}`);
266
- }
267
-
268
- break;
269
-
270
- default:
271
- break;
272
- }
321
+ // shellExec(Cmd.replica(deployId, host, path));
273
322
  }
274
323
  }
275
324
  }
@@ -278,7 +327,7 @@ try {
278
327
  await buildClient();
279
328
 
280
329
  for (const replicaDeployId of deployIdSingleReplicas) {
281
- shellExec(Cmd.conf(replicaDeployId));
330
+ shellExec(Cmd.conf(replicaDeployId, process.env.NODE_ENV));
282
331
  shellExec(Cmd.build(replicaDeployId));
283
332
  }
284
333
  }
@@ -307,7 +356,7 @@ try {
307
356
  }
308
357
 
309
358
  case 'adminer': {
310
- const directory = '/dd/engine/public/adminer';
359
+ const directory = '/home/dd/engine/public/adminer';
311
360
  // const host = '127.0.0.1';
312
361
  const host = 'localhost';
313
362
  const port = 80;
@@ -337,7 +386,7 @@ try {
337
386
 
338
387
  case 'pma':
339
388
  {
340
- const directory = '/dd/engine/public/phpmyadmin';
389
+ const directory = '/home/dd/engine/public/phpmyadmin';
341
390
  // const host = '127.0.0.1';
342
391
  const host = 'localhost';
343
392
  const port = 80;
@@ -454,14 +503,16 @@ try {
454
503
  }
455
504
  break;
456
505
 
457
- case 'update-package':
506
+ case 'update-dependencies':
458
507
  const files = await fs.readdir(`./engine-private/conf`, { recursive: true });
459
508
  const originPackage = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
460
509
  for (const relativePath of files) {
461
510
  const filePah = `./engine-private/conf/${relativePath.replaceAll(`\\`, '/')}`;
462
511
  if (filePah.split('/').pop() === 'package.json') {
463
- originPackage.scripts.start = JSON.parse(fs.readFileSync(filePah), 'utf8').scripts.start;
464
- fs.writeFileSync(filePah, JSON.stringify(originPackage, null, 4), 'utf8');
512
+ const deployPackage = JSON.parse(fs.readFileSync(filePah), 'utf8');
513
+ deployPackage.dependencies = originPackage.dependencies;
514
+ deployPackage.devDependencies = originPackage.devDependencies;
515
+ fs.writeFileSync(filePah, JSON.stringify(deployPackage, null, 4), 'utf8');
465
516
  }
466
517
  }
467
518
  break;
@@ -469,12 +520,13 @@ try {
469
520
  case 'run-macro':
470
521
  {
471
522
  if (fs.existsSync(`./tmp/await-deploy`)) fs.remove(`./tmp/await-deploy`);
472
- const dataDeploy = getDataDeploy({ deployGroupId: process.argv[3], buildSingleReplica: true });
523
+ const dataDeploy = getDataDeploy({
524
+ deployGroupId: process.argv[3],
525
+ buildSingleReplica: true,
526
+ deployIdConcat: ['dd-proxy', 'dd-cron'],
527
+ });
473
528
  if (!process.argv[4]) await setUpProxyMaintenanceServer({ deployGroupId: process.argv[3] });
474
- await deployRun(
475
- process.argv[4] ? dataDeploy.filter((d) => d.deployId.match(process.argv[4])) : dataDeploy,
476
- true,
477
- );
529
+ await deployRun(process.argv[4] ? dataDeploy.filter((d) => d.deployId.match(process.argv[4])) : dataDeploy);
478
530
  }
479
531
  break;
480
532
 
@@ -546,13 +598,7 @@ try {
546
598
  ? envInstanceObj.port
547
599
  : envInstanceObj.port + port - singleReplicaHosts.length - (replicaHost ? 1 : 0);
548
600
 
549
- fs.writeFileSync(
550
- envPath,
551
- Object.keys(envObj)
552
- .map((key) => `${key}=${envObj[key]}`)
553
- .join(`\n`),
554
- 'utf8',
555
- );
601
+ writeEnv(envPath, envObj);
556
602
  }
557
603
  const serverConf = loadReplicas(
558
604
  JSON.parse(fs.readFileSync(`${baseConfPath}/${deployId}/conf.server.json`, 'utf8')),
@@ -596,6 +642,7 @@ try {
596
642
  }
597
643
  case 'build-uml':
598
644
  {
645
+ const plantuml = await import('plantuml');
599
646
  const folder = process.argv[3] ? process.argv[3] : `./src/client/public/default/plantuml`;
600
647
  const confData = Config.default;
601
648
 
@@ -709,89 +756,102 @@ try {
709
756
  break;
710
757
  }
711
758
 
712
- case 'update-version':
713
- {
714
- const newVersion = process.argv[3];
715
- const originPackageJson = JSON.parse(fs.readFileSync(`package.json`, 'utf8'));
716
- const { version } = originPackageJson;
717
- originPackageJson.version = newVersion;
718
- fs.writeFileSync(`package.json`, JSON.stringify(originPackageJson, null, 4), 'utf8');
719
-
720
- const originPackageLockJson = JSON.parse(fs.readFileSync(`package-lock.json`, 'utf8'));
721
- originPackageLockJson.version = newVersion;
722
- originPackageLockJson.packages[''].version = newVersion;
723
- fs.writeFileSync(`package-lock.json`, JSON.stringify(originPackageLockJson, null, 4), 'utf8');
724
-
725
- if (fs.existsSync(`./engine-private/conf`)) {
726
- const files = await fs.readdir(`./engine-private/conf`, { recursive: true });
727
- for (const relativePath of files) {
728
- const filePah = `./engine-private/conf/${relativePath.replaceAll(`\\`, '/')}`;
729
- if (filePah.split('/').pop() === 'package.json') {
730
- const originPackage = JSON.parse(fs.readFileSync(filePah, 'utf8'));
731
- originPackage.version = newVersion;
732
- fs.writeFileSync(filePah, JSON.stringify(originPackage, null, 4), 'utf8');
733
- }
759
+ case 'version-build': {
760
+ const originPackageJson = JSON.parse(fs.readFileSync(`package.json`, 'utf8'));
761
+ const newVersion = process.argv[3] ?? originPackageJson.version;
762
+ const { version } = originPackageJson;
763
+ originPackageJson.version = newVersion;
764
+ fs.writeFileSync(`package.json`, JSON.stringify(originPackageJson, null, 4), 'utf8');
765
+
766
+ const originPackageLockJson = JSON.parse(fs.readFileSync(`package-lock.json`, 'utf8'));
767
+ originPackageLockJson.version = newVersion;
768
+ originPackageLockJson.packages[''].version = newVersion;
769
+ fs.writeFileSync(`package-lock.json`, JSON.stringify(originPackageLockJson, null, 4), 'utf8');
770
+
771
+ if (fs.existsSync(`./engine-private/conf`)) {
772
+ const files = await fs.readdir(`./engine-private/conf`, { recursive: true });
773
+ for (const relativePath of files) {
774
+ const filePah = `./engine-private/conf/${relativePath.replaceAll(`\\`, '/')}`;
775
+ if (filePah.split('/').pop() === 'package.json') {
776
+ const originPackage = JSON.parse(fs.readFileSync(filePah, 'utf8'));
777
+ originPackage.version = newVersion;
778
+ fs.writeFileSync(filePah, JSON.stringify(originPackage, null, 4), 'utf8');
779
+ }
780
+ if (filePah.split('/').pop() === 'deployment.yaml') {
781
+ fs.writeFileSync(
782
+ filePah,
783
+ fs
784
+ .readFileSync(filePah, 'utf8')
785
+ .replaceAll(`v${version}`, `v${newVersion}`)
786
+ .replaceAll(`engine.version: ${version}`, `engine.version: ${newVersion}`),
787
+ 'utf8',
788
+ );
734
789
  }
735
790
  }
791
+ }
792
+
793
+ fs.writeFileSync(
794
+ `./docker-compose.yml`,
795
+ fs
796
+ .readFileSync(`./docker-compose.yml`, 'utf8')
797
+ .replaceAll(`engine.version: '${version}'`, `engine.version: '${newVersion}'`),
798
+ 'utf8',
799
+ );
736
800
 
801
+ if (fs.existsSync(`./.github/workflows/docker-image.yml`))
737
802
  fs.writeFileSync(
738
- `./docker-compose.yml`,
803
+ `./.github/workflows/docker-image.yml`,
739
804
  fs
740
- .readFileSync(`./docker-compose.yml`, 'utf8')
741
- .replaceAll(`engine.version: '${version}'`, `engine.version: '${newVersion}'`),
805
+ .readFileSync(`./.github/workflows/docker-image.yml`, 'utf8')
806
+ .replaceAll(`underpost-engine:v${version}`, `underpost-engine:v${newVersion}`),
742
807
  'utf8',
743
808
  );
744
809
 
745
- if (fs.existsSync(`./.github/workflows/docker-image.yml`))
746
- fs.writeFileSync(
747
- `./.github/workflows/docker-image.yml`,
748
- fs
749
- .readFileSync(`./.github/workflows/docker-image.yml`, 'utf8')
750
- .replaceAll(`underpost-engine:v${version}`, `underpost-engine:v${newVersion}`),
751
- 'utf8',
752
- );
753
-
754
- fs.writeFileSync(
755
- `./src/index.js`,
756
- fs.readFileSync(`./src/index.js`, 'utf8').replaceAll(`${version}`, `${newVersion}`),
757
- 'utf8',
758
- );
810
+ fs.writeFileSync(
811
+ `./src/index.js`,
812
+ fs.readFileSync(`./src/index.js`, 'utf8').replaceAll(`${version}`, `${newVersion}`),
813
+ 'utf8',
814
+ );
815
+ shellExec(`node bin/deploy cli-docs`);
816
+ shellExec(`node bin/deploy update-dependencies`);
817
+ shellExec(`auto-changelog`);
818
+ shellExec(`node bin/build dd`);
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`);
821
+ break;
822
+ }
759
823
 
760
- shellExec(`node bin/deploy update-package`);
761
- shellExec(`auto-changelog`);
762
- }
824
+ case 'version-deploy': {
825
+ shellExec(`node bin/build dd conf`);
826
+ shellExec(`git add . && cd ./engine-private && git add .`);
827
+ shellExec(`node bin cmt . ci package-pwa-microservices-template`);
828
+ shellExec(`node bin cmt ./engine-private ci package-pwa-microservices-template`);
829
+ shellExec(`node bin push . underpostnet/engine`);
830
+ shellExec(`cd ./engine-private && node ../bin push . underpostnet/engine-private`);
763
831
  break;
832
+ }
764
833
 
765
834
  case 'update-authors': {
766
- // shellExec(`git log --reverse --format='%aN (<%aE>)' | sort -u`, { stdout: true });
767
- const logs = await simpleGit().log();
768
-
835
+ // #### Ordered by first contribution.
769
836
  fs.writeFileSync(
770
837
  './AUTHORS.md',
771
838
  `# Authors
772
839
 
773
- #### Ordered by first contribution.
774
840
 
775
- ${uniqueArray(logs.all.map((log) => `- ${log.author_name} ([${log.author_email}](mailto:${log.author_email}))`)).join(`
776
- `)}
841
+ ${shellExec(`git log | grep Author: | sort -u`, { stdout: true }).split(`\n`).join(`\n\n\n`)}
777
842
 
778
843
  #### Generated by [underpost.net](https://underpost.net)`,
779
844
  'utf8',
780
845
  );
781
846
 
782
- // hash: '1c7418ad2f49c7798a6d28d370b34c69d31dce46',
783
- // date: '2024-09-16T17:10:13-03:00',
784
- // message: 'update',
785
- // refs: '',
786
- // body: '',
787
- // author_name: 'fcoverdugo',
788
- // author_email: 'fcoverdugoa@underpost.net'
847
+ break;
789
848
  }
790
849
 
791
850
  case 'restore-macro-db':
792
851
  {
793
852
  const deployGroupId = process.argv[3];
794
- await restoreMacroDb(deployGroupId);
853
+ const deployId = process.argv[4];
854
+ await restoreMacroDb(deployGroupId, deployId);
795
855
  }
796
856
 
797
857
  break;
@@ -826,10 +886,10 @@ ${uniqueArray(logs.all.map((log) => `- ${log.author_name} ([${log.author_email}]
826
886
  shellExec(`bin/besu --help`);
827
887
 
828
888
  // Set env path
829
- // export PATH=$PATH:/dd/besu-24.9.1/bin
889
+ // export PATH=$PATH:/home/dd/besu-24.9.1/bin
830
890
 
831
891
  // Open src
832
- // shellExec(`sudo code /dd/besu-24.9.1 --user-data-dir="/root/.vscode-root" --no-sandbox`);
892
+ // shellExec(`sudo code /home/dd/besu-24.9.1 --user-data-dir="/root/.vscode-root" --no-sandbox`);
833
893
  }
834
894
 
835
895
  break;
@@ -847,184 +907,1355 @@ ${uniqueArray(logs.all.map((log) => `- ${log.author_name} ([${log.author_email}]
847
907
  }
848
908
 
849
909
  case 'update-default-conf': {
850
- const host = process.argv[3] ? process.argv[3] : 'underpostnet.github.io';
851
- const path = process.argv[4] ? process.argv[4] : '/pwa-microservices-template-ghpkg';
852
- DefaultConf.server = {
853
- [host]: { [path]: DefaultConf.server['default.net']['/'] },
910
+ const defaultServer = DefaultConf.server['default.net']['/'];
911
+ let confName = process.argv[3];
912
+ if (confName === 'ghpkg') {
913
+ confName = undefined;
914
+ const host = 'underpostnet.github.io';
915
+ const path = '/pwa-microservices-template-ghpkg';
916
+ DefaultConf.server = {
917
+ [host]: { [path]: defaultServer },
918
+ };
919
+ DefaultConf.server[host][path].apiBaseProxyPath = '/';
920
+ DefaultConf.server[host][path].apiBaseHost = 'www.nexodev.org';
921
+ } else if (confName) {
922
+ DefaultConf.client = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.client.json`, 'utf8'));
923
+ DefaultConf.server = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.server.json`, 'utf8'));
924
+ DefaultConf.ssr = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.ssr.json`, 'utf8'));
925
+ // DefaultConf.cron = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.cron.json`, 'utf8'));
926
+
927
+ for (const host of Object.keys(DefaultConf.server)) {
928
+ for (const path of Object.keys(DefaultConf.server[host])) {
929
+ DefaultConf.server[host][path].db = defaultServer.db;
930
+ DefaultConf.server[host][path].mailer = defaultServer.mailer;
931
+
932
+ delete DefaultConf.server[host][path]._wp_client;
933
+ delete DefaultConf.server[host][path]._wp_git;
934
+ delete DefaultConf.server[host][path]._wp_directory;
935
+ delete DefaultConf.server[host][path].wp;
936
+ delete DefaultConf.server[host][path].git;
937
+ delete DefaultConf.server[host][path].directory;
938
+ }
939
+ }
940
+ }
941
+ const sepRender = '/**/';
942
+ const confRawPaths = fs.readFileSync('./conf.js', 'utf8').split(sepRender);
943
+ confRawPaths[1] = `${JSON.stringify(DefaultConf)};`;
944
+ const targetConfPath = `./conf${confName ? `.${confName}` : ''}.js`;
945
+ fs.writeFileSync(targetConfPath, confRawPaths.join(sepRender), 'utf8');
946
+ shellExec(`prettier --write ${targetConfPath}`);
947
+
948
+ break;
949
+ }
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
+ );
854
1044
  };
855
- DefaultConf.server[host][path].apiBaseProxyPath = '/';
856
- DefaultConf.server[host][path].apiBaseHost = 'www.nexodev.org';
857
- fs.writeFileSync(
858
- './conf.js',
859
- `
860
- const DefaultConf = ${JSONweb(DefaultConf)};
861
-
862
- export { DefaultConf }
863
1045
 
864
- `,
865
- 'utf8',
866
- );
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`);
867
1063
 
1064
+ shellExec(`sudo rm -rf ./id_rsa`);
1065
+ shellExec(`sudo rm -rf ./id_rsa.pub`);
1066
+ setUpSSH();
868
1067
  break;
869
1068
  }
870
- case 'ssh-export-server-keys': {
871
- fs.copyFile('/etc/ssh/ssh_host_rsa_key', './engine-private/deploy/ssh_host_rsa_key');
872
- fs.copyFile('/etc/ssh/ssh_host_rsa_key.pub', './engine-private/deploy/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
+
873
1093
  break;
874
1094
  }
875
- case 'ssh-import-server-keys': {
876
- fs.copyFile('./engine-private/deploy/ssh_host_rsa_key', '/etc/ssh/ssh_host_rsa_key');
877
- fs.copyFile('./engine-private/deploy/ssh_host_rsa_key.pub', '/etc/ssh/ssh_host_rsa_key.pub');
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
+
878
1135
  break;
879
1136
  }
880
- case 'ssh-import-client-keys': {
881
- const host = process.argv[3];
882
- shellExec(`node bin/deploy set-ssh-keys ./engine-private/deploy/ssh_host_rsa_key${host ? ` ${host}` : ``} clean`);
1137
+
1138
+ case 'cli-docs': {
1139
+ buildCliDoc();
883
1140
  break;
884
1141
  }
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');
1142
+
1143
+ case 'monitor': {
1144
+ shellExec(
1145
+ `node bin monitor ${process.argv[6] === 'sync' ? '--sync ' : ''}--type ${process.argv[3]} ${process.argv[4]} ${
1146
+ process.argv[5]
1147
+ }`,
1148
+ {
1149
+ async: true,
1150
+ },
1151
+ );
1152
+ break;
1153
+ }
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
+
897
1188
  break;
898
1189
  }
899
1190
 
900
- case 'set-ssh-keys': {
901
- const files = ['authorized_keys', 'id_rsa', 'id_rsa.pub', 'known_hosts ', 'known_hosts.old'];
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-stop': {
1201
+ shellExec(`sudo systemctl stop postgresql-14`);
1202
+ shellExec(`sudo systemctl disable postgresql-14`);
1203
+ break;
1204
+ }
1205
+ case 'pg-start': {
1206
+ shellExec(`sudo systemctl enable postgresql-14`);
1207
+ shellExec(`sudo systemctl restart postgresql-14`);
1208
+ break;
1209
+ }
1210
+
1211
+ case 'pg-list-db': {
1212
+ shellExec(`sudo -i -u postgres psql -c "\\l"`);
1213
+ break;
1214
+ }
1215
+
1216
+ case 'pg-list-table': {
1217
+ shellExec(`sudo -i -u postgres psql -c "\\dt *.*"`);
1218
+ // schema_name.*
1219
+ break;
1220
+ }
1221
+ case 'pg-drop-db': {
1222
+ shellExec(`sudo -i -u postgres psql -c "DROP DATABASE ${process.argv[3]} WITH (FORCE)"`);
1223
+ shellExec(`sudo -i -u postgres psql -c "DROP USER ${process.argv[4]}"`);
1224
+ break;
1225
+ }
1226
+
1227
+ case 'maas-stop': {
1228
+ shellExec(`sudo snap stop maas`);
1229
+ break;
1230
+ }
1231
+
1232
+ case 'maas': {
1233
+ dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1234
+ const IP_ADDRESS = getLocalIPv4Address();
1235
+ const serverip = IP_ADDRESS;
1236
+ const tftpRoot = process.env.TFTP_ROOT;
1237
+ const ipaddr = process.env.RPI4_IP;
1238
+ const netmask = process.env.NETMASK;
1239
+ const gatewayip = process.env.GATEWAY_IP;
1240
+
1241
+ let resources;
1242
+ try {
1243
+ resources = JSON.parse(
1244
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-resources read`, {
1245
+ silent: true,
1246
+ stdout: true,
1247
+ }),
1248
+ ).map((o) => ({
1249
+ id: o.id,
1250
+ name: o.name,
1251
+ architecture: o.architecture,
1252
+ }));
1253
+ } catch (error) {
1254
+ logger.error(error);
1255
+ }
1256
+
1257
+ const machineFactory = (m) => ({
1258
+ system_id: m.interface_set[0].system_id,
1259
+ mac_address: m.interface_set[0].mac_address,
1260
+ hostname: m.hostname,
1261
+ status_name: m.status_name,
1262
+ });
1263
+
1264
+ let machines;
1265
+ try {
1266
+ machines = JSON.parse(
1267
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machines read`, {
1268
+ stdout: true,
1269
+ silent: true,
1270
+ }),
1271
+ ).map((m) => machineFactory(m));
1272
+ } catch (error) {
1273
+ logger.error(error);
1274
+ }
1275
+
1276
+ if (process.argv.includes('db')) {
1277
+ // DROP, ALTER, CREATE, WITH ENCRYPTED
1278
+ // sudo -u <user> -h <host> psql <db-name>
1279
+ shellExec(`DB_PG_MAAS_NAME=${process.env.DB_PG_MAAS_NAME}`);
1280
+ shellExec(`DB_PG_MAAS_PASS=${process.env.DB_PG_MAAS_PASS}`);
1281
+ shellExec(`DB_PG_MAAS_USER=${process.env.DB_PG_MAAS_USER}`);
1282
+ shellExec(`DB_PG_MAAS_HOST=${process.env.DB_PG_MAAS_HOST}`);
1283
+ shellExec(
1284
+ `sudo -i -u postgres psql -c "CREATE USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1285
+ );
1286
+ shellExec(
1287
+ `sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
1288
+ );
1289
+ const actions = ['LOGIN', 'SUPERUSER', 'INHERIT', 'CREATEDB', 'CREATEROLE', 'REPLICATION'];
1290
+ shellExec(`sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ${actions.join(' ')}"`);
1291
+ shellExec(`sudo -i -u postgres psql -c "\\du"`);
1292
+
1293
+ shellExec(`sudo -i -u postgres createdb -O "$DB_PG_MAAS_USER" "$DB_PG_MAAS_NAME"`);
1294
+
1295
+ shellExec(`sudo -i -u postgres psql -c "\\l"`);
1296
+ }
1297
+
1298
+ if (process.argv.includes('ls')) {
1299
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-sources read`);
1300
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} commissioning-scripts read`);
1301
+ // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-source-selections read 60`);
1302
+ console.table(resources);
1303
+ console.table(machines);
1304
+ process.exit(0);
1305
+ }
1306
+
1307
+ // TODO: - Disable maas proxy (egress forwarding to public dns)
1308
+ // - Configure maas dns forwarding ${process.env.MAAS_DNS}
1309
+ // - Enable DNSSEC validation of upstream zones: Automatic (use default root key)
1310
+
1311
+ if (process.argv.includes('clear')) {
1312
+ for (const machine of machines) {
1313
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine delete ${machine.system_id}`);
1314
+ }
1315
+ // machines = [];
1316
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries clear all=true`);
1317
+ if (process.argv.includes('force')) {
1318
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries scan force=true`);
1319
+ }
1320
+ process.exit(0);
1321
+ }
1322
+ if (process.argv.includes('grub-arm64')) {
1323
+ shellExec(`sudo dnf install grub2-efi-aa64-modules`);
1324
+ shellExec(`sudo dnf install grub2-efi-x64-modules`);
1325
+ // sudo grub2-mknetdir --net-directory=${tftpRoot} --subdir=/boot/grub --module-path=/usr/lib/grub/arm64-efi arm64-efi
1326
+ process.exit(0);
1327
+ }
1328
+
1329
+ if (process.argv.includes('psql')) {
1330
+ const cmd = `psql -U ${process.env.DB_PG_MAAS_USER} -h ${process.env.DB_PG_MAAS_HOST} -W ${process.env.DB_PG_MAAS_NAME}`;
1331
+ pbcopy(cmd);
1332
+ process.exit(0);
1333
+ }
1334
+ if (process.argv.includes('logs')) {
1335
+ shellExec(`maas status`);
1336
+ const cmd = `journalctl -f -t dhcpd -u snap.maas.pebble.service`;
1337
+ pbcopy(cmd);
1338
+ process.exit(0);
1339
+ }
1340
+ if (process.argv.includes('reset')) {
1341
+ // shellExec(
1342
+ // `maas init region+rack --database-uri "postgres://$DB_PG_MAAS_USER:$DB_PG_MAAS_PASS@$DB_PG_MAAS_HOST/$DB_PG_MAAS_NAME"` +
1343
+ // ` --maas-url http://${IP_ADDRESS}:5240/MAAS`,
1344
+ // );
1345
+ const cmd =
1346
+ `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}"` +
1347
+ ` --maas-url http://${IP_ADDRESS}:5240/MAAS`;
1348
+ pbcopy(cmd);
1349
+ process.exit(0);
1350
+ }
1351
+ if (process.argv.includes('dhcp')) {
1352
+ const snippets = JSON.parse(
1353
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} dhcpsnippets read`, {
1354
+ stdout: true,
1355
+ silent: true,
1356
+ disableLog: true,
1357
+ }),
1358
+ );
1359
+ for (const snippet of snippets) {
1360
+ switch (snippet.name) {
1361
+ case 'arm64':
1362
+ snippet.value = snippet.value.split(`\n`);
1363
+ snippet.value[1] = ` filename "http://${IP_ADDRESS}:5248/images/bootloaders/uefi/arm64/grubaa64.efi";`;
1364
+ snippet.value[5] = ` filename "http://${IP_ADDRESS}:5248/images/bootloaders/uefi/arm64/grubaa64.efi";`;
1365
+ snippet.value = snippet.value.join(`\n`);
1366
+ shellExec(
1367
+ `maas ${process.env.MAAS_ADMIN_USERNAME} dhcpsnippet update ${snippet.name} value='${snippet.value}'`,
1368
+ );
1369
+ break;
1370
+
1371
+ default:
1372
+ break;
1373
+ }
1374
+ }
902
1375
 
903
- // > write
904
- // >> append
1376
+ console.log(snippets);
905
1377
 
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}`);
1378
+ process.exit(0);
1379
+ }
1380
+ // shellExec(`MAAS_ADMIN_USERNAME=${process.env.MAAS_ADMIN_USERNAME}`);
1381
+ // shellExec(`MAAS_ADMIN_EMAIL=${process.env.MAAS_ADMIN_EMAIL}`);
1382
+ // shellExec(`maas createadmin --username $MAAS_ADMIN_USERNAME --email $MAAS_ADMIN_EMAIL`);
1383
+
1384
+ // MaaS admin CLI:
1385
+ // maas login <maas-admin-username> http://localhost:5240/MAAS
1386
+ // paste GUI API KEY (profile section)
1387
+
1388
+ // Import custom image
1389
+ // maas <maas-admin-username> boot-resources create name='custom/RockyLinuxRpi4' \
1390
+ // title='RockyLinuxRpi4' \
1391
+ // architecture='arm64/generic' \
1392
+ // filetype='tgz' \
1393
+ // content@=/home/RockyLinuxRpi_9-latest.tar.gz
1394
+
1395
+ // Image boot resource:
1396
+ // /var/snap/maas/current/root/snap/maas
1397
+ // /var/snap/maas/common/maas/tftp_root
1398
+ // sudo chmod 755 /var/snap/maas/common/maas/tftp_root
1399
+
1400
+ // /var/snap/maas/common/maas/dhcpd.conf
1401
+ // sudo snap restart maas.pebble
1402
+
1403
+ // PXE Linux files:
1404
+ // /var/snap/maas/common/maas/image-storage/bootloaders/pxe/i386
1405
+ // sudo nmcli con modify <interface-device-name-connection-id> ethtool.feature-rx on ethtool.feature-tx off
1406
+ // sudo nmcli connection up <interface-device-name-connection-id>
1407
+
1408
+ // man nm-settings |grep feature-tx-checksum
1409
+
1410
+ // nmcli c modify <interface-device-name-connection-id> \
1411
+ // ethtool.feature-tx-checksum-fcoe-crc off \
1412
+ // ethtool.feature-tx-checksum-ip-generic off \
1413
+ // ethtool.feature-tx-checksum-ipv4 off \
1414
+ // ethtool.feature-tx-checksum-ipv6 off \
1415
+ // ethtool.feature-tx-checksum-sctp off
1416
+
1417
+ // Ensure Rocky NFS server and /etc/exports configured
1418
+ // sudo systemctl restart nfs-server
1419
+ // Check mounts: showmount -e <server-ip>
1420
+ // Check nfs ports: rpcinfo -p
1421
+ // sudo chown -R root:root ${process.env.NFS_EXPORT_PATH}/rpi4mb
1422
+ // sudo chmod 755 ${process.env.NFS_EXPORT_PATH}/rpi4mb
1423
+
1424
+ // tftp server
1425
+ // sudo chown -R root:root /var/snap/maas/common/maas/tftp_root/rpi4mb
1426
+
1427
+ // tftp client
1428
+ // sudo dnf install tftp
1429
+ // tftp <server-ip> -c get <path>
1430
+
1431
+ // Check firewall-cmd
1432
+ // firewall-cmd --permanent --add-service=rpc-bind
1433
+ // firewall-cmd --reload
1434
+ // systemctl disable firewalld
1435
+ // sudo firewall-cmd --permanent --add-port=10259/tcp --zone=public
1436
+
1437
+ // Image extension transform (.img.xz to .tar.gz):
1438
+ // tar -cvzf image-name.tar.gz image-name.img.xz
1439
+
1440
+ // Rocky network configuration:
1441
+ // /etc/NetworkManager/system-connections
1442
+
1443
+ // Rocky kernel params update
1444
+ // sudo grubby --args="<key>=<value> <key>=<value>" --update-kernel=ALL
1445
+ // sudo reboot now
1446
+
1447
+ // Temporal:
1448
+ // sudo snap install temporal
1449
+ // journalctl -u snap.maas.pebble -t maas-regiond
1450
+ // journalctl -u snap.maas.pebble -t maas-temporal -n 100 --no-pager -f
1451
+
1452
+ // Remove:
1453
+ // sudo dnf remove <package> -y; sudo dnf autoremove -y; sudo dnf clean packages
1454
+ // check: ~
1455
+ // check: ~./cache
1456
+ // check: ~./config
1457
+
1458
+ // Check file logs
1459
+ // grep -i -E -C 1 '<key-a>|<key-b>' /example.log | tail -n 600
1460
+
1461
+ // Back into your firmware setup (UEFI or BIOS config screen).
1462
+ // grub> fwsetup
1463
+
1464
+ // Poweroff:
1465
+ // grub > halt
1466
+ // initramfs > poweroff
1467
+
1468
+ // Check interface
1469
+ // ip link show
1470
+ // nmcli con show
1471
+
1472
+ let firmwarePath,
1473
+ tftpSubDir,
1474
+ kernelFilesPaths,
1475
+ name,
1476
+ architecture,
1477
+ resource,
1478
+ nfsConnectStr,
1479
+ etcExports,
1480
+ nfsServerRootPath,
1481
+ bootConf,
1482
+ zipFirmwareFileName,
1483
+ zipFirmwareName,
1484
+ zipFirmwareUrl,
1485
+ interfaceName,
1486
+ nfsHost;
1487
+
1488
+ switch (process.argv[3]) {
1489
+ case 'rpi4mb':
1490
+ const resourceId = process.argv[4] ?? '39';
1491
+ tftpSubDir = '/rpi4mb';
1492
+ zipFirmwareFileName = `RPi4_UEFI_Firmware_v1.41.zip`;
1493
+ zipFirmwareName = zipFirmwareFileName.split('.zip')[0];
1494
+ zipFirmwareUrl = `https://github.com/pftf/RPi4/releases/download/v1.41/RPi4_UEFI_Firmware_v1.41.zip`;
1495
+ firmwarePath = `../${zipFirmwareName}`;
1496
+ interfaceName = process.env.RPI4_INTERFACE_NAME;
1497
+ nfsHost = 'rpi4mb';
1498
+ if (!fs.existsSync(firmwarePath)) {
1499
+ await Downloader(zipFirmwareUrl, `../${zipFirmwareFileName}`);
1500
+ shellExec(`cd .. && mkdir ${zipFirmwareName} && cd ${zipFirmwareName} && unzip ../${zipFirmwareFileName}`);
913
1501
  }
914
- fs.writeFileSync(`/root/.ssh/${file}`, '', 'utf8');
1502
+ resource = resources.find((o) => o.id == resourceId);
1503
+ name = resource.name;
1504
+ architecture = resource.architecture;
1505
+ resource = resources.find((o) => o.name === name && o.architecture === architecture);
1506
+ nfsServerRootPath = `${process.env.NFS_EXPORT_PATH}/rpi4mb`;
1507
+ // ,anonuid=1001,anongid=100
1508
+ // etcExports = `${nfsServerRootPath} *(rw,all_squash,sync,no_root_squash,insecure)`;
1509
+ etcExports = `${nfsServerRootPath} 192.168.1.0/24(${[
1510
+ 'rw',
1511
+ // 'all_squash',
1512
+ 'sync',
1513
+ 'no_root_squash',
1514
+ 'no_subtree_check',
1515
+ 'insecure',
1516
+ ]})`;
1517
+ const resourceData = JSON.parse(
1518
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} boot-resource read ${resource.id}`, {
1519
+ stdout: true,
1520
+ silent: true,
1521
+ disableLog: true,
1522
+ }),
1523
+ );
1524
+ const bootFiles = resourceData.sets[Object.keys(resourceData.sets)[0]].files;
1525
+ const suffix = architecture.match('xgene') ? '.xgene' : '';
1526
+
1527
+ kernelFilesPaths = {
1528
+ 'vmlinuz-efi': bootFiles['boot-kernel' + suffix].filename_on_disk,
1529
+ 'initrd.img': bootFiles['boot-initrd' + suffix].filename_on_disk,
1530
+ squashfs: bootFiles['squashfs'].filename_on_disk,
1531
+ };
1532
+ const protocol = 'tcp'; // v3 -> tcp, v4 -> udp
1533
+
1534
+ const mountOptions = [
1535
+ protocol,
1536
+ 'vers=3',
1537
+ 'nfsvers=3',
1538
+ 'nolock',
1539
+ // 'protocol=tcp',
1540
+ // 'hard=true',
1541
+ 'port=2049',
1542
+ // 'sec=none',
1543
+ 'rw',
1544
+ 'hard',
1545
+ 'intr',
1546
+ 'rsize=32768',
1547
+ 'wsize=32768',
1548
+ 'acregmin=0',
1549
+ 'acregmax=0',
1550
+ 'acdirmin=0',
1551
+ 'acdirmax=0',
1552
+ 'noac',
1553
+ // 'nodev',
1554
+ // 'nosuid',
1555
+ ];
1556
+ const cmd = [
1557
+ `console=serial0,115200`,
1558
+ `console=tty1`,
1559
+ // `initrd=-1`,
1560
+ // `net.ifnames=0`,
1561
+ // `dwc_otg.lpm_enable=0`,
1562
+ // `elevator=deadline`,
1563
+ `root=/dev/nfs`,
1564
+ `nfsroot=${serverip}:${process.env.NFS_EXPORT_PATH}/rpi4mb,${mountOptions}`,
1565
+ // `nfsroot=${serverip}:${process.env.NFS_EXPORT_PATH}/rpi4mb`,
1566
+ `ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${nfsHost}:${interfaceName}:static`,
1567
+ `rootfstype=nfs`,
1568
+ `rw`,
1569
+ `rootwait`,
1570
+ `fixrtc`,
1571
+ 'initrd=initrd.img',
1572
+ // 'boot=casper',
1573
+ // 'ro',
1574
+ 'netboot=nfs',
1575
+ `cloud-config-url=/dev/null`,
1576
+ // 'ip=dhcp',
1577
+ // 'ip=dfcp',
1578
+ // 'autoinstall',
1579
+ // 'rd.break',
1580
+ ];
1581
+
1582
+ nfsConnectStr = cmd.join(' ');
1583
+ bootConf = `[all]
1584
+ MAC_ADDRESS=00:00:00:00:00:00
1585
+ MAC_ADDRESS_OTP=0,1
1586
+ BOOT_UART=0
1587
+ WAKE_ON_GPIO=1
1588
+ POWER_OFF_ON_HALT=0
1589
+ ENABLE_SELF_UPDATE=1
1590
+ DISABLE_HDMI=0
1591
+ TFTP_IP=${serverip}
1592
+ TFTP_PREFIX=1
1593
+ TFTP_PREFIX_STR=${tftpSubDir.slice(1)}/
1594
+ NET_INSTALL_ENABLED=1
1595
+ DHCP_TIMEOUT=45000
1596
+ DHCP_REQ_TIMEOUT=4000
1597
+ TFTP_FILE_TIMEOUT=30000
1598
+ BOOT_ORDER=0x21`;
1599
+
1600
+ break;
1601
+
1602
+ default:
1603
+ break;
1604
+ }
1605
+ shellExec(`sudo chmod 755 ${process.env.NFS_EXPORT_PATH}/${nfsHost}`);
1606
+
1607
+ shellExec(`sudo rm -rf ${tftpRoot}${tftpSubDir}`);
1608
+ shellExec(`sudo cp -a ${firmwarePath} ${tftpRoot}${tftpSubDir}`);
1609
+ shellExec(`mkdir -p ${tftpRoot}${tftpSubDir}/pxe`);
1610
+
1611
+ fs.writeFileSync(`/etc/exports`, etcExports, 'utf8');
1612
+ if (bootConf) fs.writeFileSync(`${tftpRoot}${tftpSubDir}/boot.conf`, bootConf, 'utf8');
1613
+
1614
+ shellExec(`node bin/deploy nfs`);
1615
+
1616
+ if (process.argv.includes('restart')) {
1617
+ shellExec(`sudo snap restart maas.pebble`);
1618
+ let secs = 0;
1619
+ while (
1620
+ !(
1621
+ shellExec(`maas status`, { silent: true, disableLog: true, stdout: true })
1622
+ .split(' ')
1623
+ .filter((l) => l.match('inactive')).length === 1
1624
+ )
1625
+ ) {
1626
+ await timer(1000);
1627
+ console.log(`Waiting... (${++secs}s)`);
915
1628
  }
916
- shellExec('eval `ssh-agent -s`' + ` && ssh-add -D`);
917
1629
  }
918
1630
 
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}`);
1631
+ switch (process.argv[3]) {
1632
+ case 'rpi4mb':
1633
+ {
1634
+ // subnet DHCP snippets
1635
+ // # UEFI ARM64
1636
+ // if option arch = 00:0B {
1637
+ // filename "rpi4mb/pxe/grubaa64.efi";
1638
+ // }
1639
+ // elsif option arch = 00:13 {
1640
+ // filename "http://<IP_ADDRESS>:5248/images/bootloaders/uefi/arm64/grubaa64.efi";
1641
+ // option vendor-class-identifier "HTTPClient";
1642
+ // }
1643
+ for (const file of ['bootaa64.efi', 'grubaa64.efi']) {
1644
+ shellExec(
1645
+ `sudo cp -a /var/snap/maas/common/maas/image-storage/bootloaders/uefi/arm64/${file} ${tftpRoot}${tftpSubDir}/pxe/${file}`,
1646
+ );
1647
+ }
1648
+ // const file = 'bcm2711-rpi-4-b.dtb';
1649
+ // shellExec(
1650
+ // `sudo cp -a ${firmwarePath}/${file} /var/snap/maas/common/maas/image-storage/bootloaders/uefi/arm64/${file}`,
1651
+ // );
1652
+
1653
+ // const ipxeSrc = fs
1654
+ // .readFileSync(`${tftpRoot}/ipxe.cfg`, 'utf8')
1655
+ // .replaceAll('amd64', 'arm64')
1656
+ // .replaceAll('${next-server}', IP_ADDRESS);
1657
+ // fs.writeFileSync(`${tftpRoot}/ipxe.cfg`, ipxeSrc, 'utf8');
1658
+
1659
+ {
1660
+ for (const file of Object.keys(kernelFilesPaths)) {
1661
+ shellExec(
1662
+ `sudo cp -a /var/snap/maas/common/maas/image-storage/${kernelFilesPaths[file]} ${tftpRoot}${tftpSubDir}/pxe/${file}`,
1663
+ );
1664
+ }
1665
+ // const configTxtSrc = fs.readFileSync(`${firmwarePath}/config.txt`, 'utf8');
1666
+ // fs.writeFileSync(
1667
+ // `${tftpRoot}${tftpSubDir}/config.txt`,
1668
+ // configTxtSrc
1669
+ // .replace(`kernel=kernel8.img`, `kernel=vmlinuz`)
1670
+ // .replace(`# max_framebuffers=2`, `max_framebuffers=2`)
1671
+ // .replace(`initramfs initramfs8 followkernel`, `initramfs initrd.img followkernel`),
1672
+ // 'utf8',
1673
+ // );
1674
+
1675
+ // grub:
1676
+ // set root=(pxe)
1677
+
1678
+ // UNDERPOST.NET UEFI/GRUB/MAAS RPi4 commissioning (ARM64)
1679
+ const menuentryStr = 'underpost.net rpi4mb maas commissioning (ARM64)';
1680
+ const grubCfgPath = `${tftpRoot}/grub/grub.cfg`;
1681
+ fs.writeFileSync(
1682
+ grubCfgPath,
1683
+ `
1684
+ insmod gzio
1685
+ insmod http
1686
+ insmod nfs
1687
+ set timeout=5
1688
+ set default=0
1689
+
1690
+ menuentry '${menuentryStr}' {
1691
+ set root=(tftp,${serverip})
1692
+ linux ${tftpSubDir}/pxe/vmlinuz-efi ${nfsConnectStr}
1693
+ initrd ${tftpSubDir}/pxe/initrd.img
1694
+ boot
1695
+ }
1696
+
1697
+ `,
1698
+ 'utf8',
1699
+ );
1700
+ }
1701
+ const arm64EfiPath = `${tftpRoot}/grub/arm64-efi`;
1702
+ if (fs.existsSync(arm64EfiPath)) shellExec(`sudo rm -rf ${arm64EfiPath}`);
1703
+ shellExec(`sudo cp -a /usr/lib/grub/arm64-efi ${arm64EfiPath}`);
1704
+ }
924
1705
 
925
- if (!fs.existsSync('/root/.ssh/id_rsa')) shellExec(`touch ${'/root/.ssh/id_rsa'}`);
926
- shellExec(`cat ${destPath} > ${'/root/.ssh/id_rsa'}`);
1706
+ break;
927
1707
 
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'}`);
1708
+ default:
1709
+ break;
1710
+ }
930
1711
 
931
- shellExec(`chmod 700 /root/.ssh/`);
932
- for (const file of files) {
933
- shellExec(`chmod 600 /root/.ssh/${file}`);
1712
+ logger.info('succes maas deploy', {
1713
+ resource,
1714
+ kernelFilesPaths,
1715
+ tftpRoot,
1716
+ tftpSubDir,
1717
+ firmwarePath,
1718
+ etcExports,
1719
+ nfsServerRootPath,
1720
+ nfsConnectStr,
1721
+ });
1722
+ if (process.argv.includes('restart')) {
1723
+ if (fs.existsSync(`node engine-private/r.js`)) shellExec(`node engine-private/r`);
1724
+ shellExec(`node bin/deploy maas dhcp`);
1725
+ shellExec(`sudo chown -R root:root ${tftpRoot}`);
1726
+ shellExec(`sudo sudo chmod 755 ${tftpRoot}`);
934
1727
  }
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`);
1728
+ // for (const machine of machines) {
1729
+ // // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine delete ${machine.system_id}`);
1730
+ // shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine commission ${machine.system_id}`, {
1731
+ // silent: true,
1732
+ // });
1733
+ // }
1734
+ // machines = [];
1735
+
1736
+ const monitor = async () => {
1737
+ // discoveries Query observed discoveries.
1738
+ // discovery Read or delete an observed discovery.
1739
+
1740
+ const discoveries = JSON.parse(
1741
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} discoveries read`, {
1742
+ silent: true,
1743
+ stdout: true,
1744
+ }),
1745
+ ).filter(
1746
+ (o) => o.ip !== IP_ADDRESS && o.ip !== gatewayip && !machines.find((_o) => _o.mac_address === o.mac_address),
1747
+ );
942
1748
 
1749
+ // {
1750
+ // "discovery_id": "",
1751
+ // "ip": "192.168.1.189",
1752
+ // "mac_address": "00:00:00:00:00:00",
1753
+ // "last_seen": "2025-05-05T14:17:37.354",
1754
+ // "hostname": null,
1755
+ // "fabric_name": "",
1756
+ // "vid": null,
1757
+ // "mac_organization": "",
1758
+ // "observer": {
1759
+ // "system_id": "",
1760
+ // "hostname": "",
1761
+ // "interface_id": 1,
1762
+ // "interface_name": ""
1763
+ // },
1764
+ // "resource_uri": "/MAAS/api/2.0/discovery/MTkyLjE2OC4xLjE4OSwwMDowMDowMDowMDowMDowMA==/"
1765
+ // },
1766
+
1767
+ for (const discovery of discoveries) {
1768
+ const machine = {
1769
+ architecture: architecture.match('amd') ? 'amd64/generic' : 'arm64/generic',
1770
+ mac_address: discovery.mac_address,
1771
+ hostname: discovery.hostname ?? discovery.mac_organization ?? discovery.domain ?? `generic-host-${s4()}`,
1772
+ // discovery.ip.match(ipaddr)
1773
+ // ? nfsHost
1774
+ // : `unknown-${s4()}`,
1775
+ // description: '',
1776
+ // https://maas.io/docs/reference-power-drivers
1777
+ power_type: 'manual', // manual
1778
+ // power_parameters_power_address: discovery.ip,
1779
+ mac_addresses: discovery.mac_address,
1780
+ };
1781
+ machine.hostname = machine.hostname.replaceAll(' ', '').replaceAll('.', '');
1782
+
1783
+ try {
1784
+ let newMachine = shellExec(
1785
+ `maas ${process.env.MAAS_ADMIN_USERNAME} machines create ${Object.keys(machine)
1786
+ .map((k) => `${k}="${machine[k]}"`)
1787
+ .join(' ')}`,
1788
+ {
1789
+ silent: true,
1790
+ stdout: true,
1791
+ },
1792
+ );
1793
+ newMachine = machineFactory(JSON.parse(newMachine));
1794
+ machines.push(newMachine);
1795
+ console.log(newMachine);
1796
+ shellExec(`maas ${process.env.MAAS_ADMIN_USERNAME} machine commission ${newMachine.system_id}`, {
1797
+ silent: true,
1798
+ });
1799
+ } catch (error) {
1800
+ logger.error(error, error.stack);
1801
+ }
1802
+ }
1803
+ // if (discoveries.length > 0) {
1804
+ // shellExec(
1805
+ // `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}'`,
1806
+ // );
1807
+ // }
1808
+ await timer(1000);
1809
+ monitor();
1810
+ };
1811
+ // shellExec(`node bin/deploy open-virtual-root ${architecture.match('amd') ? 'amd64' : 'arm64'} ${nfsHost}`);
1812
+ machines = [];
1813
+ shellExec(`node bin/deploy maas clear`);
1814
+ monitor();
943
1815
  break;
944
1816
  }
945
1817
 
946
- case 'ssh': {
947
- if (!process.argv.includes('server')) {
948
- shellExec(`sudo apt update`);
949
- shellExec(`sudo apt install openssh-server -y`);
950
- shellExec(`sudo apt install ssh-askpass`);
951
- }
952
- shellExec(`sudo systemctl enable ssh`);
953
- shellExec(`sudo systemctl restart ssh`);
954
- shellExec(`sudo systemctl status ssh`);
955
- // sudo service ssh restart
956
- shellExec(`ip a`);
1818
+ case 'nfs': {
1819
+ // Daemon RPC NFSv3. ports:
957
1820
 
958
- // adduser newuser
959
- // usermod -aG sudo newuser
1821
+ // 2049 (TCP/UDP) – nfsd standard port.
1822
+ // 111 (TCP/UDP) rpcbind/portmapper.
1823
+ // 20048 (TCP/UDP) – rpc.mountd.
1824
+ // 32765 (TCP/UDP) – rpc.statd.
1825
+ // 32766 (TCP/UDP) – lockd (NLM).
960
1826
 
961
- // ssh -i '/path/to/keyfile' username@server
1827
+ // Configure export and permissions:
1828
+ // /etc/exports
962
1829
 
963
- // ssh-keygen -t ed25519 -C "your_email@example.com" -f $HOME/.ssh/id_rsa
1830
+ // Configure ports:
1831
+ // /etc/nfs.conf
964
1832
 
965
- // legacy: ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -f $HOME/.ssh/id_rsa
1833
+ fs.writeFileSync(
1834
+ `/etc/nfs.conf`,
1835
+ `
1836
+ [mountd]
1837
+ port = 20048
966
1838
 
967
- // vi .ssh/authorized_keys
968
- // chmod 700 .ssh
969
- // chmod 600 authorized_keys
1839
+ [statd]
1840
+ port = 32765
1841
+ outgoing-port = 32765
970
1842
 
971
- // cat id_rsa.pub > .ssh/authorized_keys
1843
+ [nfsd]
1844
+ rdma=y
1845
+ rdma-port=20049
972
1846
 
973
- // add public key to authorized keys
974
- // cat .ssh/id_ed25519.pub | ssh [sudo username]@[host/ip] 'cat >> .ssh/authorized_keys'
1847
+ [lockd]
1848
+ port = 32766
1849
+ udp-port = 32766
1850
+ `,
1851
+ 'utf8',
1852
+ );
975
1853
 
976
- // 2. Open /etc/ssh/sshd_config file
977
- // nano /etc/ssh/sshd_config
1854
+ // Client users have read-only access to resources and are identified as anonymous on the server.
1855
+ // /share ip-client(ro,all_squash)
978
1856
 
979
- // 3. add example code to last line of file
980
- // Match User newuser
981
- // PasswordAuthentication yes
1857
+ // Client users can modify resources and keep their UID on the server. Only root is identified as anonymous.
1858
+ // /share ip-client(rw)
982
1859
 
983
- // ssh [sudo username]@[host/ip]
984
- // open port 22
1860
+ // Users on client workstation 1 can modify resources, while those on client workstation 2 have read-only access.
1861
+ // UIDs are kept on the server, and only root is identified as anonymous.
1862
+ // /share ip-client1(rw) ip-client2(ro)
985
1863
 
986
- // init ssh agent service
987
- // eval `ssh-agent -s`
1864
+ // Client1 users can modify resources. Their UID is changed to 1001 and their GID to 100 on the server.
1865
+ // /share ip-client(rw,all_squash,anonuid=1001,anongid=100)
988
1866
 
989
- // list keys
990
- // ssh-add -l
1867
+ // sudo dnf install nfs-utils
1868
+ // sudo systemctl enable --now rpcbind // RPC map service
1869
+ // sudo systemctl enable --now nfs-server // nfs domains nfsd
991
1870
 
992
- // add key
993
- // ssh-add /root/.ssh/id_rsa
1871
+ // Update exports:
1872
+ // shellExec(`sudo exportfs -a -r`);
1873
+ // shellExec(`sudo exportfs -v`);
994
1874
 
995
- // remove
996
- // ssh-add -d /path/to/private/key
1875
+ // Active nfs
1876
+ shellExec(`sudo exportfs -s`);
997
1877
 
998
- // remove all
999
- // ssh-add -D
1878
+ shellExec(`sudo exportfs -rav`);
1000
1879
 
1001
- // sshpass -p ${{ secrets.PSWD }} ssh -o StrictHostKeyChecking=no -p 22 ${{ secrets.USER}}@${{ secrets.VPS_IP }} 'cd /home/adam && ./deploy.sh'
1880
+ // Rocky enable virt_use_nfs
1881
+ // sudo setsebool -P virt_use_nfs 1
1002
1882
 
1003
- // copies the public key of your default identity (use -i identity_file for other identities) to the remote host.
1004
- // ssh-copy-id user@hostname.example.com
1005
- // ssh-copy-id "user@hostname.example.com -p <port-number>"
1883
+ // Disable share:
1884
+ // sudo exportfs -u <client-ip>:${process.env.NFS_EXPORT_PATH}/rpi4mb
1006
1885
 
1886
+ // Nfs client:
1887
+ // mount -t nfs <server-ip>:/server-mnt /mnt
1888
+ // umount /mnt
1889
+
1890
+ shellExec(`sudo systemctl restart nfs-server`);
1891
+ break;
1892
+ }
1893
+ case 'update-virtual-root': {
1894
+ dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1895
+ const IP_ADDRESS = getLocalIPv4Address();
1896
+ const architecture = process.argv[3];
1897
+ const host = process.argv[4];
1898
+ const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
1899
+ const ipaddr = process.env.RPI4_IP;
1900
+ await updateVirtualRoot({
1901
+ IP_ADDRESS,
1902
+ architecture,
1903
+ host,
1904
+ nfsHostPath,
1905
+ ipaddr,
1906
+ });
1007
1907
  break;
1008
1908
  }
1909
+ case 'open-virtual-root': {
1910
+ dotenv.config({ path: `${getUnderpostRootPath()}/.env`, override: true });
1911
+ const IP_ADDRESS = getLocalIPv4Address();
1912
+ const architecture = process.argv[3];
1913
+ const host = process.argv[4];
1914
+ const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
1915
+ shellExec(`sudo dnf install -y iptables-legacy`);
1916
+ shellExec(`sudo dnf install -y debootstrap`);
1917
+ shellExec(`sudo dnf install kernel-modules-extra-$(uname -r)`);
1918
+ switch (architecture) {
1919
+ case 'arm64':
1920
+ shellExec(`sudo podman run --rm --privileged multiarch/qemu-user-static --reset -p yes`);
1009
1921
 
1010
- case 'valkey': {
1011
- if (!process.argv.includes('server')) {
1012
- shellExec(`cd /dd && git clone https://github.com/valkey-io/valkey.git`);
1013
- shellExec(`cd /dd/valkey && make`);
1014
- shellExec(`apt install valkey-tools`); // valkey-cli
1922
+ break;
1923
+
1924
+ default:
1925
+ break;
1926
+ }
1927
+
1928
+ shellExec(`sudo modprobe binfmt_misc`);
1929
+ shellExec(`sudo mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc`);
1930
+
1931
+ if (process.argv.includes('build')) {
1932
+ // shellExec(`depmod -a`);
1933
+ shellExec(`mkdir -p ${nfsHostPath}`);
1934
+ let cmd;
1935
+ switch (host) {
1936
+ case 'rpi4mb':
1937
+ shellExec(`sudo rm -rf ${nfsHostPath}/*`);
1938
+ shellExec(`sudo chown -R root:root ${nfsHostPath}`);
1939
+ cmd = [
1940
+ `sudo debootstrap`,
1941
+ `--arch=arm64`,
1942
+ `--variant=minbase`,
1943
+ `--foreign`, // arm64 on amd64
1944
+ `noble`,
1945
+ nfsHostPath,
1946
+ `http://ports.ubuntu.com/ubuntu-ports/`,
1947
+ ];
1948
+ break;
1949
+
1950
+ default:
1951
+ break;
1952
+ }
1953
+ shellExec(cmd.join(' '));
1954
+
1955
+ shellExec(`sudo podman create --name extract multiarch/qemu-user-static`);
1956
+ shellExec(`podman ps -a`);
1957
+ shellExec(`sudo podman cp extract:/usr/bin/qemu-aarch64-static ${nfsHostPath}/usr/bin/`);
1958
+ shellExec(`sudo podman rm extract`);
1959
+ shellExec(`podman ps -a`);
1960
+
1961
+ switch (host) {
1962
+ case 'rpi4mb':
1963
+ shellExec(`file ${nfsHostPath}/bin/bash`); // expected: ELF 64-bit LSB pie executable, ARM aarch64 …
1964
+ break;
1965
+
1966
+ default:
1967
+ break;
1968
+ }
1969
+
1970
+ shellExec(`sudo chroot ${nfsHostPath} /usr/bin/qemu-aarch64-static /bin/bash <<'EOF'
1971
+ /debootstrap/debootstrap --second-stage
1972
+ EOF`);
1973
+ }
1974
+ if (process.argv.includes('mount')) {
1975
+ shellExec(`sudo mount --bind /proc ${nfsHostPath}/proc`);
1976
+ shellExec(`sudo mount --bind /sys ${nfsHostPath}/sys`);
1977
+ shellExec(`sudo mount --rbind /dev ${nfsHostPath}/dev`);
1978
+ }
1979
+
1980
+ if (process.argv.includes('build')) {
1981
+ switch (host) {
1982
+ case 'rpi4mb':
1983
+ const ipaddr = process.env.RPI4_IP;
1984
+
1985
+ await updateVirtualRoot({
1986
+ IP_ADDRESS,
1987
+ architecture,
1988
+ host,
1989
+ nfsHostPath,
1990
+ ipaddr,
1991
+ });
1992
+
1993
+ break;
1994
+
1995
+ default:
1996
+ break;
1997
+ }
1015
1998
  }
1016
- shellExec(`cd /dd/valkey && ./src/valkey-server`);
1999
+ // if (process.argv.includes('mount')) {
2000
+ // shellExec(`sudo mount --bind /lib/modules ${nfsHostPath}/lib/modules`);
2001
+ // }
1017
2002
 
1018
2003
  break;
1019
2004
  }
1020
2005
 
1021
- case 'valkey-service': {
1022
- shellExec(`pm2 start bin/deploy.js --node-args=\"--max-old-space-size=8192\" --name valkey -- valkey server`);
2006
+ case 'close-virtual-root': {
2007
+ const architecture = process.argv[3];
2008
+ const host = process.argv[4];
2009
+ const nfsHostPath = `${process.env.NFS_EXPORT_PATH}/${host}`;
2010
+ shellExec(`sudo umount ${nfsHostPath}/proc`);
2011
+ shellExec(`sudo umount ${nfsHostPath}/sys`);
2012
+ shellExec(`sudo umount ${nfsHostPath}/dev`);
2013
+ // shellExec(`sudo umount ${nfsHostPath}/lib/modules`);
2014
+ break;
2015
+ }
2016
+
2017
+ case 'mount': {
2018
+ const mounts = shellExec(`mount`).split(`\n`);
2019
+ console.table(
2020
+ mounts
2021
+ .filter((l) => l.trim())
2022
+ .map(
2023
+ (o) => (
2024
+ (o = o.split(' ')),
2025
+ {
2026
+ path: o[2],
2027
+ type: o[4],
2028
+ permissions: o[5],
2029
+ }
2030
+ ),
2031
+ ),
2032
+ );
2033
+ break;
2034
+ }
2035
+
2036
+ case 'create-ports': {
2037
+ const cmd = [];
2038
+ const ipaddr = getLocalIPv4Address();
2039
+ for (const port of ['5240']) {
2040
+ const name = 'maas';
2041
+ cmd.push(`${name}:${port}-${port}:${ipaddr}`);
2042
+ }
2043
+ pbcopy(`node engine-private/r create-port ${cmd}`);
2044
+ break;
2045
+ }
2046
+
2047
+ case 'maas-ports': {
2048
+ // Configure firewall:
2049
+
2050
+ // systemctl stop firewalld
2051
+ // systemctl mask firewalld
2052
+
2053
+ // ufw disable
2054
+ // ufw enable
2055
+
2056
+ // sudo snap install ufw
2057
+ // const ports = ['80', '443', '22', '3000-3100'];
2058
+ const ports = [
2059
+ '43',
2060
+ '53',
2061
+ '60',
2062
+ '66',
2063
+ '67',
2064
+ '69',
2065
+ '4011',
2066
+ '111',
2067
+ '2049',
2068
+ '20048',
2069
+ '20049',
2070
+ '32765',
2071
+ '32766',
2072
+ '5248',
2073
+ '5240',
2074
+ ];
2075
+ for (const port of ports) {
2076
+ shellExec(`ufw allow ${port}/tcp`);
2077
+ shellExec(`ufw allow ${port}/udp`);
2078
+ }
2079
+
2080
+ shellExec(`sudo systemctl mask firewalld`);
2081
+
2082
+ break;
2083
+ }
2084
+
2085
+ case 'iptables': {
2086
+ shellExec(`sudo systemctl enable nftables`);
2087
+ shellExec(`sudo systemctl restart nftables`);
2088
+
2089
+ shellExec(`sudo tee /etc/nftables.conf <<EOF
2090
+ table inet filter {
2091
+ chain input {
2092
+ type filter hook input priority 0;
2093
+ policy drop;
2094
+ tcp dport 22 accept
2095
+ }
2096
+ }
2097
+ EOF`);
2098
+ shellExec(`sudo nft -f /etc/nftables.conf`);
2099
+
2100
+ // sudo systemctl stop nftables
2101
+ // sudo systemctl disable nftables
2102
+
2103
+ break;
2104
+ }
2105
+
2106
+ case 'rpi4': {
2107
+ // Rpi4 Run Bootloader:
2108
+
2109
+ // 1) create boot.conf
2110
+
2111
+ // 2) Run lite RPiOs from rpi-imager
2112
+ // with boot.conf files in root disk path
2113
+
2114
+ // 3) cd /boot/firmware && sudo rpi-eeprom-config --apply boot.conf
2115
+
2116
+ // 4) sudo reboot
2117
+
2118
+ // 5) check: 'vcgencmd bootloader_version'
2119
+ // 6) check: 'vcgencmd bootloader_config'
2120
+
2121
+ // 7) shutdown and restart without sd card
2122
+
2123
+ // sudo apt update
2124
+ // sudo apt install git
2125
+
2126
+ break;
2127
+ }
2128
+
2129
+ case 'blue': {
2130
+ // lsusb | grep blue -i
2131
+ // rfkill list
2132
+ // sudo service bluetooth start
2133
+ // bluetoothctl show
2134
+ // sudo rfkill unblock bluetooth
2135
+ // dmesg | grep -i bluetooth
2136
+ // journalctl -u bluetooth -f
2137
+ // sudo dnf update bluez bluez-libs bluez-utils
2138
+ // sudo rmmod btusb
2139
+ // sudo modprobe btusb
1023
2140
  break;
1024
2141
  }
1025
2142
 
1026
2143
  default:
1027
2144
  break;
2145
+
2146
+ case 'fastapi': {
2147
+ // https://github.com/NonsoEchendu/full-stack-fastapi-project
2148
+ // https://github.com/fastapi/full-stack-fastapi-template
2149
+ const path = `../full-stack-fastapi-template`;
2150
+ if (process.argv.includes('env')) {
2151
+ const password = fs.readFileSync(`/home/dd/engine/engine-private/postgresql-password`, 'utf8');
2152
+
2153
+ fs.writeFileSync(
2154
+ `${path}/.env`,
2155
+ fs
2156
+ .readFileSync(`${path}/.env`, 'utf8')
2157
+ .replace(`FIRST_SUPERUSER=admin@example.com`, `FIRST_SUPERUSER=development@underpost.net`)
2158
+ .replace(`FIRST_SUPERUSER_PASSWORD=changethis`, `FIRST_SUPERUSER_PASSWORD=${password}`)
2159
+ .replace(`SECRET_KEY=changethis`, `SECRET_KEY=${password}`)
2160
+ .replace(`POSTGRES_DB=app`, `POSTGRES_DB=postgresdb`)
2161
+ .replace(`POSTGRES_USER=postgres`, `POSTGRES_USER=admin`)
2162
+ .replace(`POSTGRES_PASSWORD=changethis`, `POSTGRES_PASSWORD=${password}`),
2163
+ 'utf8',
2164
+ );
2165
+ fs.writeFileSync(
2166
+ `${path}/backend/app/core/db.py`,
2167
+ fs
2168
+ .readFileSync(`${path}/backend/app/core/db.py`, 'utf8')
2169
+ .replace(` # from sqlmodel import SQLModel`, ` from sqlmodel import SQLModel`)
2170
+ .replace(` # SQLModel.metadata.create_all(engine)`, ` SQLModel.metadata.create_all(engine)`),
2171
+
2172
+ 'utf8',
2173
+ );
2174
+ }
2175
+ if (process.argv.includes('build-back')) {
2176
+ const imageName = `fastapi-backend:latest`;
2177
+ shellExec(`sudo podman pull docker.io/library/python:3.10`);
2178
+ shellExec(`sudo podman pull ghcr.io/astral-sh/uv:0.5.11`);
2179
+ shellExec(`sudo rm -rf ${path}/${imageName.replace(':', '_')}.tar`);
2180
+ const args = [
2181
+ `node bin dockerfile-image-build --path ${path}/backend/`,
2182
+ `--image-name=${imageName} --image-path=${path}`,
2183
+ `--podman-save --kind-load --no-cache`,
2184
+ ];
2185
+ shellExec(args.join(' '));
2186
+ }
2187
+ if (process.argv.includes('build-front')) {
2188
+ const imageName = `fastapi-frontend:latest`;
2189
+ shellExec(`sudo podman pull docker.io/library/node:20`);
2190
+ shellExec(`sudo podman pull docker.io/library/nginx:1`);
2191
+ shellExec(`sudo rm -rf ${path}/${imageName.replace(':', '_')}.tar`);
2192
+ const args = [
2193
+ `node bin dockerfile-image-build --path ${path}/frontend/`,
2194
+ `--image-name=${imageName} --image-path=${path}`,
2195
+ `--podman-save --kind-load --no-cache`,
2196
+ ];
2197
+ shellExec(args.join(' '));
2198
+ }
2199
+ if (process.argv.includes('build') || process.argv.includes('secret')) {
2200
+ {
2201
+ const secretSelector = `fastapi-postgres-credentials`;
2202
+ shellExec(`sudo kubectl delete secret ${secretSelector}`);
2203
+ shellExec(
2204
+ `sudo kubectl create secret generic ${secretSelector}` +
2205
+ ` --from-literal=POSTGRES_DB=postgresdb` +
2206
+ ` --from-literal=POSTGRES_USER=admin` +
2207
+ ` --from-file=POSTGRES_PASSWORD=/home/dd/engine/engine-private/postgresql-password`,
2208
+ );
2209
+ }
2210
+ {
2211
+ const secretSelector = `fastapi-backend-config-secret`;
2212
+ shellExec(`sudo kubectl delete secret ${secretSelector}`);
2213
+ shellExec(
2214
+ `sudo kubectl create secret generic ${secretSelector}` +
2215
+ ` --from-file=SECRET_KEY=/home/dd/engine/engine-private/postgresql-password` +
2216
+ ` --from-literal=FIRST_SUPERUSER=development@underpost.net` +
2217
+ ` --from-file=FIRST_SUPERUSER_PASSWORD=/home/dd/engine/engine-private/postgresql-password`,
2218
+ );
2219
+ }
2220
+ }
2221
+ if (process.argv.includes('run-back')) {
2222
+ shellExec(`sudo kubectl apply -f ./manifests/deployment/fastapi/backend-deployment.yml`);
2223
+ shellExec(`sudo kubectl apply -f ./manifests/deployment/fastapi/backend-service.yml`);
2224
+ }
2225
+ if (process.argv.includes('run-front')) {
2226
+ shellExec(`sudo kubectl apply -f ./manifests/deployment/fastapi/frontend-deployment.yml`);
2227
+ shellExec(`sudo kubectl apply -f ./manifests/deployment/fastapi/frontend-service.yml`);
2228
+ }
2229
+ break;
2230
+ }
2231
+
2232
+ case 'conda': {
2233
+ shellExec(
2234
+ `export PATH="/root/miniconda3/bin:$PATH" && conda init && conda config --set auto_activate_base false`,
2235
+ );
2236
+ shellExec(`conda env list`);
2237
+ break;
2238
+ }
2239
+
2240
+ case 'kafka': {
2241
+ // https://medium.com/@martin.hodges/deploying-kafka-on-a-kind-kubernetes-cluster-for-development-and-testing-purposes-ed7adefe03cb
2242
+ const imageName = `doughgle/kafka-kraft`;
2243
+ shellExec(`docker pull ${imageName}`);
2244
+ shellExec(`kind load docker-image ${imageName}`);
2245
+ shellExec(`kubectl create namespace kafka`);
2246
+ shellExec(`kubectl apply -f ./manifests/deployment/kafka/deployment.yaml`);
2247
+ // kubectl logs kafka-0 -n kafka | grep STARTED
2248
+ // kubectl logs kafka-1 -n kafka | grep STARTED
2249
+ // kubectl logs kafka-2 -n kafka | grep STARTED
2250
+
2251
+ // kafka-topics.sh --create --topic my-topic --bootstrap-server kafka-svc:9092
2252
+ // kafka-topics.sh --list --topic my-topic --bootstrap-server kafka-svc:9092
2253
+ // kafka-topics.sh --delete --topic my-topic --bootstrap-server kafka-svc:9092
2254
+
2255
+ // kafka-console-producer.sh --bootstrap-server kafka-svc:9092 --topic my-topic
2256
+ // kafka-console-consumer.sh --bootstrap-server kafka-svc:9092 --topic my-topic
2257
+ break;
2258
+ }
1028
2259
  }
1029
2260
  } catch (error) {
1030
2261
  logger.error(error, error.stack);