underpost 2.8.6 → 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 (79) hide show
  1. package/.vscode/extensions.json +3 -2
  2. package/.vscode/settings.json +2 -0
  3. package/CHANGELOG.md +24 -4
  4. package/Dockerfile +9 -10
  5. package/README.md +39 -2
  6. package/bin/build.js +2 -2
  7. package/bin/deploy.js +1337 -131
  8. package/bin/file.js +8 -0
  9. package/bin/index.js +1 -218
  10. package/cli.md +451 -0
  11. package/docker-compose.yml +1 -1
  12. package/jsdoc.json +1 -1
  13. package/manifests/calico-custom-resources.yaml +25 -0
  14. package/manifests/deployment/adminer/deployment.yaml +32 -0
  15. package/manifests/deployment/adminer/kustomization.yaml +7 -0
  16. package/manifests/deployment/adminer/service.yaml +13 -0
  17. package/manifests/deployment/fastapi/backend-deployment.yml +120 -0
  18. package/manifests/deployment/fastapi/backend-service.yml +19 -0
  19. package/manifests/deployment/fastapi/frontend-deployment.yml +54 -0
  20. package/manifests/deployment/fastapi/frontend-service.yml +15 -0
  21. package/manifests/deployment/kafka/deployment.yaml +69 -0
  22. package/manifests/kubeadm-calico-config.yaml +119 -0
  23. package/manifests/mongodb-4.4/service-deployment.yaml +1 -1
  24. package/manifests/postgresql/configmap.yaml +9 -0
  25. package/manifests/postgresql/kustomization.yaml +10 -0
  26. package/manifests/postgresql/pv.yaml +15 -0
  27. package/manifests/postgresql/pvc.yaml +13 -0
  28. package/manifests/postgresql/service.yaml +10 -0
  29. package/manifests/postgresql/statefulset.yaml +37 -0
  30. package/manifests/valkey/statefulset.yaml +6 -4
  31. package/package.json +3 -9
  32. package/src/api/default/default.service.js +1 -1
  33. package/src/api/user/user.service.js +14 -11
  34. package/src/cli/cluster.js +207 -20
  35. package/src/cli/cron.js +39 -8
  36. package/src/cli/db.js +20 -10
  37. package/src/cli/deploy.js +254 -85
  38. package/src/cli/env.js +9 -3
  39. package/src/cli/fs.js +21 -9
  40. package/src/cli/image.js +42 -124
  41. package/src/cli/index.js +312 -0
  42. package/src/cli/monitor.js +236 -0
  43. package/src/cli/repository.js +5 -2
  44. package/src/client/components/core/Account.js +28 -24
  45. package/src/client/components/core/Blockchain.js +1 -1
  46. package/src/client/components/core/CalendarCore.js +14 -84
  47. package/src/client/components/core/CommonJs.js +2 -1
  48. package/src/client/components/core/Css.js +0 -1
  49. package/src/client/components/core/CssCore.js +10 -2
  50. package/src/client/components/core/Docs.js +1 -1
  51. package/src/client/components/core/EventsUI.js +3 -3
  52. package/src/client/components/core/FileExplorer.js +86 -78
  53. package/src/client/components/core/JoyStick.js +2 -2
  54. package/src/client/components/core/LoadingAnimation.js +1 -17
  55. package/src/client/components/core/LogIn.js +3 -3
  56. package/src/client/components/core/LogOut.js +1 -1
  57. package/src/client/components/core/Modal.js +14 -8
  58. package/src/client/components/core/Panel.js +19 -61
  59. package/src/client/components/core/PanelForm.js +13 -22
  60. package/src/client/components/core/Recover.js +3 -3
  61. package/src/client/components/core/RichText.js +1 -11
  62. package/src/client/components/core/Router.js +3 -1
  63. package/src/client/components/core/SignUp.js +2 -2
  64. package/src/client/components/default/RoutesDefault.js +3 -2
  65. package/src/client/services/default/default.management.js +45 -38
  66. package/src/client/ssr/Render.js +2 -0
  67. package/src/index.js +18 -2
  68. package/src/mailer/MailerProvider.js +3 -0
  69. package/src/runtime/lampp/Dockerfile +65 -0
  70. package/src/server/client-build.js +13 -0
  71. package/src/server/conf.js +93 -1
  72. package/src/server/dns.js +56 -18
  73. package/src/server/json-schema.js +77 -0
  74. package/src/server/network.js +7 -122
  75. package/src/server/peer.js +2 -2
  76. package/src/server/proxy.js +4 -4
  77. package/src/server/runtime.js +24 -11
  78. package/src/server/start.js +122 -0
  79. package/src/server/valkey.js +25 -11
