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.
- package/.vscode/extensions.json +3 -2
- package/.vscode/settings.json +2 -0
- package/CHANGELOG.md +24 -4
- package/Dockerfile +9 -10
- package/README.md +39 -2
- package/bin/build.js +2 -2
- package/bin/deploy.js +1337 -131
- package/bin/file.js +8 -0
- package/bin/index.js +1 -218
- package/cli.md +451 -0
- package/docker-compose.yml +1 -1
- package/jsdoc.json +1 -1
- package/manifests/calico-custom-resources.yaml +25 -0
- package/manifests/deployment/adminer/deployment.yaml +32 -0
- package/manifests/deployment/adminer/kustomization.yaml +7 -0
- package/manifests/deployment/adminer/service.yaml +13 -0
- package/manifests/deployment/fastapi/backend-deployment.yml +120 -0
- package/manifests/deployment/fastapi/backend-service.yml +19 -0
- package/manifests/deployment/fastapi/frontend-deployment.yml +54 -0
- package/manifests/deployment/fastapi/frontend-service.yml +15 -0
- package/manifests/deployment/kafka/deployment.yaml +69 -0
- package/manifests/kubeadm-calico-config.yaml +119 -0
- package/manifests/mongodb-4.4/service-deployment.yaml +1 -1
- package/manifests/postgresql/configmap.yaml +9 -0
- package/manifests/postgresql/kustomization.yaml +10 -0
- package/manifests/postgresql/pv.yaml +15 -0
- package/manifests/postgresql/pvc.yaml +13 -0
- package/manifests/postgresql/service.yaml +10 -0
- package/manifests/postgresql/statefulset.yaml +37 -0
- package/manifests/valkey/statefulset.yaml +6 -4
- package/package.json +3 -9
- package/src/api/default/default.service.js +1 -1
- package/src/api/user/user.service.js +14 -11
- package/src/cli/cluster.js +207 -20
- package/src/cli/cron.js +39 -8
- package/src/cli/db.js +20 -10
- package/src/cli/deploy.js +254 -85
- package/src/cli/env.js +9 -3
- package/src/cli/fs.js +21 -9
- package/src/cli/image.js +42 -124
- package/src/cli/index.js +312 -0
- package/src/cli/monitor.js +236 -0
- package/src/cli/repository.js +5 -2
- package/src/client/components/core/Account.js +28 -24
- package/src/client/components/core/Blockchain.js +1 -1
- package/src/client/components/core/CalendarCore.js +14 -84
- package/src/client/components/core/CommonJs.js +2 -1
- package/src/client/components/core/Css.js +0 -1
- package/src/client/components/core/CssCore.js +10 -2
- package/src/client/components/core/Docs.js +1 -1
- package/src/client/components/core/EventsUI.js +3 -3
- package/src/client/components/core/FileExplorer.js +86 -78
- package/src/client/components/core/JoyStick.js +2 -2
- package/src/client/components/core/LoadingAnimation.js +1 -17
- package/src/client/components/core/LogIn.js +3 -3
- package/src/client/components/core/LogOut.js +1 -1
- package/src/client/components/core/Modal.js +14 -8
- package/src/client/components/core/Panel.js +19 -61
- package/src/client/components/core/PanelForm.js +13 -22
- package/src/client/components/core/Recover.js +3 -3
- package/src/client/components/core/RichText.js +1 -11
- package/src/client/components/core/Router.js +3 -1
- package/src/client/components/core/SignUp.js +2 -2
- package/src/client/components/default/RoutesDefault.js +3 -2
- package/src/client/services/default/default.management.js +45 -38
- package/src/client/ssr/Render.js +2 -0
- package/src/index.js +18 -2
- package/src/mailer/MailerProvider.js +3 -0
- package/src/runtime/lampp/Dockerfile +65 -0
- package/src/server/client-build.js +13 -0
- package/src/server/conf.js +93 -1
- package/src/server/dns.js +56 -18
- package/src/server/json-schema.js +77 -0
- package/src/server/network.js +7 -122
- package/src/server/peer.js +2 -2
- package/src/server/proxy.js +4 -4
- package/src/server/runtime.js +24 -11
- package/src/server/start.js +122 -0
- 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: () =>
|
|
14
|
+
render: () => Modal.onHomeRouterEvent(),
|
|
14
15
|
upperCase: false,
|
|
15
16
|
},
|
|
16
|
-
'/home': { title: 'home', render: () =>
|
|
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(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
<
|
|
228
|
-
|
|
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
|
-
|
|
232
|
-
|
|
238
|
+
{ context: 'modal' },
|
|
239
|
+
);
|
|
233
240
|
if (confirmResult.status === 'cancelled') return;
|
|
234
241
|
const result = await ServiceProvider.delete();
|
|
235
242
|
NotificationManager.Push({
|
package/src/client/ssr/Render.js
CHANGED
|
@@ -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.
|
|
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
|
|
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) {
|
package/src/server/conf.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
+
if (!isOnline) return;
|
|
39
57
|
|
|
40
|
-
|
|
41
|
-
|
|
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 };
|