underpost 2.8.65 → 2.8.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import axios from 'axios';
5
5
  import UnderpostRootEnv from './env.js';
6
6
  import fs from 'fs-extra';
7
7
  import { shellExec } from '../server/process.js';
8
+ import { isInternetConnection } from '../server/dns.js';
8
9
 
9
10
  const logger = loggerFactory(import.meta);
10
11
 
@@ -13,7 +14,8 @@ class UnderpostMonitor {
13
14
  async callback(
14
15
  deployId,
15
16
  env = 'development',
16
- options = { now: false, single: false, msInterval: '', type: '' },
17
+ options = { now: false, single: false, msInterval: '', type: '', replicas: '', sync: false },
18
+ commanderOptions,
17
19
  auxRouter,
18
20
  ) {
19
21
  if (deployId === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`)) {
@@ -22,6 +24,7 @@ class UnderpostMonitor {
22
24
  _deployId.trim(),
23
25
  env,
24
26
  options,
27
+ commanderOptions,
25
28
  await UnderpostDeploy.API.routerFactory(_deployId, env),
26
29
  );
27
30
  return;
@@ -36,15 +39,43 @@ class UnderpostMonitor {
36
39
 
37
40
  const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
38
41
 
39
- logger.info(`${deployId} ${env}`, pathPortAssignmentData);
40
-
41
42
  let errorPayloads = [];
42
- let traffic = 'blue';
43
- const maxAttempts = Object.keys(pathPortAssignmentData)
44
- .map((host) => pathPortAssignmentData[host].length)
45
- .reduce((accumulator, value) => accumulator + value, 0);
43
+ if (options.sync === true) {
44
+ const currentTraffic = UnderpostDeploy.API.getCurrentTraffic(deployId);
45
+ if (currentTraffic) UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, currentTraffic);
46
+ }
47
+ let traffic = UnderpostRootEnv.API.get(`${deployId}-${env}-traffic`) ?? 'blue';
48
+ const maxAttempts = parseInt(
49
+ Object.keys(pathPortAssignmentData)
50
+ .map((host) => pathPortAssignmentData[host].length)
51
+ .reduce((accumulator, value) => accumulator + value, 0) * 2.5,
52
+ );
53
+
54
+ logger.info(`Init deploy monitor`, {
55
+ pathPortAssignmentData,
56
+ maxAttempts,
57
+ deployId,
58
+ env,
59
+ traffic,
60
+ });
61
+
62
+ const switchTraffic = () => {
63
+ if (traffic === 'blue') traffic = 'green';
64
+ else traffic = 'blue';
65
+ UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, traffic);
66
+ shellExec(
67
+ `node bin deploy --info-router --build-manifest --traffic ${traffic} --replicas ${
68
+ options.replicas ? options.replicas : 1
69
+ } ${deployId} ${env}`,
70
+ );
71
+ shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
72
+ };
46
73
 
47
74
  const monitor = async (reject) => {
75
+ if (UnderpostRootEnv.API.get(`monitor-init-callback-script`))
76
+ shellExec(UnderpostRootEnv.API.get(`monitor-init-callback-script`));
77
+ const currentTimestamp = new Date().getTime();
78
+ errorPayloads = errorPayloads.filter((e) => currentTimestamp - e.timestamp < 60 * 1000 * 5);
48
79
  logger.info(`[${deployId}-${env}] Check server health`);
49
80
  for (const host of Object.keys(pathPortAssignmentData)) {
50
81
  for (const instance of pathPortAssignmentData[host]) {
@@ -52,6 +83,7 @@ class UnderpostMonitor {
52
83
  if (path.match('peer') || path.match('socket')) continue;
53
84
  let urlTest = `http://localhost:${port}${path}`;
54
85
  switch (options.type) {
86
+ case 'remote':
55
87
  case 'blue-green':
56
88
  urlTest = `https://${host}${path}`;
57
89
  break;
@@ -71,6 +103,7 @@ class UnderpostMonitor {
71
103
  status: error.status,
72
104
  code: error.code,
73
105
  errors: error.errors,
106
+ timestamp: new Date().getTime(),
74
107
  };
75
108
  if (errorPayload.status !== 404) {
76
109
  errorPayloads.push(errorPayload);
@@ -87,30 +120,31 @@ class UnderpostMonitor {
87
120
  fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'),
88
121
  );
89
122
 
123
+ shellExec(`kubectl delete configmap underpost-config`);
124
+ shellExec(
125
+ `kubectl create configmap underpost-config --from-file=/home/dd/engine/engine-private/conf/dd-cron/.env.${env}`,
126
+ );
127
+
90
128
  for (const host of Object.keys(confServer)) {
91
129
  shellExec(`sudo kubectl delete HTTPProxy ${host}`);
92
130
  }
93
131
  shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${traffic}`);
94
132
 
95
- if (traffic === 'blue') traffic = 'green';
96
- else traffic = 'blue';
97
-
98
- shellExec(
99
- `node bin deploy --info-router --build-manifest --traffic ${traffic} ${deployId} ${env}`,
100
- );
101
-
102
- shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
103
- errorPayloads = [];
133
+ switchTraffic();
104
134
  }
105
135
 
106
136
  break;
107
137
 
138
+ case 'remote':
139
+ break;
140
+
108
141
  default:
109
142
  if (reject) reject(message);
110
143
  else throw new Error(message);
111
144
  }
145
+ errorPayloads = [];
112
146
  }
113
- logger.error('Error accumulator', errorPayloads.length);
147
+ logger.error(`Error accumulator ${deployId}-${env}-${traffic}`, errorPayloads.length);
114
148
  }
115
149
  });
116
150
  }
@@ -119,13 +153,19 @@ class UnderpostMonitor {
119
153
  if (options.now === true) await monitor();
120
154
  if (options.single === true) return;
121
155
  let optionsMsTimeout = parseInt(options.msInterval);
122
- if (isNaN(optionsMsTimeout)) optionsMsTimeout = 30000;
156
+ if (isNaN(optionsMsTimeout)) optionsMsTimeout = 60250; // 60.25 seconds
123
157
  let monitorTrafficName;
124
158
  let monitorPodName;
125
159
  const monitorCallBack = (resolve, reject) => {
126
160
  const envMsTimeout = UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-ms`);
127
161
  setTimeout(
128
162
  async () => {
163
+ const isOnline = await isInternetConnection();
164
+ if (!isOnline) {
165
+ logger.warn('No internet connection');
166
+ monitorCallBack(resolve, reject);
167
+ return;
168
+ }
129
169
  switch (options.type) {
130
170
  case 'blue-green':
131
171
  {
@@ -135,14 +175,17 @@ class UnderpostMonitor {
135
175
  }
136
176
  const cmd = `underpost config get container-status`;
137
177
  const checkDeploymentReadyStatus = () => {
138
- const [podName] = UnderpostDeploy.API.get(`${deployId}-${env}-${traffic}`);
139
- if (
140
- shellExec(`sudo kubectl exec -i ${podName} -- sh -c "${cmd}"`, { stdout: true }).match(
141
- `${deployId}-${env}-running-deployment`,
142
- )
143
- ) {
144
- monitorPodName = podName;
145
- monitorTrafficName = `${traffic}`;
178
+ const pods = UnderpostDeploy.API.get(`${deployId}-${env}-${traffic}`);
179
+ if (pods && pods[0]) {
180
+ const { NAME } = pods[0];
181
+ if (
182
+ shellExec(`sudo kubectl exec -i ${NAME} -- sh -c "${cmd}"`, { stdout: true }).match(
183
+ `${deployId}-${env}-running-deployment`,
184
+ )
185
+ ) {
186
+ monitorPodName = NAME;
187
+ monitorTrafficName = `${traffic}`;
188
+ }
146
189
  }
147
190
  };
148
191
  if (!monitorPodName) {
@@ -157,19 +200,30 @@ class UnderpostMonitor {
157
200
  default:
158
201
  break;
159
202
  }
160
- switch (UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-input`)) {
161
- case 'pause':
162
- monitorCallBack(resolve, reject);
163
- return;
164
- case 'restart':
165
- return reject();
166
- case 'stop':
167
- return resolve();
168
- default:
169
- await monitor(reject);
170
- monitorCallBack(resolve, reject);
171
- return;
172
- }
203
+ for (const monitorStatus of [
204
+ { key: `monitor-input`, value: UnderpostRootEnv.API.get(`monitor-input`) },
205
+ {
206
+ key: `${deployId}-${env}-monitor-input`,
207
+ value: UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-input`),
208
+ },
209
+ ])
210
+ switch (monitorStatus.value) {
211
+ case 'pause':
212
+ monitorCallBack(resolve, reject);
213
+ return;
214
+ case 'restart':
215
+ UnderpostRootEnv.API.delete(monitorStatus.key);
216
+ return reject();
217
+ case 'stop':
218
+ UnderpostRootEnv.API.delete(monitorStatus.key);
219
+ return resolve();
220
+ case 'blue-green-switch':
221
+ UnderpostRootEnv.API.delete(monitorStatus.key);
222
+ switchTraffic();
223
+ }
224
+ await monitor(reject);
225
+ monitorCallBack(resolve, reject);
226
+ return;
173
227
  },
174
228
  !isNaN(envMsTimeout) ? envMsTimeout : optionsMsTimeout,
175
229
  );
@@ -19,8 +19,8 @@ const JoyStick = {
19
19
  /* border: 2px solid red; */
20
20
  left: 5px;
21
21
  bottom: 5px;
22
- height: 200px;
23
- width: 200px;
22
+ height: 175px;
23
+ width: 175px;
24
24
  z-index: 3;
25
25
  }
26
26
  .joy-img-background-${id} {
@@ -1629,6 +1629,7 @@ const Modal = {
1629
1629
  currentTopModalId: '',
1630
1630
  zIndexSync: function ({ idModal }) {
1631
1631
  setTimeout(() => {
1632
+ if (!this.Data[idModal]) return;
1632
1633
  const cleanTopModal = () => {
1633
1634
  Object.keys(this.Data).map((_idModal) => {
1634
1635
  if (this.Data[_idModal].options.zIndexSync && s(`.${_idModal}`)) s(`.${_idModal}`).style.zIndex = '3';
package/src/index.js CHANGED
@@ -30,7 +30,7 @@ class Underpost {
30
30
  * @type {String}
31
31
  * @memberof Underpost
32
32
  */
33
- static version = 'v2.8.65';
33
+ static version = 'v2.8.67';
34
34
  /**
35
35
  * Repository cli API
36
36
  * @static
@@ -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) {
@@ -927,7 +927,9 @@ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
927
927
  const confServer = loadReplicas(
928
928
  JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
929
929
  );
930
+ const hosts = {};
930
931
  for (const host of Object.keys(confServer)) {
932
+ hosts[host] = {};
931
933
  for (const path of Object.keys(confServer[host])) {
932
934
  if (!confServer[host][path].db) continue;
933
935
  const { singleReplica, replicas, db } = confServer[host][path];
@@ -940,6 +942,7 @@ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
940
942
  );
941
943
  for (const _host of Object.keys(confServerReplica)) {
942
944
  for (const _path of Object.keys(confServerReplica[_host])) {
945
+ hosts[host][_path] = { replica: { host, path } };
943
946
  confServerReplica[_host][_path].valkey = valkey;
944
947
  switch (provider) {
945
948
  case 'mongoose':
@@ -954,7 +957,7 @@ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
954
957
  'utf8',
955
958
  );
956
959
  }
957
- }
960
+ } else hosts[host][path] = {};
958
961
  confServer[host][path].valkey = valkey;
959
962
  switch (provider) {
960
963
  case 'mongoose':
@@ -964,6 +967,7 @@ const rebuildConfFactory = ({ deployId, valkey, mongo }) => {
964
967
  }
965
968
  }
966
969
  fs.writeFileSync(`./engine-private/conf/${deployId}/conf.server.json`, JSON.stringify(confServer, null, 4), 'utf8');
970
+ return { hosts };
967
971
  };
968
972
 
969
973
  const getRestoreCronCmd = async (options = { host: '', path: '', conf: {}, deployId: '' }) => {
package/src/server/dns.js CHANGED
@@ -5,6 +5,9 @@ import validator from 'validator';
5
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
 
@@ -18,35 +21,59 @@ const ip = {
18
21
  },
19
22
  };
20
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
+
21
38
  class Dns {
22
39
  static callback = async function (deployList) {
23
40
  // Network topology configuration:
24
41
  // LAN -> [NAT-VPS](modem/router device) -> WAN
25
42
  // enabled DMZ Host to proxy IP 80-443 (79-444) sometimes router block first port
26
- // 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
+
27
49
  // verify inet ip proxy server address
28
50
  // DHCP (Dynamic Host Configuration Protocol) LAN reserver IP -> MAC ID
29
51
  // LAN server or device's local servers port -> 3000-3100 (2999-3101)
30
52
  // DNS Records: [ANAME](Address Dynamic) -> [A](ipv4) host | [AAAA](ipv6) host -> [public-ip]
31
53
  // Forward the router's TCP/UDP ports to the LAN device's IP address
32
- for (const _deployId of deployList.split(',')) {
33
- const deployId = _deployId.trim();
34
- const privateCronConfPath = `./engine-private/conf/${deployId}/conf.cron.json`;
35
- const confCronPath = fs.existsSync(privateCronConfPath) ? privateCronConfPath : './conf/conf.cron.json';
36
- const confCronData = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
37
-
38
- let testIp;
39
-
40
- try {
41
- testIp = await ip.public.ipv4();
42
- } catch (error) {
43
- logger.error(error, { testIp, stack: error.stack });
44
- }
54
+ const isOnline = await isInternetConnection();
55
+
56
+ if (!isOnline) return;
45
57
 
46
- const currentIp = UnderpostRootEnv.API.get('ip');
58
+ let testIp;
47
59
 
48
- if (testIp && typeof testIp === 'string' && validator.isIP(testIp) && currentIp !== testIp) {
49
- logger.info(`new ip`, testIp);
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'));
50
77
  for (const recordType of Object.keys(confCronData.records)) {
51
78
  switch (recordType) {
52
79
  case 'A':
@@ -68,6 +95,7 @@ class Dns {
68
95
  if (verifyIp === testIp) {
69
96
  logger.info('ip updated successfully', testIp);
70
97
  UnderpostRootEnv.API.set('ip', testIp);
98
+ UnderpostRootEnv.API.delete('monitor-input');
71
99
  } else logger.error('ip not updated', testIp);
72
100
  } catch (error) {
73
101
  logger.error(error, error.stack);
@@ -102,3 +130,5 @@ class Dns {
102
130
  }
103
131
 
104
132
  export default Dns;
133
+
134
+ export { Dns, ip, isInternetConnection, getLocalIPv4Address };
@@ -142,6 +142,8 @@ const buildRuntime = async () => {
142
142
  // if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
143
143
  // $_SERVER['HTTPS'] = 'on';
144
144
  // }
145
+ // For plugins:
146
+ // define( 'FS_METHOD', 'direct' );
145
147
 
146
148
  // ErrorDocument 404 /custom_404.html
147
149
  // ErrorDocument 500 /custom_50x.html
@@ -1,5 +1,4 @@
1
1
  import UnderpostDeploy from '../cli/deploy.js';
2
- import UnderpostMonitor from '../cli/monitor.js';
3
2
  import fs from 'fs-extra';
4
3
  import { awaitDeployMonitor } from './conf.js';
5
4
  import { actionInitLog, loggerFactory } from './logger.js';