@@ -63,7 +63,7 @@ const SignUp = {
63
63
  });
64
64
  return html`
65
65
  ${await BtnIcon.Render({
66
- class: 'section-mp form-button btn-sign-up-i-have-account',
66
+ class: 'in section-mp form-button btn-sign-up-i-have-account',
67
67
  label: html`<i class="fas fa-sign-in-alt"></i> ${Translate.Render('i-have-account')}<br />${Translate.Render(
68
68
  'log-in',
69
69
  )}`,
@@ -112,7 +112,7 @@ const SignUp = {
112
112
  ${options?.bottomRender ? await options.bottomRender() : ``}
113
113
  <div class="in">
114
114
  ${await BtnIcon.Render({
115
- class: 'section-mp form-button btn-sign-up',
115
+ class: 'in section-mp form-button btn-sign-up',
116
116
  label: Translate.Render('sign-up'),
117
117
  type: 'submit',
118
118
  })}
@@ -1,4 +1,5 @@
1
1
  import { loggerFactory } from '../core/Logger.js';
2
+ import { Modal } from '../core/Modal.js';
2
3
  import { getProxyPath, s } from '../core/VanillaJs.js';
3
4
 
4
5
  const logger = loggerFactory(import.meta);
@@ -10,10 +11,10 @@ const RoutesDefault = () => {
10
11
  return {
11
12
  '/': {
12
13
  title: 'Home',
13
- render: () => s(`.main-btn-home`).click(),
14
+ render: () => Modal.onHomeRouterEvent(),
14
15
  upperCase: false,
15
16
  },
16
- '/home': { title: 'home', render: () => s(`.main-btn-home`).click() },
17
+ '/home': { title: 'home', render: () => Modal.onHomeRouterEvent() },
17
18
  '/settings': { title: 'settings', render: () => s(`.main-btn-settings`).click(), translateTitle: true },
18
19
  '/log-in': { title: 'log-in', render: () => s(`.main-btn-log-in`).click(), translateTitle: true },
19
20
  '/sign-up': { title: 'sign-up', render: () => s(`.main-btn-sign-up`).click(), translateTitle: true },
@@ -80,36 +80,40 @@ const DefaultManagement = {
80
80
  class: `in fll section-mp management-table-btn-mini management-table-btn-remove-${id}-${cellRenderId}`,
81
81
  })}`;
