underpost 2.7.83 → 2.7.92
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/.github/workflows/ghpkg.yml +41 -1
- package/.github/workflows/pwa-microservices-template.page.yml +54 -0
- package/.vscode/settings.json +7 -0
- package/CHANGELOG.md +64 -16
- package/bin/cron.js +47 -0
- package/bin/db.js +60 -7
- package/bin/deploy.js +358 -26
- package/bin/file.js +18 -1
- package/bin/hwt.js +59 -0
- package/bin/index.js +1 -1
- package/bin/util.js +31 -1
- package/conf.js +46 -8
- package/docker-compose.yml +1 -1
- package/package.json +133 -133
- package/src/api/core/core.router.js +9 -9
- package/src/api/core/core.service.js +12 -4
- package/src/api/default/default.service.js +4 -4
- package/src/api/file/file.service.js +3 -3
- package/src/api/user/user.service.js +10 -8
- package/src/client/components/core/404.js +20 -0
- package/src/client/components/core/500.js +20 -0
- package/src/client/{ssr/common → components/core}/Alert.js +13 -11
- package/src/client/components/core/CommonJs.js +3 -0
- package/src/client/components/core/CssCore.js +30 -3
- package/src/client/components/core/Docs.js +110 -10
- package/src/client/components/core/LoadingAnimation.js +4 -2
- package/src/client/components/core/Modal.js +223 -22
- package/src/client/components/core/Panel.js +1 -1
- package/src/client/components/core/PanelForm.js +2 -1
- package/src/client/components/core/Responsive.js +34 -5
- package/src/client/components/core/RichText.js +4 -2
- package/src/client/components/core/Translate.js +21 -5
- package/src/client/components/core/VanillaJs.js +2 -1
- package/src/client/components/core/WebComponent.js +44 -0
- package/src/client/components/core/Worker.js +15 -18
- package/src/client/components/default/MenuDefault.js +68 -0
- package/src/client/components/default/RoutesDefault.js +2 -0
- package/src/client/public/default/plantuml/client-conf.svg +1 -1
- package/src/client/public/default/plantuml/client-schema.svg +1 -1
- package/src/client/public/default/plantuml/cron-conf.svg +1 -1
- package/src/client/public/default/plantuml/cron-schema.svg +1 -1
- package/src/client/public/default/plantuml/server-conf.svg +1 -1
- package/src/client/public/default/plantuml/server-schema.svg +1 -1
- package/src/client/public/default/plantuml/ssr-conf.svg +1 -1
- package/src/client/public/default/plantuml/ssr-schema.svg +1 -1
- package/src/client/public/default/site.webmanifest +69 -0
- package/src/client/ssr/Render.js +1 -6
- package/src/client/ssr/{components/body → body}/CacheControl.js +1 -1
- package/src/client/ssr/head/Production.js +1 -0
- package/src/client/ssr/head/Pwa.js +146 -0
- package/src/client/ssr/head/Seo.js +14 -0
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +21 -0
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +17 -0
- package/src/client/ssr/offline/NoNetworkConnection.js +65 -0
- package/src/client/ssr/pages/Test.js +196 -0
- package/src/client/ssr/pages/maintenance.js +14 -0
- package/src/client/ssr/pages/offline.js +21 -0
- package/src/client/sw/default.sw.js +44 -165
- package/src/db/DataBaseProvider.js +12 -1
- package/src/db/mongo/MongooseDB.js +0 -1
- package/src/mailer/EmailRender.js +2 -4
- package/src/mailer/MailerProvider.js +4 -1
- package/src/runtime/lampp/Lampp.js +9 -9
- package/src/server/backup.js +82 -70
- package/src/server/client-build.js +133 -155
- package/src/server/client-formatted.js +2 -4
- package/src/server/conf.js +114 -23
- package/src/server/crypto.js +91 -0
- package/src/server/dns.js +48 -16
- package/src/server/network.js +94 -7
- package/src/server/proxy.js +26 -28
- package/src/server/runtime.js +42 -12
- package/src/server/ssl.js +2 -2
- package/src/client/ssr/common/SsrCore.js +0 -91
- package/src/client/ssr/common/Translate.js +0 -26
- package/src/client/ssr/common/Worker.js +0 -28
- package/src/client/ssr/components/head/PwaDefault.js +0 -60
- package/src/client/ssr/offline/default.index.js +0 -31
- package/src/cron.js +0 -30
- package/src/server/cron.js +0 -35
- /package/src/client/ssr/{components/body → body}/DefaultSplashScreen.js +0 -0
- /package/src/client/ssr/{components/email → email}/DefaultRecoverEmail.js +0 -0
- /package/src/client/ssr/{components/email → email}/DefaultVerifyEmail.js +0 -0
- /package/src/client/ssr/{components/head → head}/Css.js +0 -0
- /package/src/client/ssr/{components/head → head}/DefaultScripts.js +0 -0
package/src/server/conf.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
|
-
import {
|
|
3
|
+
import { capFirst, getCapVariableName, newInstance, range, timer } from '../client/components/core/CommonJs.js';
|
|
4
4
|
import * as dir from 'path';
|
|
5
5
|
import cliProgress from 'cli-progress';
|
|
6
6
|
import cliSpinners from 'cli-spinners';
|
|
7
7
|
import logUpdate from 'log-update';
|
|
8
8
|
import colors from 'colors';
|
|
9
9
|
import { loggerFactory } from './logger.js';
|
|
10
|
-
import { shellExec
|
|
10
|
+
import { shellExec } from './process.js';
|
|
11
11
|
import { DefaultConf } from '../../conf.js';
|
|
12
12
|
import ncp from 'copy-paste';
|
|
13
13
|
import read from 'read';
|
|
14
14
|
import splitFile from 'split-file';
|
|
15
15
|
import axios from 'axios';
|
|
16
16
|
import https from 'https';
|
|
17
|
+
import { ssrFactory } from './client-formatted.js';
|
|
17
18
|
|
|
18
19
|
// axios.defaults.baseURL = BASE_URL;
|
|
19
20
|
|
|
@@ -44,7 +45,9 @@ const Config = {
|
|
|
44
45
|
this.default.server = {};
|
|
45
46
|
for (const deployId of process.argv[3].split(',')) {
|
|
46
47
|
let confPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
47
|
-
const privateConfDevPath = `./engine-private/
|
|
48
|
+
const privateConfDevPath = fs.existsSync(`./engine-private/replica/${deployId}/conf.server.json`)
|
|
49
|
+
? `./engine-private/replica/${deployId}/conf.server.json`
|
|
50
|
+
: `./engine-private/conf/${deployId}/conf.server.dev.${process.argv[4]}.json`;
|
|
48
51
|
const confDevPath = fs.existsSync(privateConfDevPath)
|
|
49
52
|
? privateConfDevPath
|
|
50
53
|
: `./engine-private/conf/${deployId}/conf.server.dev.json`;
|
|
@@ -89,6 +92,11 @@ const loadConf = (deployId) => {
|
|
|
89
92
|
if (!fs.existsSync(`./conf`)) fs.mkdirSync(`./conf`);
|
|
90
93
|
if (!fs.existsSync(`./tmp`)) fs.mkdirSync(`./tmp`, { recursive: true });
|
|
91
94
|
const isValidDeployId = fs.existsSync(`${folder}`);
|
|
95
|
+
if (!isValidDeployId) {
|
|
96
|
+
logger.info(`Save new deploy conf: '${deployId}'`);
|
|
97
|
+
shellExec(`node bin/deploy save ${deployId}`);
|
|
98
|
+
return loadConf(deployId);
|
|
99
|
+
}
|
|
92
100
|
for (const typeConf of Object.keys(Config.default)) {
|
|
93
101
|
let srcConf = isValidDeployId
|
|
94
102
|
? fs.readFileSync(`${folder}/conf.${typeConf}.json`, 'utf8')
|
|
@@ -100,7 +108,6 @@ const loadConf = (deployId) => {
|
|
|
100
108
|
if (typeConf === 'server') srcConf = JSON.stringify(loadReplicas(JSON.parse(srcConf)), null, 4);
|
|
101
109
|
fs.writeFileSync(`./conf/conf.${typeConf}.json`, srcConf, 'utf8');
|
|
102
110
|
}
|
|
103
|
-
if (!isValidDeployId) return {};
|
|
104
111
|
fs.writeFileSync(`./.env.production`, fs.readFileSync(`${folder}/.env.production`, 'utf8'), 'utf8');
|
|
105
112
|
fs.writeFileSync(`./.env.development`, fs.readFileSync(`${folder}/.env.development`, 'utf8'), 'utf8');
|
|
106
113
|
fs.writeFileSync(`./.env.test`, fs.readFileSync(`${folder}/.env.test`, 'utf8'), 'utf8');
|
|
@@ -120,10 +127,16 @@ const loadReplicas = (confServer) => {
|
|
|
120
127
|
for (const host of Object.keys(confServer)) {
|
|
121
128
|
for (const path of Object.keys(confServer[host])) {
|
|
122
129
|
const { replicas, singleReplica } = confServer[host][path];
|
|
123
|
-
if (
|
|
130
|
+
if (
|
|
131
|
+
replicas &&
|
|
132
|
+
(process.argv[2] === 'proxy' ||
|
|
133
|
+
!singleReplica ||
|
|
134
|
+
(singleReplica && process.env.NODE_ENV === 'development' && !process.argv[3]))
|
|
135
|
+
)
|
|
124
136
|
for (const replicaPath of replicas) {
|
|
125
137
|
confServer[host][replicaPath] = newInstance(confServer[host][path]);
|
|
126
138
|
delete confServer[host][replicaPath].replicas;
|
|
139
|
+
delete confServer[host][replicaPath].singleReplica;
|
|
127
140
|
}
|
|
128
141
|
}
|
|
129
142
|
}
|
|
@@ -246,8 +259,8 @@ const buildClientSrc = async (
|
|
|
246
259
|
);
|
|
247
260
|
|
|
248
261
|
fs.writeFileSync(
|
|
249
|
-
`./src/client/${toClientVariableName}.js`,
|
|
250
|
-
formattedSrc(fs.readFileSync(`./src/client/${fromClientVariableName}.js`, 'utf8')),
|
|
262
|
+
`./src/client/${toClientVariableName}.index.js`,
|
|
263
|
+
formattedSrc(fs.readFileSync(`./src/client/${fromClientVariableName}.index.js`, 'utf8')),
|
|
251
264
|
'utf8',
|
|
252
265
|
);
|
|
253
266
|
|
|
@@ -454,6 +467,16 @@ const buildProxyRouter = () => {
|
|
|
454
467
|
}
|
|
455
468
|
}
|
|
456
469
|
}
|
|
470
|
+
if (process.argv.includes('maintenance'))
|
|
471
|
+
(async () => {
|
|
472
|
+
globalThis.defaultHtmlSrcMaintenance = (await ssrFactory())({
|
|
473
|
+
title: 'Site in maintenance',
|
|
474
|
+
ssrPath: '/',
|
|
475
|
+
ssrHeadComponents: '',
|
|
476
|
+
ssrBodyComponents: (await ssrFactory(`./src/client/ssr/body/Maintenance.js`))(),
|
|
477
|
+
});
|
|
478
|
+
})();
|
|
479
|
+
|
|
457
480
|
return proxyRouter;
|
|
458
481
|
};
|
|
459
482
|
|
|
@@ -584,14 +607,32 @@ const validateTemplatePath = (absolutePath = '') => {
|
|
|
584
607
|
return false;
|
|
585
608
|
}
|
|
586
609
|
if (
|
|
587
|
-
absolutePath.match('src/client/ssr/
|
|
588
|
-
!confSsr.body.find((p) => absolutePath.match(`src/client/ssr/
|
|
610
|
+
absolutePath.match('src/client/ssr/body') &&
|
|
611
|
+
!confSsr.body.find((p) => absolutePath.match(`src/client/ssr/body/${p}.js`))
|
|
612
|
+
) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
if (
|
|
616
|
+
absolutePath.match('src/client/ssr/head') &&
|
|
617
|
+
!confSsr.head.find((p) => absolutePath.match(`src/client/ssr/head/${p}.js`))
|
|
618
|
+
) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
if (
|
|
622
|
+
absolutePath.match('src/client/ssr/mailer') &&
|
|
623
|
+
!Object.keys(confSsr.mailer).find((p) => absolutePath.match(`src/client/ssr/mailer/${confSsr.mailer[p]}.js`))
|
|
624
|
+
) {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
if (
|
|
628
|
+
absolutePath.match('src/client/ssr/offline') &&
|
|
629
|
+
!confSsr.offline.find((p) => absolutePath.match(`src/client/ssr/offline/${p.client}.js`))
|
|
589
630
|
) {
|
|
590
631
|
return false;
|
|
591
632
|
}
|
|
592
633
|
if (
|
|
593
|
-
absolutePath.match('src/client/ssr/
|
|
594
|
-
!confSsr.
|
|
634
|
+
absolutePath.match('src/client/ssr/pages') &&
|
|
635
|
+
!confSsr.pages.find((p) => absolutePath.match(`src/client/ssr/pages/${p.client}.js`))
|
|
595
636
|
) {
|
|
596
637
|
return false;
|
|
597
638
|
}
|
|
@@ -624,7 +665,7 @@ const deployTest = async (dataDeploy) => {
|
|
|
624
665
|
if (singleReplica) continue;
|
|
625
666
|
const urlTest = `https://${host}${path}`;
|
|
626
667
|
try {
|
|
627
|
-
const result = await axios.get(urlTest);
|
|
668
|
+
const result = await axios.get(urlTest, { timeout: 10000 });
|
|
628
669
|
const test = result.data.split('<title>');
|
|
629
670
|
if (test[1])
|
|
630
671
|
logger.info('Success deploy', {
|
|
@@ -660,19 +701,29 @@ const getDeployGroupId = () => {
|
|
|
660
701
|
return 'dd';
|
|
661
702
|
};
|
|
662
703
|
|
|
704
|
+
const getDeployId = () => {
|
|
705
|
+
const deployIndexArg = process.argv.findIndex((a) => a.match(`deploy-id:`));
|
|
706
|
+
if (deployIndexArg > -1) return process.argv[deployIndexArg].split(':')[1].trim();
|
|
707
|
+
for (const deployId of process.argv) {
|
|
708
|
+
if (fs.existsSync(`./engine-private/conf/${deployId}`)) return deployId;
|
|
709
|
+
else if (fs.existsSync(`./engine-private/replica/${deployId}`)) return deployId;
|
|
710
|
+
}
|
|
711
|
+
return 'default';
|
|
712
|
+
};
|
|
713
|
+
|
|
663
714
|
const getCronBackUpFolder = (host = '', path = '') => {
|
|
664
715
|
return `${host}${path.replace(/\\/g, '/').replace(`/`, '-')}`;
|
|
665
716
|
};
|
|
666
717
|
|
|
667
|
-
const execDeploy = async (options = { deployId: 'default' }) => {
|
|
718
|
+
const execDeploy = async (options = { deployId: 'default' }, currentAttempt = 1) => {
|
|
668
719
|
const { deployId } = options;
|
|
669
720
|
shellExec(Cmd.delete(deployId));
|
|
670
721
|
shellExec(Cmd.conf(deployId));
|
|
671
722
|
shellExec(Cmd.run(deployId));
|
|
723
|
+
const maxTime = 1000 * 60;
|
|
724
|
+
const minTime = 20 * 1000;
|
|
725
|
+
const intervalTime = 1000;
|
|
672
726
|
return await new Promise(async (resolve) => {
|
|
673
|
-
const maxTime = 1000 * 60 * 5;
|
|
674
|
-
const minTime = 10000 * 2;
|
|
675
|
-
const intervalTime = 1000;
|
|
676
727
|
let currentTime = 0;
|
|
677
728
|
const attempt = () => {
|
|
678
729
|
if (currentTime >= minTime && !fs.existsSync(`./tmp/await-deploy`)) {
|
|
@@ -682,27 +733,40 @@ const execDeploy = async (options = { deployId: 'default' }) => {
|
|
|
682
733
|
cliSpinner(
|
|
683
734
|
intervalTime,
|
|
684
735
|
`[deploy.js] `,
|
|
685
|
-
` Load instance | elapsed time ${currentTime / 1000}s / ${maxTime / 1000}s`,
|
|
736
|
+
` Load instance | attempt:${currentAttempt} | elapsed time ${currentTime / 1000}s / ${maxTime / 1000}s`,
|
|
686
737
|
'yellow',
|
|
687
738
|
'material',
|
|
688
739
|
);
|
|
689
740
|
currentTime += intervalTime;
|
|
690
|
-
if (currentTime >= maxTime)
|
|
741
|
+
if (currentTime >= maxTime) {
|
|
742
|
+
clearInterval(processMonitor);
|
|
743
|
+
return resolve(false);
|
|
744
|
+
}
|
|
691
745
|
};
|
|
692
746
|
const processMonitor = setInterval(attempt, intervalTime);
|
|
693
747
|
});
|
|
694
748
|
};
|
|
695
749
|
|
|
696
|
-
const deployRun = async (dataDeploy,
|
|
750
|
+
const deployRun = async (dataDeploy, currentAttempt = 1) => {
|
|
697
751
|
if (!fs.existsSync(`./tmp`)) fs.mkdirSync(`./tmp`, { recursive: true });
|
|
698
|
-
if (reset) fs.writeFileSync(`./tmp/runtime-router.json`, '{}', 'utf8');
|
|
699
752
|
await fixDependencies();
|
|
700
|
-
|
|
753
|
+
const maxAttempts = 3;
|
|
754
|
+
for (const deploy of dataDeploy) {
|
|
755
|
+
let currentAttempt = 1;
|
|
756
|
+
const attempt = async () => {
|
|
757
|
+
const success = await execDeploy(deploy, currentAttempt);
|
|
758
|
+
currentAttempt++;
|
|
759
|
+
if (!success && currentAttempt <= maxAttempts) await attempt();
|
|
760
|
+
};
|
|
761
|
+
await attempt();
|
|
762
|
+
}
|
|
701
763
|
const { failed } = await deployTest(dataDeploy);
|
|
702
764
|
if (failed.length > 0) {
|
|
703
765
|
for (const deploy of failed) logger.error(deploy.deployId, Cmd.run(deploy.deployId));
|
|
704
|
-
|
|
705
|
-
await
|
|
766
|
+
if (currentAttempt === maxAttempts) return logger.error(`max deploy attempts exceeded`);
|
|
767
|
+
if (process.argv.includes('manual')) await read({ prompt: 'Press enter to retry failed processes\n' });
|
|
768
|
+
currentAttempt++;
|
|
769
|
+
await deployRun(failed, currentAttempt);
|
|
706
770
|
} else logger.info(`Deploy process successfully`);
|
|
707
771
|
};
|
|
708
772
|
|
|
@@ -833,6 +897,10 @@ const Cmd = {
|
|
|
833
897
|
conf: (deployId, env) => `node bin/deploy conf ${deployId} ${env ? env : 'production'}`,
|
|
834
898
|
replica: (deployId, host, path) => `node bin/deploy build-single-replica ${deployId} ${host} ${path}`,
|
|
835
899
|
syncPorts: (deployGroupId) => `node bin/deploy sync-env-port ${deployGroupId}`,
|
|
900
|
+
cron: (deployId, job, expression) => {
|
|
901
|
+
shellExec(Cmd.delete(`${deployId}-${job}`));
|
|
902
|
+
return `env-cmd -f .env.production pm2 start bin/cron.js --no-autorestart --instances 1 --cron "${expression}" --name ${deployId}-${job} -- ${job} ${deployId}`;
|
|
903
|
+
},
|
|
836
904
|
};
|
|
837
905
|
|
|
838
906
|
const fixDependencies = async () => {
|
|
@@ -852,6 +920,26 @@ const fixDependencies = async () => {
|
|
|
852
920
|
);
|
|
853
921
|
};
|
|
854
922
|
|
|
923
|
+
const maintenanceMiddleware = (req, res, port, proxyRouter) => {
|
|
924
|
+
if (process.argv.includes('maintenance') && globalThis.defaultHtmlSrcMaintenance) {
|
|
925
|
+
if (req.method.toUpperCase() === 'GET') {
|
|
926
|
+
res.set('Content-Type', 'text/html');
|
|
927
|
+
return res.status(503).send(globalThis.defaultHtmlSrcMaintenance);
|
|
928
|
+
}
|
|
929
|
+
return res.status(503).json({
|
|
930
|
+
status: 'error',
|
|
931
|
+
message: 'Server is under maintenance',
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
const setUpProxyMaintenanceServer = ({ deployGroupId }) => {
|
|
937
|
+
shellExec(`pm2 kill`);
|
|
938
|
+
const proxyDeployId = fs.readFileSync(`./engine-private/deploy/${deployGroupId}.proxy`, 'utf8').trim();
|
|
939
|
+
shellExec(`node bin/deploy conf ${proxyDeployId} production`);
|
|
940
|
+
shellExec(`node bin/deploy run ${proxyDeployId} maintenance`);
|
|
941
|
+
};
|
|
942
|
+
|
|
855
943
|
export {
|
|
856
944
|
Cmd,
|
|
857
945
|
Config,
|
|
@@ -880,4 +968,7 @@ export {
|
|
|
880
968
|
getRestoreCronCmd,
|
|
881
969
|
mergeBackUp,
|
|
882
970
|
fixDependencies,
|
|
971
|
+
getDeployId,
|
|
972
|
+
maintenanceMiddleware,
|
|
973
|
+
setUpProxyMaintenanceServer,
|
|
883
974
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
|
|
4
|
+
const CryptoBuilder = {
|
|
5
|
+
symmetric: {
|
|
6
|
+
instance: function (options = { iv: '', encryptionKey: '' }) {
|
|
7
|
+
// Generate a random 32-byte encryption key
|
|
8
|
+
const encryptionKey = option?.encryptionKey ? options.encryptionKey : crypto.randomBytes(32);
|
|
9
|
+
const iv = option?.iv ? options.iv : crypto.randomBytes(16); // Generate a new Initialization Vector (IV) for each encryption
|
|
10
|
+
|
|
11
|
+
// Function to encrypt data
|
|
12
|
+
function encryptData(plaintext = '') {
|
|
13
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', encryptionKey, iv);
|
|
14
|
+
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
|
|
15
|
+
encrypted += cipher.final('hex');
|
|
16
|
+
return `${iv.toString('hex')}:${encrypted}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Function to decrypt data
|
|
20
|
+
function decryptData(ciphertext = '') {
|
|
21
|
+
const [ivHex, encrypted] = ciphertext.split(':');
|
|
22
|
+
const _iv = Buffer.from(ivHex, 'hex');
|
|
23
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', encryptionKey, _iv);
|
|
24
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
25
|
+
decrypted += decipher.final('utf8');
|
|
26
|
+
return decrypted;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
encryptionKey,
|
|
31
|
+
iv,
|
|
32
|
+
encryptData,
|
|
33
|
+
decryptData,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
asymmetric: {
|
|
38
|
+
instance: function (
|
|
39
|
+
options = {
|
|
40
|
+
publicKey: '', // fs.readFileSync('./key.pem', 'utf8')
|
|
41
|
+
privateKey: '', // fs.readFileSync('./key.pem', 'utf8')
|
|
42
|
+
},
|
|
43
|
+
) {
|
|
44
|
+
// Generate a new key pair
|
|
45
|
+
const { privateKey, publicKey } = options
|
|
46
|
+
? options
|
|
47
|
+
: crypto.generateKeyPairSync('rsa', {
|
|
48
|
+
modulusLength: 2048, // Key size in bits
|
|
49
|
+
publicKeyEncoding: {
|
|
50
|
+
type: 'spki',
|
|
51
|
+
format: 'pem',
|
|
52
|
+
},
|
|
53
|
+
privateKeyEncoding: {
|
|
54
|
+
type: 'pkcs8',
|
|
55
|
+
format: 'pem',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Function to encrypt data
|
|
60
|
+
function encryptData(plaintext) {
|
|
61
|
+
const buffer = Buffer.from(plaintext, 'utf8');
|
|
62
|
+
const encrypted = crypto.publicEncrypt(publicKey, buffer);
|
|
63
|
+
return encrypted.toString('hex');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Function to decrypt data
|
|
67
|
+
function decryptData(ciphertext) {
|
|
68
|
+
const buffer = Buffer.from(ciphertext, 'hex');
|
|
69
|
+
const decrypted = crypto.privateDecrypt(privateKey, buffer);
|
|
70
|
+
return decrypted.toString('utf8');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fs.writeFileSync('./public.pem', publicKey);
|
|
74
|
+
fs.writeFileSync('./private.pem', privateKey);
|
|
75
|
+
|
|
76
|
+
const result = {
|
|
77
|
+
privateKey: fs.readFileSync('./public.pem', 'utf8'),
|
|
78
|
+
publicKey: fs.readFileSync('./private.pem', 'utf8'),
|
|
79
|
+
encryptData,
|
|
80
|
+
decryptData,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
fs.removeSync('./public.pem');
|
|
84
|
+
fs.removeSync('./private.pem');
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export { CryptoBuilder };
|
package/src/server/dns.js
CHANGED
|
@@ -1,38 +1,47 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
-
import
|
|
4
|
+
import https from 'https';
|
|
5
5
|
|
|
6
6
|
import { ip } from './network.js';
|
|
7
7
|
import { loggerFactory } from './logger.js';
|
|
8
8
|
import { isIPv4 } from 'is-ip';
|
|
9
|
+
import { shellExec } from './process.js';
|
|
10
|
+
|
|
11
|
+
const httpsAgent = new https.Agent({
|
|
12
|
+
rejectUnauthorized: false,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
axios.defaults.httpsAgent = httpsAgent;
|
|
9
16
|
|
|
10
17
|
dotenv.config();
|
|
11
18
|
|
|
12
19
|
const logger = loggerFactory(import.meta);
|
|
13
20
|
|
|
14
21
|
const Dns = {
|
|
15
|
-
|
|
16
|
-
ipDaemon: null,
|
|
22
|
+
repoUrl: `https://${process.env.GITHUB_TOKEN}@github.com/${process.env.GITHUB_USERNAME}/${process.env.GITHUB_DNS_REPO}.git`,
|
|
17
23
|
callback: () => null,
|
|
18
|
-
InitIpDaemon: async function () {
|
|
19
|
-
//
|
|
24
|
+
InitIpDaemon: async function ({ deployId }) {
|
|
25
|
+
// NAT-VPS modem/router device configuration:
|
|
26
|
+
// LAN --> [NAT-VPS] --> WAN
|
|
20
27
|
// enabled DMZ Host to proxy IP 80-443 (79-444) sometimes router block first port
|
|
21
|
-
//
|
|
22
|
-
//
|
|
28
|
+
// disabled local red DHCP
|
|
29
|
+
// verify inet ip proxy server address
|
|
23
30
|
// DHCP (Dynamic Host Configuration Protocol) LAN reserver IP -> MAC ID
|
|
31
|
+
// LAN server or device's local servers port -> 3000-3100 (2999-3101)
|
|
32
|
+
// DNS Records: [ANAME](Address Dynamic) -> [A](ipv4) host | [AAAA](ipv6) host -> [public-ip]
|
|
24
33
|
// Forward the router's TCP/UDP ports to the LAN device's IP address
|
|
25
34
|
|
|
26
|
-
const privateCronConfPath = `./engine-private/conf/${
|
|
35
|
+
const privateCronConfPath = `./engine-private/conf/${deployId}/conf.cron.json`;
|
|
27
36
|
|
|
28
37
|
const confCronPath = fs.existsSync(privateCronConfPath) ? privateCronConfPath : './conf/conf.cron.json';
|
|
29
|
-
|
|
30
38
|
let confCronData = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
|
|
31
39
|
if (confCronData.ipDaemon.disabled) return;
|
|
32
40
|
Dns.ip = confCronData.ipDaemon.ip;
|
|
33
41
|
logger.info(`Current ip`, Dns.ip);
|
|
34
|
-
if (Dns.ipDaemon) clearInterval(Dns.ipDaemon);
|
|
35
42
|
const callback = async () => {
|
|
43
|
+
logger.info('init dns ip callback');
|
|
44
|
+
await logger.setUpInfo();
|
|
36
45
|
let testIp;
|
|
37
46
|
try {
|
|
38
47
|
testIp = await ip.public.ipv4();
|
|
@@ -41,15 +50,12 @@ const Dns = {
|
|
|
41
50
|
}
|
|
42
51
|
if (testIp && typeof testIp === 'string' && isIPv4(testIp) && Dns.ip !== testIp) {
|
|
43
52
|
logger.info(`New ip`, testIp);
|
|
44
|
-
Dns.ip = testIp;
|
|
45
|
-
confCronData.ipDaemon.ip = Dns.ip;
|
|
46
|
-
fs.writeFileSync(confCronPath, JSON.stringify(confCronData, null, 4), 'utf8');
|
|
47
53
|
for (const recordType of Object.keys(confCronData.records)) {
|
|
48
54
|
switch (recordType) {
|
|
49
55
|
case 'A':
|
|
50
56
|
for (const dnsProvider of confCronData.records[recordType]) {
|
|
51
57
|
if (typeof Dns.services.updateIp[dnsProvider.dns] === 'function')
|
|
52
|
-
await Dns.services.updateIp[dnsProvider.dns](dnsProvider);
|
|
58
|
+
await Dns.services.updateIp[dnsProvider.dns]({ ...dnsProvider, ip: testIp });
|
|
53
59
|
}
|
|
54
60
|
break;
|
|
55
61
|
|
|
@@ -57,16 +63,28 @@ const Dns = {
|
|
|
57
63
|
break;
|
|
58
64
|
}
|
|
59
65
|
}
|
|
66
|
+
try {
|
|
67
|
+
const ipUrlTest = `https://${process.env.DEFAULT_DEPLOY_HOST}`;
|
|
68
|
+
const response = await axios.get(ipUrlTest);
|
|
69
|
+
const verifyIp = response.request.socket.remoteAddress;
|
|
70
|
+
logger.info(ipUrlTest + ' IP', verifyIp);
|
|
71
|
+
if (verifyIp === testIp) {
|
|
72
|
+
await this.saveIp(confCronPath, confCronData, testIp);
|
|
73
|
+
} else logger.error('ip not updated');
|
|
74
|
+
} catch (error) {
|
|
75
|
+
logger.error(error), 'ip not updated';
|
|
76
|
+
}
|
|
60
77
|
}
|
|
61
78
|
};
|
|
79
|
+
await callback();
|
|
62
80
|
this.callback = callback;
|
|
63
81
|
return callback;
|
|
64
82
|
},
|
|
65
83
|
services: {
|
|
66
84
|
updateIp: {
|
|
67
85
|
dondominio: (options) => {
|
|
68
|
-
const { user, api_key, host, dns } = options;
|
|
69
|
-
const url = `https://dondns.dondominio.com/json/?user=${user}&password=${api_key}&host=${host}&ip=${
|
|
86
|
+
const { user, api_key, host, dns, ip } = options;
|
|
87
|
+
const url = `https://dondns.dondominio.com/json/?user=${user}&password=${api_key}&host=${host}&ip=${ip}`;
|
|
70
88
|
logger.info(`${dns} update ip url`, url);
|
|
71
89
|
if (process.env.NODE_ENV !== 'production') return false;
|
|
72
90
|
return new Promise((resolve) => {
|
|
@@ -84,6 +102,20 @@ const Dns = {
|
|
|
84
102
|
},
|
|
85
103
|
},
|
|
86
104
|
},
|
|
105
|
+
saveIp: async (confCronPath, confCronData, ip) => {
|
|
106
|
+
Dns.ip = ip;
|
|
107
|
+
confCronData.ipDaemon.ip = ip;
|
|
108
|
+
fs.writeFileSync(confCronPath, JSON.stringify(confCronData, null, 4), 'utf8');
|
|
109
|
+
shellExec(
|
|
110
|
+
`cd ./engine-private` +
|
|
111
|
+
` && git pull ${Dns.repoUrl}` +
|
|
112
|
+
` && git add . && git commit -m "update ip ${new Date().toLocaleDateString()}"` +
|
|
113
|
+
` && git push ${Dns.repoUrl}`,
|
|
114
|
+
{
|
|
115
|
+
disableLog: true,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
},
|
|
87
119
|
};
|
|
88
120
|
|
|
89
121
|
export { Dns };
|
package/src/server/network.js
CHANGED
|
@@ -5,6 +5,8 @@ import { publicIp, publicIpv4, publicIpv6 } from 'public-ip';
|
|
|
5
5
|
import { killPortProcess } from 'kill-port-process';
|
|
6
6
|
import { loggerFactory } from './logger.js';
|
|
7
7
|
import { orderArrayFromAttrInt } from '../client/components/core/CommonJs.js';
|
|
8
|
+
import { DataBaseProvider } from '../db/DataBaseProvider.js';
|
|
9
|
+
import { getDeployId } from './conf.js';
|
|
8
10
|
|
|
9
11
|
// Network Address Translation Management
|
|
10
12
|
|
|
@@ -66,12 +68,86 @@ const logRuntimeRouter = () => {
|
|
|
66
68
|
logger.info('Runtime network', displayLog);
|
|
67
69
|
};
|
|
68
70
|
|
|
69
|
-
const saveRuntimeRouter = () =>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const saveRuntimeRouter = async () => {
|
|
72
|
+
try {
|
|
73
|
+
const deployId = process.env.DEFAULT_DEPLOY_ID;
|
|
74
|
+
const host = process.env.DEFAULT_DEPLOY_HOST;
|
|
75
|
+
const path = process.env.DEFAULT_DEPLOY_PATH;
|
|
76
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
77
|
+
const confServer = JSON.parse(fs.readFileSync(confServerPath, 'utf8'));
|
|
78
|
+
const { db } = confServer[host][path];
|
|
79
|
+
|
|
80
|
+
let closeConn;
|
|
81
|
+
if (!DataBaseProvider.instance[`${host}${path}`]) {
|
|
82
|
+
await DataBaseProvider.load({ apis: ['instance'], host, path, db });
|
|
83
|
+
closeConn = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @type {import('../api/instance/instance.model.js').InstanceModel} */
|
|
87
|
+
const Instance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Instance;
|
|
88
|
+
|
|
89
|
+
for (const _host of Object.keys(networkRouter)) {
|
|
90
|
+
for (const _path of Object.keys(networkRouter[_host])) {
|
|
91
|
+
const body = {
|
|
92
|
+
host: _host,
|
|
93
|
+
path: _path,
|
|
94
|
+
deployId: getDeployId(),
|
|
95
|
+
client: networkRouter[_host][_path].client,
|
|
96
|
+
runtime: networkRouter[_host][_path].runtime,
|
|
97
|
+
port: networkRouter[_host][_path].port,
|
|
98
|
+
apis: networkRouter[_host][_path].apis,
|
|
99
|
+
};
|
|
100
|
+
const instance = await Instance.findOne({ deployId: body.deployId, port: body.port });
|
|
101
|
+
if (instance) {
|
|
102
|
+
await Instance.findByIdAndUpdate(instance._id, body);
|
|
103
|
+
} else {
|
|
104
|
+
await new Instance(body).save();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (closeConn) await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
logger.error(error);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const netWorkCron = [];
|
|
116
|
+
|
|
117
|
+
const saveRuntimeCron = async () => {
|
|
118
|
+
try {
|
|
119
|
+
const deployId = process.env.DEFAULT_DEPLOY_ID;
|
|
120
|
+
const host = process.env.DEFAULT_DEPLOY_HOST;
|
|
121
|
+
const path = process.env.DEFAULT_DEPLOY_PATH;
|
|
122
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
123
|
+
const confServer = JSON.parse(fs.readFileSync(confServerPath, 'utf8'));
|
|
124
|
+
const { db } = confServer[host][path];
|
|
125
|
+
|
|
126
|
+
let closeConn;
|
|
127
|
+
if (!DataBaseProvider.instance[`${host}${path}`]) {
|
|
128
|
+
await DataBaseProvider.load({ apis: ['cron'], host, path, db });
|
|
129
|
+
closeConn = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @type {import('../api/cron/cron.model.js').CronModel} */
|
|
133
|
+
const Cron = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Cron;
|
|
134
|
+
|
|
135
|
+
// await Cron.insertMany(netWorkCron);
|
|
136
|
+
|
|
137
|
+
for (const cronInstance of netWorkCron) {
|
|
138
|
+
const cron = await Cron.findOne({ deployId: cronInstance.deployId, jobId: cronInstance.jobId });
|
|
139
|
+
if (cron) {
|
|
140
|
+
await Cron.findByIdAndUpdate(cron._id, cronInstance);
|
|
141
|
+
} else {
|
|
142
|
+
await new Cron(cronInstance).save();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (closeConn) await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.error(error);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
75
151
|
|
|
76
152
|
const listenServerFactory = (logic = async () => {}) => {
|
|
77
153
|
return {
|
|
@@ -109,6 +185,7 @@ const listenPortController = async (server, port, metadata) =>
|
|
|
109
185
|
? `https://${host}${path}`
|
|
110
186
|
: `http://${host}:${port}${path}`,
|
|
111
187
|
local: `http://localhost:${port}${path}`,
|
|
188
|
+
apis: metadata.apis,
|
|
112
189
|
};
|
|
113
190
|
|
|
114
191
|
return resolve(true);
|
|
@@ -119,4 +196,14 @@ const listenPortController = async (server, port, metadata) =>
|
|
|
119
196
|
}
|
|
120
197
|
});
|
|
121
198
|
|
|
122
|
-
export {
|
|
199
|
+
export {
|
|
200
|
+
ip,
|
|
201
|
+
network,
|
|
202
|
+
listenPortController,
|
|
203
|
+
networkRouter,
|
|
204
|
+
netWorkCron,
|
|
205
|
+
saveRuntimeRouter,
|
|
206
|
+
logRuntimeRouter,
|
|
207
|
+
listenServerFactory,
|
|
208
|
+
saveRuntimeCron,
|
|
209
|
+
};
|