82
82
  setTimeout(() => {
83
- EventsUI.onClick(`.management-table-btn-remove-${id}-${cellRenderId}`, async () => {
84
- const confirmResult = await Modal.RenderConfirm({
85
- html: async () => {
86
- return html`
87
- <div class="in section-mp" style="text-align: center">
88
- ${Translate.Render('confirm-delete-item')}
89
- ${Object.keys(params.data).length > 0
90
- ? html`<br />
91
- "${options.defaultColKeyFocus
92
- ? getValueFromJoinString(params.data, options.defaultColKeyFocus)
93
- : params.data[Object.keys(params.data)[0]]}"`
94
- : ''}
95
- </div>
96
- `;
97
- },
98
- id: `delete-${params.data._id}`,
99
- });
100
- if (confirmResult.status !== 'confirm') return;
101
- let result;
102
- if (params.data._id) result = await ServiceProvider.delete({ id: params.data._id });
103
- else result = { status: 'success' };
83
+ EventsUI.onClick(
84
+ `.management-table-btn-remove-${id}-${cellRenderId}`,
85
+ async () => {
86
+ const confirmResult = await Modal.RenderConfirm({
87
+ html: async () => {
88
+ return html`
89
+ <div class="in section-mp" style="text-align: center">
90
+ ${Translate.Render('confirm-delete-item')}
91
+ ${Object.keys(params.data).length > 0
92
+ ? html`<br />
93
+ "${options.defaultColKeyFocus
94
+ ? getValueFromJoinString(params.data, options.defaultColKeyFocus)
95
+ : params.data[Object.keys(params.data)[0]]}"`
96
+ : ''}
97
+ </div>
98
+ `;
99
+ },
100
+ id: `delete-${params.data._id}`,
101
+ });
102
+ if (confirmResult.status !== 'confirm') return;
103
+ let result;
104
+ if (params.data._id) result = await ServiceProvider.delete({ id: params.data._id });
105
+ else result = { status: 'success' };
104
106
 
105
- NotificationManager.Push({
106
- html: result.status === 'error' ? result.message : Translate.Render('item-success-delete'),
107
- status: result.status,
108
- });
109
- if (result.status === 'success') {
110
- AgGrid.grids[gridId].applyTransaction({ remove: [params.data] });
111
- }
112
- });
107
+ NotificationManager.Push({
108
+ html: result.status === 'error' ? result.message : Translate.Render('item-success-delete'),
109
+ status: result.status,
110
+ });
111
+ if (result.status === 'success') {
112
+ AgGrid.grids[gridId].applyTransaction({ remove: [params.data] });
113
+ }
114
+ },
115
+ { context: 'modal' },
116
+ );
113
117
  });
114
118
  }
115
119
 
@@ -220,16 +224,19 @@ const DefaultManagement = {
220
224
  });
221
225
  });
222
226
  EventsUI.onClick(`.management-table-btn-clean-${id}`, async () => {
223
- const confirmResult = await Modal.RenderConfirm({
224
- html: async () => {
225
- return html`
226
- <div class="in section-mp" style="text-align: center;">
227
- <strong>${Translate.Render('confirm-delete-all-data')}</strong>
228
- </div>
229
- `;
227
+ const confirmResult = await Modal.RenderConfirm(
228
+ {
229
+ html: async () => {
230
+ return html`
231
+ <div class="in section-mp" style="text-align: center;">
232
+ <strong>${Translate.Render('confirm-delete-all-data')}</strong>
233
+ </div>
234
+ `;
235
+ },
236
+ id: `clean-table-${id}`,
230
237
  },
231
- id: `clean-table-${id}`,
232
- });
238
+ { context: 'modal' },
239
+ );
233
240
  if (confirmResult.status === 'cancelled') return;
234
241
  const result = await ServiceProvider.delete();
235
242
  NotificationManager.Push({
@@ -133,6 +133,8 @@ SrrComponent = ({ title, ssrPath, buildId, ssrHeadComponents, ssrBodyComponents,
133
133
  border: none;
134
134
  padding-block: 0;
135
135
  padding-inline: 0;
136
+ height: 30px;
137
+ line-height: 30px;
136
138
  }
137
139
  input::file-selector-button {
138
140
  outline: none !important;
package/src/index.js CHANGED
@@ -11,10 +11,12 @@ import UnderpostDeploy from './cli/deploy.js';
11
11
  import UnderpostRootEnv from './cli/env.js';
12
12
  import UnderpostFileStorage from './cli/fs.js';
13
13
  import UnderpostImage from './cli/image.js';
14
+ import UnderpostMonitor from './cli/monitor.js';
14
15
  import UnderpostRepository from './cli/repository.js';
15
16
  import UnderpostScript from './cli/script.js';
16
17
  import UnderpostSecret from './cli/secrets.js';
17
18
  import UnderpostTest from './cli/test.js';
19
+ import UnderpostStartUp from './server/start.js';
18
20
 
19
21
  /**
20
22
  * Underpost main module methods
@@ -28,7 +30,7 @@ class Underpost {
28
30
  * @type {String}
29
31
  * @memberof Underpost
30
32
  */
31
- static version = 'v2.8.6';
33
+ static version = 'v2.8.7';
32
34
  /**
33
35
  * Repository cli API
34
36
  * @static
@@ -50,6 +52,13 @@ class Underpost {
50
52
  * @memberof Underpost
51
53
  */
52
54
  static test = UnderpostTest.API;
55
+ /**
56
+ * Underpost Start Up cli API
57
+ * @static
58
+ * @type {UnderpostStartUp.API}
59
+ * @memberof Underpost
60
+ */
61
+ static start = UnderpostStartUp.API;
53
62
  /**
54
63
  * Cluster cli API
55
64
  * @static
@@ -103,9 +112,16 @@ class Underpost {
103
112
  * File Storage cli API
104
113
  * @static
105
114
  * @type {UnderpostFileStorage.API}
106
- * @memberof UnderpostFileStorage
115
+ * @memberof Underpost
107
116
  */
108
117
  static fs = UnderpostFileStorage.API;
118
+ /**
119
+ * Monitor cli API
120
+ * @static
121
+ * @type {UnderpostMonitor.API}
122
+ * @memberof Underpost
123
+ */
124
+ static monitor = UnderpostMonitor.API;
109
125
  }
110
126
 
111
127
  const up = Underpost;
@@ -32,6 +32,9 @@ const MailerProvider = {
32
32
  },
33
33
  ) {
34
34
  try {
35
+ options.transport.tls = {
36
+ rejectUnauthorized: false,
37
+ };
35
38
  const { id } = options;
36
39
  // Generate test SMTP service account from ethereal.email
37
40
  // Only needed if you don't have a real mail account for testing
@@ -0,0 +1,65 @@
1
+ ARG BASE_DEBIAN=buster
2
+
3
+ USER root
4
+
5
+ FROM debian:${BASE_DEBIAN}
6
+
7
+ ENV DEBIAN_FRONTEND=noninteractive
8
+
9
+ # Set root password to root, format is 'user:password'.
10
+ RUN echo 'root:root' | chpasswd
11
+
12
+ RUN apt-get update --fix-missing
13
+ RUN apt-get upgrade -y
14
+ # install sudo
15
+ RUN apt-get -y install sudo
16
+ # net-tools provides netstat commands
17
+ RUN apt-get -y install curl net-tools
18
+ RUN apt-get -yq install openssh-server supervisor
19
+ # Few handy utilities which are nice to have
20
+ RUN apt-get -y install nano vim less --no-install-recommends
21
+ RUN apt-get clean
22
+
23
+ # install ssh
24
+ RUN mkdir -p /var/run/sshd
25
+ # Allow root login via password
26
+ RUN sed -ri 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config
27
+
28
+ # install open ssl git and others tools
29
+ RUN apt-get install -yq --no-install-recommends libssl-dev curl wget git gnupg
30
+
31
+ # install lampp
32
+ RUN curl -Lo xampp-linux-installer.run https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/7.4.33/xampp-linux-x64-7.4.33-0-installer.run?from_af=true
33
+ RUN chmod +x xampp-linux-installer.run
34
+ RUN bash -c './xampp-linux-installer.run'
35
+ RUN ln -sf /opt/lampp/lampp /usr/bin/lampp
36
+ # Enable XAMPP web interface(remove security checks)
37
+ RUN sed -i.bak s'/Require local/Require all granted/g' /opt/lampp/etc/extra/httpd-xampp.conf
38
+ # Enable error display in php
39
+ RUN sed -i.bak s'/display_errors=Off/display_errors=On/g' /opt/lampp/etc/php.ini
40
+ # Enable includes of several configuration files
41
+ RUN mkdir /opt/lampp/apache2/conf.d
42
+ RUN echo "IncludeOptional /opt/lampp/apache2/conf.d/*.conf" >>/opt/lampp/etc/httpd.conf
43
+ # Create a /www folder and a symbolic link to it in /opt/lampp/htdocs. It'll be accessible via http://localhost:[port]/www/
44
+ # This is convenient because it doesn't interfere with xampp, phpmyadmin or other tools in /opt/lampp/htdocs
45
+ # /opt/lampp/etc/httpd.conf
46
+ RUN mkdir /www
47
+ RUN ln -s /www /opt/lampp/htdocs
48
+
49
+ # install nodejs https://github.com/nodesource/distributions/blob/master/README.md#deb
50
+ RUN curl -fsSL https://deb.nodesource.com/setup_23.x | bash -
51
+ RUN apt-get install -y nodejs build-essential
52
+ RUN node --version
53
+ RUN npm --version
54
+
55
+ WORKDIR /home/dd
56
+
57
+ EXPOSE 22
58
+
59
+ EXPOSE 80
60
+
61
+ EXPOSE 443
62
+
63
+ EXPOSE 3000-3100
64
+
65
+ EXPOSE 4000-4100
@@ -683,6 +683,19 @@ Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
683
683
  root file where the route starts, such as index.js, app.js, routes.js, etc ... */
684
684
 
685
685
  await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
686
+
687
+ const htmlFiles = await fs.readdir(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}`);
688
+ for (const htmlFile of htmlFiles) {
689
+ if (htmlFile.match('.html')) {
690
+ fs.writeFileSync(
691
+ `./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`,
692
+ fs
693
+ .readFileSync(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`, 'utf8')
694
+ .replaceAll('Tutorials', 'References'),
695
+ 'utf8',
696
+ );
697
+ }
698
+ }
686
699
  }
687
700
 
688
701
  if (client) {
@@ -499,6 +499,40 @@ const buildProxyRouter = () => {
499
499
  return proxyRouter;
500
500
  };
501
501
 
502
+ const pathPortAssignmentFactory = (router, confServer) => {
503
+ const pathPortAssignmentData = {};
504
+ for (const host of Object.keys(confServer)) {
505
+ const pathPortAssignment = [];
506
+ for (const path of Object.keys(confServer[host])) {
507
+ const { peer } = confServer[host][path];
508
+ if (!router[`${host}${path === '/' ? '' : path}`]) continue;
509
+ const port = parseInt(router[`${host}${path === '/' ? '' : path}`].split(':')[2]);
510
+ // logger.info('', { host, port, path });
511
+ pathPortAssignment.push({
512
+ port,
513
+ path,
514
+ });
515
+
516
+ if (peer) {
517
+ // logger.info('', { host, port: port + 1, path: '/peer' });
518
+ pathPortAssignment.push({
519
+ port: port + 1,
520
+ path: '/peer',
521
+ });
522
+ }
523
+ }
524
+ pathPortAssignmentData[host] = pathPortAssignment;
525
+ }
526
+ return pathPortAssignmentData;
527
+ };
528
+
529
+ const deployRangePortFactory = (router) => {
530
+ const ports = Object.values(router).map((p) => parseInt(p.split(':')[2]));
531
+ const fromPort = Math.min(...ports);
532
+ const toPort = Math.max(...ports);
533
+ return { ports, fromPort, toPort };
534
+ };
535
+
502
536
  const buildKindPorts = (from, to) =>
503
537
  range(parseInt(from), parseInt(to))
504
538
  .map(
@@ -729,7 +763,7 @@ const validateTemplatePath = (absolutePath = '') => {
729
763
  return true;
730
764
  };
731
765
 
732
- const deployTest = async (dataDeploy) => {
766
+ const deployTest = async (dataDeploy = [{ deployId: 'default' }]) => {
733
767
  const failed = [];
734
768
  for (const deploy of dataDeploy) {
735
769
  const deployServerConfPath = fs.existsSync(`./engine-private/replica/${deploy.deployId}/conf.server.json`)
@@ -773,6 +807,12 @@ const deployTest = async (dataDeploy) => {
773
807
  return { failed };
774
808
  };
775
809
 
810
+ const awaitDeployMonitor = async (init = false, deltaMs = 1000) => {
811
+ if (init) fs.writeFileSync(`./tmp/await-deploy`, '', 'utf8');
812
+ await timer(deltaMs);
813
+ if (fs.existsSync(`./tmp/await-deploy`)) return await awaitDeployMonitor();
814
+ };
815
+
776
816
  const getDeployGroupId = () => {
777
817
  const deployGroupIndexArg = process.argv.findIndex((a) => a.match(`deploy-group:`));
778
818
  if (deployGroupIndexArg > -1) return process.argv[deployGroupIndexArg].split(':')[1].trim();
@@ -883,6 +923,53 @@ const mergeFile = async (parts = [], outputFilePath) => {
883
923
  });
884
924
  };
885
925
 
926
+ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
927
+ const confServer = loadReplicas(
928
+ JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
929
+ );
930
+ const hosts = {};
931
+ for (const host of Object.keys(confServer)) {
932
+ hosts[host] = {};
933
+ for (const path of Object.keys(confServer[host])) {
934
+ if (!confServer[host][path].db) continue;
935
+ const { singleReplica, replicas, db } = confServer[host][path];
936
+ const { provider } = db;
937
+ if (singleReplica) {
938
+ for (const replica of replicas) {
939
+ const deployIdReplica = buildReplicaId({ replica, deployId });
940
+ const confServerReplica = JSON.parse(
941
+ fs.readFileSync(`./engine-private/replica/${deployIdReplica}/conf.server.json`, 'utf8'),
942
+ );
943
+ for (const _host of Object.keys(confServerReplica)) {
944
+ for (const _path of Object.keys(confServerReplica[_host])) {
945
+ hosts[host][_path] = { replica: { host, path } };
946
+ confServerReplica[_host][_path].valkey = valkey;
947
+ switch (provider) {
948
+ case 'mongoose':
949
+ confServerReplica[_host][_path].db.host = mongo.host;
950
+ break;
951
+ }
952
+ }
953
+ }
954
+ fs.writeFileSync(
955
+ `./engine-private/replica/${deployIdReplica}/conf.server.json`,
956
+ JSON.stringify(confServerReplica, null, 4),
957
+ 'utf8',
958
+ );
959
+ }
960
+ } else hosts[host][path] = {};
961
+ confServer[host][path].valkey = valkey;
962
+ switch (provider) {
963
+ case 'mongoose':
964
+ confServer[host][path].db.host = mongo.host;
965
+ break;
966
+ }
967
+ }
968
+ }
969
+ fs.writeFileSync(`./engine-private/conf/${deployId}/conf.server.json`, JSON.stringify(confServer, null, 4), 'utf8');
970
+ return { hosts };
971
+ };
972
+
886
973
  const getRestoreCronCmd = async (options = { host: '', path: '', conf: {}, deployId: '' }) => {
887
974
  const { host, path, conf, deployId } = options;
888
975
  const { runtime, db, git, directory } = conf[host][path];
@@ -1122,4 +1209,9 @@ export {
1122
1209
  getNpmRootPath,
1123
1210
  getUnderpostRootPath,
1124
1211
  writeEnv,
1212
+ deployTest,
1213
+ pathPortAssignmentFactory,
1214
+ deployRangePortFactory,
1215
+ awaitDeployMonitor,
1216
+ rebuildConfFactory,
1125
1217
  };
package/src/server/dns.js CHANGED
@@ -2,43 +2,78 @@ import axios from 'axios';
2
2
  import dotenv from 'dotenv';
3
3
  import fs from 'fs';
4
4
  import validator from 'validator';
5
- import { ip } from './network.js';
5
+ import { publicIp, publicIpv4, publicIpv6 } from 'public-ip';
6
6
  import { loggerFactory } from './logger.js';
7
7
  import UnderpostRootEnv from '../cli/env.js';
8
+ import dns from 'node:dns';
9
+ import os from 'node:os';
10
+ import { shellExec } from './process.js';
8
11
 
9
12
  dotenv.config();
10
13
 
11
14
  const logger = loggerFactory(import.meta);
12
15
 
16
+ const ip = {
17
+ public: {
18
+ get: async () => await publicIp(), // => 'fe80::200:f8ff:fe21:67cf'
19
+ ipv4: async () => await publicIpv4(), // => '46.5.21.123'
20
+ ipv6: async () => await publicIpv6(), // => 'fe80::200:f8ff:fe21:67cf'
21
+ },
22
+ };
23
+
24
+ const isInternetConnection = (domain = 'google.com') =>
25
+ new Promise((resolve) => dns.lookup(domain, {}, (err) => resolve(err ? false : true)));
26
+
27
+ // export INTERFACE=$(ip route | grep default | cut -d ' ' -f 5)
28
+ // export IP_ADDRESS=$(ip -4 addr show dev $INTERFACE | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
29
+ const getLocalIPv4Address = () =>
30
+ os.networkInterfaces()[
31
+ shellExec(`ip route | grep default | cut -d ' ' -f 5`, {
32
+ stdout: true,
33
+ silent: true,
34
+ disableLog: true,
35
+ }).trim()
36
+ ].find((i) => i.family === 'IPv4').address;
37
+
13
38
  class Dns {
14
39
  static callback = async function (deployList) {
15
40
  // Network topology configuration:
16
41
  // LAN -> [NAT-VPS](modem/router device) -> WAN
17
42
  // enabled DMZ Host to proxy IP 80-443 (79-444) sometimes router block first port
18
- // disabled local red DHCP
43
+
44
+ // Enabling DHCP
45
+ // Navigate to Subnets > VLAN > Configure DHCP.
46
+ // Select the appropriate DHCP options (Managed or Relay).
47
+ // Save and apply changes.
48
+
19
49
  // verify inet ip proxy server address
20
50
  // DHCP (Dynamic Host Configuration Protocol) LAN reserver IP -> MAC ID
21
51
  // LAN server or device's local servers port -> 3000-3100 (2999-3101)
22
52
  // DNS Records: [ANAME](Address Dynamic) -> [A](ipv4) host | [AAAA](ipv6) host -> [public-ip]
23
53
  // Forward the router's TCP/UDP ports to the LAN device's IP address
24
- for (const _deployId of deployList.split(',')) {
25
- const deployId = _deployId.trim();
26
- const privateCronConfPath = `./engine-private/conf/${deployId}/conf.cron.json`;
27
- const confCronPath = fs.existsSync(privateCronConfPath) ? privateCronConfPath : './conf/conf.cron.json';
28
- const confCronData = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
29
-
30
- let testIp;
31
-
32
- try {
33
- testIp = await ip.public.ipv4();
34
- } catch (error) {
35
- logger.error(error, { testIp, stack: error.stack });
36
- }
54
+ const isOnline = await isInternetConnection();
37
55
 
38
- const currentIp = UnderpostRootEnv.API.get('ip');
56
+ if (!isOnline) return;
39
57
 
40
- if (testIp && typeof testIp === 'string' && validator.isIP(testIp) && currentIp !== testIp) {
41
- logger.info(`new ip`, testIp);
58
+ let testIp;
59
+
60
+ try {
61
+ testIp = await ip.public.ipv4();
62
+ } catch (error) {
63
+ logger.error(error, { testIp, stack: error.stack });
64
+ }
65
+
66
+ const currentIp = UnderpostRootEnv.API.get('ip');
67
+
68
+ if (validator.isIP(testIp) && currentIp !== testIp) {
69
+ logger.info(`new ip`, testIp);
70
+ UnderpostRootEnv.API.set('monitor-input', 'pause');
71
+
72
+ for (const _deployId of deployList.split(',')) {
73
+ const deployId = _deployId.trim();
74
+ const privateCronConfPath = `./engine-private/conf/${deployId}/conf.cron.json`;
75
+ const confCronPath = fs.existsSync(privateCronConfPath) ? privateCronConfPath : './conf/conf.cron.json';
76
+ const confCronData = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
42
77
  for (const recordType of Object.keys(confCronData.records)) {
43
78
  switch (recordType) {
44
79
  case 'A':
@@ -60,6 +95,7 @@ class Dns {
60
95
  if (verifyIp === testIp) {
61
96
  logger.info('ip updated successfully', testIp);
62
97
  UnderpostRootEnv.API.set('ip', testIp);
98
+ UnderpostRootEnv.API.delete('monitor-input');
63
99
  } else logger.error('ip not updated', testIp);
64
100
  } catch (error) {
65
101
  logger.error(error, error.stack);
@@ -94,3 +130,5 @@ class Dns {
94
130
  }
95
131
 
96
132
  export default Dns;
133
+
134
+ export { Dns, ip, isInternetConnection, getLocalIPv4Address };
@@ -0,0 +1,77 @@
1
+ function isPlainObject(obj) {
2
+ return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false;
3
+ }
4
+
5
+ const supportType = ['string', 'number', 'array', 'object', 'boolean', 'integer'];
6
+
7
+ function getType(type) {
8
+ if (!type) type = 'string';
9
+ if (supportType.indexOf(type) !== -1) {
10
+ return type;
11
+ }
12
+ return typeof type;
13
+ }
14
+
15
+ function isSchema(object) {
16
+ if (supportType.indexOf(object.type) !== -1) {
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+
22
+ function handleSchema(json, schema) {
23
+ Object.assign(schema, json);
24
+ if (schema.type === 'object') {
25
+ delete schema.properties;
26
+ parse(json.properties, schema);
27
+ }
28
+ if (schema.type === 'array') {
29
+ delete schema.items;
30
+ schema.items = {};
31
+ parse(json.items, schema.items);
32
+ }
33
+ }
34
+
35
+ function handleArray(arr, schema) {
36
+ schema.type = 'array';
37
+ let props = (schema.items = {});
38
+ parse(arr[0], props);
39
+ }
40
+
41
+ function handleObject(json, schema) {
42
+ if (isSchema(json)) {
43
+ return handleSchema(json, schema);
44
+ }
45
+ schema.type = 'object';
46
+ schema.required = [];
47
+ let props = (schema.properties = {});
48
+ for (let key in json) {
49
+ let item = json[key];
50
+ let curSchema = (props[key] = {});
51
+ if (key[0] === '*') {
52
+ delete props[key];
53
+ key = key.substr(1);
54
+ schema.required.push(key);
55
+ curSchema = props[key] = {};
56
+ }
57
+ parse(item, curSchema);
58
+ }
59
+ }
60
+
61
+ function parse(json, schema) {
62
+ if (Array.isArray(json)) {
63
+ handleArray(json, schema);
64
+ } else if (isPlainObject(json)) {
65
+ handleObject(json, schema);
66
+ } else {
67
+ schema.type = getType(json);
68
+ }
69
+ }
70
+
71
+ function ejs(data) {
72
+ let JsonSchema = {};
73
+ parse(data, JsonSchema);
74
+ return JsonSchema;
75
+ }
76
+
77
+ export { ejs };