underpost 2.8.85 → 2.8.87

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 (70) hide show
  1. package/.env.development +7 -2
  2. package/.env.production +7 -2
  3. package/.env.test +7 -2
  4. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
  5. package/.github/workflows/release.cd.yml +37 -0
  6. package/README.md +7 -24
  7. package/bin/build.js +1 -0
  8. package/bin/db.js +1 -3
  9. package/bin/deploy.js +43 -368
  10. package/bin/file.js +16 -3
  11. package/bin/util.js +1 -56
  12. package/cli.md +46 -21
  13. package/conf.js +3 -3
  14. package/manifests/deployment/{dd-template-development → dd-default-development}/deployment.yaml +16 -16
  15. package/manifests/deployment/{dd-template-development → dd-default-development}/proxy.yaml +3 -3
  16. package/manifests/deployment/mongo-express/deployment.yaml +12 -12
  17. package/manifests/grafana/deployment.yaml +57 -0
  18. package/manifests/grafana/kustomization.yaml +7 -0
  19. package/manifests/grafana/pvc.yaml +12 -0
  20. package/manifests/grafana/service.yaml +14 -0
  21. package/manifests/maas/nvim.sh +91 -0
  22. package/manifests/maas/ssh-cluster-info.sh +14 -0
  23. package/manifests/prometheus/deployment.yaml +82 -0
  24. package/package.json +3 -12
  25. package/src/api/file/file.service.js +28 -8
  26. package/src/api/user/user.router.js +31 -5
  27. package/src/api/user/user.service.js +11 -38
  28. package/src/cli/cluster.js +45 -25
  29. package/src/cli/cron.js +12 -45
  30. package/src/cli/db.js +149 -19
  31. package/src/cli/deploy.js +41 -110
  32. package/src/cli/fs.js +1 -0
  33. package/src/cli/index.js +24 -7
  34. package/src/cli/monitor.js +1 -4
  35. package/src/cli/repository.js +15 -6
  36. package/src/cli/run.js +94 -16
  37. package/src/client/Default.index.js +0 -2
  38. package/src/client/components/core/Account.js +6 -2
  39. package/src/client/components/core/Content.js +11 -7
  40. package/src/client/components/core/Css.js +5 -1
  41. package/src/client/components/core/CssCore.js +12 -0
  42. package/src/client/components/core/FullScreen.js +19 -28
  43. package/src/client/components/core/Input.js +7 -1
  44. package/src/client/components/core/LogIn.js +3 -0
  45. package/src/client/components/core/LogOut.js +1 -1
  46. package/src/client/components/core/Modal.js +32 -43
  47. package/src/client/components/core/ObjectLayerEngine.js +229 -4
  48. package/src/client/components/core/ObjectLayerEngineModal.js +441 -0
  49. package/src/client/components/core/Recover.js +5 -2
  50. package/src/client/components/core/Scroll.js +65 -120
  51. package/src/client/components/core/SignUp.js +1 -0
  52. package/src/client/components/core/ToggleSwitch.js +15 -1
  53. package/src/client/components/core/VanillaJs.js +48 -2
  54. package/src/client/components/default/MenuDefault.js +2 -2
  55. package/src/client/components/default/RoutesDefault.js +3 -3
  56. package/src/client/public/default/assets/mailer/api-user-default-avatar.png +0 -0
  57. package/src/index.js +1 -1
  58. package/src/mailer/MailerProvider.js +37 -0
  59. package/src/server/client-build-docs.js +1 -1
  60. package/src/server/client-build-live.js +1 -1
  61. package/src/server/client-build.js +4 -12
  62. package/src/server/client-dev-server.js +1 -1
  63. package/src/server/client-icons.js +6 -78
  64. package/src/server/conf.js +83 -408
  65. package/src/server/proxy.js +2 -3
  66. package/src/server/runtime.js +1 -2
  67. package/src/server/start.js +5 -5
  68. package/test/api.test.js +3 -2
  69. package/docker-compose.yml +0 -67
  70. package/prometheus.yml +0 -36
@@ -5,14 +5,18 @@ import crypto from 'crypto';
5
5
  const logger = loggerFactory(import.meta);
6
6
 
7
7
  const FileFactory = {
8
- upload: async function (req, File) {
9
- const results = [];
10
- if (!req.files) throw { message: 'not file found' };
11
- if (Array.isArray(req.files.file)) for (const file of req.files.file) results.push(await new File(file).save());
8
+ filesExtract: (req) => {
9
+ const files = [];
10
+ if (Array.isArray(req.files.file)) for (const file of req.files.file) files.push(file);
12
11
  else if (Object.keys(req.files).length > 0)
13
- for (const keyFile of Object.keys(req.files)) results.push(await new File(req.files[keyFile]).save());
12
+ for (const keyFile of Object.keys(req.files)) files.push(req.files[keyFile]);
13
+ return files;
14
+ },
15
+ upload: async function (req, File) {
16
+ const results = FileFactory.filesExtract(req);
14
17
  let index = -1;
15
- for (const file of results) {
18
+ for (let file of results) {
19
+ file = await new File(file).save();
16
20
  index++;
17
21
  const [result] = await File.find({
18
22
  _id: file._id,
@@ -25,7 +29,23 @@ const FileFactory = {
25
29
  return Buffer.from(raw, 'utf8').toString('hex');
26
30
  // reverse hexValue.toString()
27
31
  },
28
- svg: (data = new Buffer(), name = '') => {
32
+ getMymeTypeFromPath: (path) => {
33
+ switch (path.split('.').pop()) {
34
+ case 'png':
35
+ return 'image/png';
36
+ case 'jpg':
37
+ return 'image/jpeg';
38
+ case 'jpeg':
39
+ return 'image/jpeg';
40
+ case 'gif':
41
+ return 'image/gif';
42
+ case 'svg':
43
+ return 'image/svg+xml';
44
+ default:
45
+ return 'application/octet-stream';
46
+ }
47
+ },
48
+ create: (data = Buffer.from([]), name = '') => {
29
49
  return {
30
50
  name: name,
31
51
  data: data,
@@ -33,7 +53,7 @@ const FileFactory = {
33
53
  encoding: '7bit',
34
54
  tempFilePath: '',
35
55
  truncated: false,
36
- mimetype: 'image/svg+xml',
56
+ mimetype: FileFactory.getMymeTypeFromPath(name),
37
57
  md5: crypto.createHash('md5').update(data).digest('hex'),
38
58
  cid: undefined,
39
59
  };
@@ -4,6 +4,7 @@ import { loggerFactory } from '../../server/logger.js';
4
4
  import { UserController } from './user.controller.js';
5
5
  import express from 'express';
6
6
  import { DataBaseProvider } from '../../db/DataBaseProvider.js';
7
+ import { FileFactory } from '../file/file.service.js';
7
8
 
8
9
  const logger = loggerFactory(import.meta);
9
10
 
@@ -11,9 +12,10 @@ const UserRouter = (options) => {
11
12
  const router = express.Router();
12
13
 
13
14
  (async () => {
14
- const models = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models;
15
- if (models.User) {
16
- try {
15
+ // admin user seed
16
+ try {
17
+ const models = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models;
18
+ if (models.User) {
17
19
  const adminUser = await models.User.findOne({ role: 'admin' });
18
20
  if (!adminUser) {
19
21
  const defaultPassword = process.env.DEFAULT_ADMIN_PASSWORD || 'changethis';
@@ -29,10 +31,13 @@ const UserRouter = (options) => {
29
31
  });
30
32
  logger.warn('Default admin user created. Please change the default password immediately!', result._doc);
31
33
  }
32
- } catch (error) {
33
- logger.error('Error checking/creating admin user:', error);
34
34
  }
35
+ } catch (error) {
36
+ logger.error('Error checking/creating admin user');
37
+ console.log(error);
35
38
  }
39
+
40
+ // default user avatar seed
36
41
  options.png = {
37
42
  buffer: {
38
43
  'invalid-token': fs.readFileSync(`./src/client/public/default/assets/mailer/api-user-invalid-token.png`),
@@ -43,6 +48,27 @@ const UserRouter = (options) => {
43
48
  res.set('Content-Type', 'image/png');
44
49
  },
45
50
  };
51
+
52
+ try {
53
+ const models = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models;
54
+ const name = 'api-user-default-avatar.png';
55
+ const imageFile = await models.File.findOne({ name });
56
+ let _id;
57
+ if (imageFile) {
58
+ _id = imageFile._id;
59
+ } else {
60
+ const file = await new models.File(
61
+ FileFactory.create(fs.readFileSync(`./src/client/public/default/assets/mailer/${name}`), name),
62
+ ).save();
63
+ _id = file._id;
64
+ }
65
+ options.getDefaultProfileImageId = async () => {
66
+ return _id;
67
+ };
68
+ } catch (error) {
69
+ logger.error('Error checking/creating default profile image');
70
+ console.log(error);
71
+ }
46
72
  })();
47
73
 
48
74
  router.post(`/mailer/:id`, authMiddleware, async (req, res) => {
@@ -9,7 +9,6 @@ import { DataBaseProvider } from '../../db/DataBaseProvider.js';
9
9
  import { FileFactory } from '../file/file.service.js';
10
10
  import { UserDto } from './user.model.js';
11
11
  import { selectDtoFactory, ValkeyAPI } from '../../server/valkey.js';
12
- import { getDefaultProfileImageId } from '../../server/client-icons.js';
13
12
 
14
13
  const logger = loggerFactory(import.meta);
15
14
 
@@ -31,20 +30,10 @@ const UserService = {
31
30
  const token = hashJWT({ email: req.body.email }, '15m');
32
31
  const payloadToken = hashJWT({ email: req.body.email }, '15m');
33
32
  const id = `${options.host}${options.path}`;
34
- const translate = {
35
- H1: {
36
- en: 'Recover your account',
37
- es: 'Recupera tu cuenta',
38
- },
39
- P1: {
40
- en: 'To recover your account, please click the button below:',
41
- es: 'Para recuperar tu cuenta, haz click en el botón de abajo:',
42
- },
43
- BTN_LABEL: {
44
- en: 'Recover Password',
45
- es: 'Recuperar Contraseña',
46
- },
47
- };
33
+ const translate = MailerProvider.instance[id].translateTemplates.recoverEmail;
34
+ const recoverUrl = `${process.env.NODE_ENV === 'development' ? 'http://' : 'https://'}${req.body.hostname}${
35
+ req.body.proxyPath
36
+ }recover?payload=${payloadToken}`;
48
37
  const sendResult = await MailerProvider.send({
49
38
  id,
50
39
  sendOptions: {
@@ -55,13 +44,8 @@ const UserService = {
55
44
  .replace('{{H1}}', translate.H1[req.lang])
56
45
  .replace('{{P1}}', translate.P1[req.lang])
57
46
  .replace('{{TOKEN}}', token)
58
- .replace(`{{COMPANY}}`, options.host) // html body
59
- .replace(
60
- '{{RECOVER_WEB_URL}}',
61
- `${process.env === 'development' ? 'http://' : 'https://'}${options.host}${options.path}${
62
- options.path === '/' ? 'recover' : `/recover`
63
- }?payload=${payloadToken}`,
64
- )
47
+ .replace(`{{COMPANY}}`, req.body.hostname) // html body
48
+ .replace('{{RECOVER_WEB_URL}}', recoverUrl)
65
49
  .replace('{{RECOVER_BTN_LABEL}}', translate.BTN_LABEL[req.lang]),
66
50
 
67
51
  attachments: [
@@ -98,18 +82,7 @@ const UserService = {
98
82
  },
99
83
  );
100
84
  }
101
- const translate = {
102
- H1: {
103
- en: 'Confirm Your Email',
104
- zh: '请确认您的电子邮箱',
105
- es: 'Confirma tu correo electrónico',
106
- },
107
- P1: {
108
- en: 'Email confirmed! Thanks.',
109
- zh: '电子邮箱已确认!感谢。',
110
- es: 'Correo electrónico confirmado! Gracias.',
111
- },
112
- };
85
+ const translate = MailerProvider.instance[id].translateTemplates.confirmEmail;
113
86
  const sendResult = await MailerProvider.send({
114
87
  id,
115
88
  sendOptions: {
@@ -120,7 +93,7 @@ const UserService = {
120
93
  .replace('{{H1}}', translate.H1[req.lang])
121
94
  .replace('{{P1}}', translate.P1[req.lang])
122
95
  .replace('{{TOKEN}}', token)
123
- .replace(`{{COMPANY}}`, options.host), // html body
96
+ .replace(`{{COMPANY}}`, req.body.hostname), // html body
124
97
  attachments: [
125
98
  // {
126
99
  // filename: 'logo.png',
@@ -155,7 +128,7 @@ const UserService = {
155
128
  if (!user.profileImageId)
156
129
  await User.findByIdAndUpdate(
157
130
  user._id,
158
- { profileImageId: await getDefaultProfileImageId(File) },
131
+ { profileImageId: await options.getDefaultProfileImageId(File) },
159
132
  {
160
133
  runValidators: true,
161
134
  },
@@ -238,7 +211,7 @@ const UserService = {
238
211
  if (validatePassword.status === 'error') throw new Error(validatePassword.message);
239
212
  req.body.password = await hashPassword(req.body.password);
240
213
  req.body.role = req.body.role === 'guest' ? 'guest' : 'user';
241
- req.body.profileImageId = await getDefaultProfileImageId(File);
214
+ req.body.profileImageId = await options.getDefaultProfileImageId(File);
242
215
  const { _id } = await new User(req.body).save();
243
216
  if (_id) {
244
217
  const user = await User.findOne({ _id }).select(UserDto.select.get());
@@ -339,7 +312,7 @@ const UserService = {
339
312
  if (!file && !(await ValkeyAPI.getValkeyObject(options, req.auth.user.email))) {
340
313
  await User.findByIdAndUpdate(
341
314
  user._id,
342
- { profileImageId: await getDefaultProfileImageId(File) },
315
+ { profileImageId: await options.getDefaultProfileImageId(File) },
343
316
  {
344
317
  runValidators: true,
345
318
  },
@@ -5,6 +5,7 @@ import UnderpostBaremetal from './baremetal.js';
5
5
  import UnderpostDeploy from './deploy.js';
6
6
  import UnderpostTest from './test.js';
7
7
  import os from 'os';
8
+ import fs from 'fs-extra';
8
9
 
9
10
  const logger = loggerFactory(import.meta);
10
11
 
@@ -33,12 +34,13 @@ class UnderpostCluster {
33
34
  * @param {string} [options.nsUse=''] - Set the current kubectl namespace.
34
35
  * @param {boolean} [options.infoCapacity=false] - Display resource capacity information for the cluster.
35
36
  * @param {boolean} [options.infoCapacityPod=false] - Display resource capacity information for pods.
36
- * @param {boolean} [options.istio=false] - Deploy Istio service mesh.
37
37
  * @param {boolean} [options.pullImage=false] - Pull necessary Docker images before deployment.
38
38
  * @param {boolean} [options.dedicatedGpu=false] - Configure for dedicated GPU usage (e.g., NVIDIA GPU Operator).
39
39
  * @param {boolean} [options.kubeadm=false] - Initialize the cluster using Kubeadm.
40
40
  * @param {boolean} [options.k3s=false] - Initialize the cluster using K3s.
41
41
  * @param {boolean} [options.initHost=false] - Perform initial host setup (install Docker, Podman, Kind, Kubeadm, Helm).
42
+ * @param {boolean} [options.grafana=false] - Initialize the cluster with a Grafana deployment.
43
+ * @param {string} [options.prom=''] - Initialize the cluster with a Prometheus Operator deployment and monitor scrap for specified hosts.
42
44
  * @param {boolean} [options.uninstallHost=false] - Uninstall all host components.
43
45
  * @param {boolean} [options.config=false] - Apply general host configuration (SELinux, containerd, sysctl, firewalld).
44
46
  * @param {boolean} [options.worker=false] - Configure as a worker node (for Kubeadm or K3s join).
@@ -63,12 +65,13 @@ class UnderpostCluster {
63
65
  nsUse: '',
64
66
  infoCapacity: false,
65
67
  infoCapacityPod: false,
66
- istio: false,
67
68
  pullImage: false,
68
69
  dedicatedGpu: false,
69
70
  kubeadm: false,
70
71
  k3s: false,
71
72
  initHost: false,
73
+ grafana: false,
74
+ prom: '',
72
75
  uninstallHost: false,
73
76
  config: false,
74
77
  worker: false,
@@ -260,6 +263,42 @@ class UnderpostCluster {
260
263
  );
261
264
  }
262
265
 
266
+ if (options.grafana === true) {
267
+ shellExec(`kubectl delete deployment grafana --ignore-not-found`);
268
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/grafana`);
269
+ }
270
+
271
+ if (options.prom && typeof options.prom === 'string') {
272
+ shellExec(`kubectl delete deployment prometheus --ignore-not-found`);
273
+ shellExec(`kubectl delete configmap prometheus-config --ignore-not-found`);
274
+ shellExec(`kubectl delete service prometheus --ignore-not-found`);
275
+ // Prometheus server host: http://<prometheus-cluster-ip>:9090
276
+ const yaml = `${fs.readFileSync(`${underpostRoot}/manifests/prometheus/deployment.yaml`, 'utf8').replace(
277
+ '- targets: []',
278
+ `- targets: [${options.prom
279
+ .split(',')
280
+ .map((host) => `'${host}'`)
281
+ .join(',')}]`,
282
+ )}`;
283
+ console.log(yaml);
284
+ shellExec(`kubectl apply -f - <<EOF
285
+ ${yaml}
286
+ EOF
287
+ `);
288
+
289
+ // https://grafana.com/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/configuration/config-other-methods/prometheus/prometheus-operator/
290
+ // shellExec(
291
+ // `kubectl create -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/master/bundle.yaml`,
292
+ // );
293
+ // shellExec(`kubectl apply -f ${underpostRoot}/manifests/prometheus/prometheus-cr.yaml`);
294
+ // shellExec(`kubectl rollout status sts prometheus-prometheus -n default`);
295
+ // shellExec(`kubectl apply -f ${underpostRoot}/manifests/prometheus/prometheus-server.yaml`);
296
+ // shellExec(`helm repo add prometheus-community https://prometheus-community.github.io/helm-charts`);
297
+ // shellExec(`helm repo update`);
298
+ // shellExec(`helm install prometheus prometheus-community/prometheus`);
299
+ // shellExec(`kubectl rollout status deployment prometheus-server -n default`);
300
+ }
301
+
263
302
  if (options.full === true || options.valkey === true) {
264
303
  if (options.pullImage === true) {
265
304
  // shellExec(`sudo podman pull valkey/valkey:latest`);
@@ -371,21 +410,10 @@ class UnderpostCluster {
371
410
  const successInstance = await UnderpostTest.API.statusMonitor('mongodb-0', 'Running', 'pods', 1000, 60 * 10);
372
411
 
373
412
  if (successInstance) {
374
- if (!options.mongoDbHost) options.mongoDbHost = 'mongodb-service';
413
+ if (!options.mongoDbHost) options.mongoDbHost = 'mongodb-0.mongodb-service';
375
414
  const mongoConfig = {
376
415
  _id: 'rs0',
377
- members: [
378
- {
379
- _id: 0,
380
- host: `${options.mongoDbHost === 'mongodb-service' ? 'mongodb-0.' : ''}${options.mongoDbHost}:27017`,
381
- priority: 1,
382
- },
383
- // {
384
- // _id: 1,
385
- // host: `${options.mongoDbHost === 'mongodb-service' ? 'mongodb-1.' : ''}${options.mongoDbHost}:27017`,
386
- // priority: 1,
387
- // },
388
- ],
416
+ members: options.mongoDbHost.split(',').map((host, index) => ({ _id: index, host: `${host}:27017` })),
389
417
  };
390
418
 
391
419
  shellExec(
@@ -644,17 +672,9 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
644
672
  }
645
673
  },
646
674
 
647
- /**
648
- * @method getResourcesCapacity
649
- * @description Retrieves and returns the allocatable CPU and memory resources
650
- * of the Kubernetes node.
651
- * @param {boolean} [isKubeadmOrK3s=false] - If true, assumes a kubeadm or k3s-managed node;
652
- * otherwise, assumes a Kind worker node.
653
- * @returns {object} An object containing CPU and memory resources with values and units.
654
- */
655
- getResourcesCapacity(isKubeadmOrK3s = false) {
675
+ getResourcesCapacity(node) {
656
676
  const resources = {};
657
- const nodeName = isKubeadmOrK3s ? os.hostname() : 'kind-worker';
677
+ const nodeName = node ?? os.hostname();
658
678
  const info = shellExec(`kubectl describe node ${nodeName} | grep -E '(Allocatable:|Capacity:)' -A 6`, {
659
679
  stdout: true,
660
680
  silent: true,
package/src/cli/cron.js CHANGED
@@ -21,7 +21,6 @@ const logger = loggerFactory(import.meta);
21
21
  * @memberof UnderpostCron
22
22
  */
23
23
  class UnderpostCron {
24
- static NETWORK = [];
25
24
  static JOB = {
26
25
  /**
27
26
  * DNS cli API
@@ -50,10 +49,9 @@ class UnderpostCron {
50
49
  callback: async function (
51
50
  deployList = 'default',
52
51
  jobList = Object.keys(UnderpostCron.JOB),
53
- options = { itc: false, init: false, git: false, dashboardUpdate: false },
52
+ options = { itc: false, init: false, git: false },
54
53
  ) {
55
54
  if (options.init === true) {
56
- UnderpostCron.NETWORK = [];
57
55
  const jobDeployId = fs.readFileSync('./engine-private/deploy/dd.cron', 'utf8').trim();
58
56
  deployList = fs.readFileSync('./engine-private/deploy/dd.router', 'utf8').trim();
59
57
  const confCronConfig = JSON.parse(fs.readFileSync(`./engine-private/conf/${jobDeployId}/conf.cron.json`));
@@ -61,26 +59,11 @@ class UnderpostCron {
61
59
  for (const job of Object.keys(confCronConfig.jobs)) {
62
60
  const name = `${jobDeployId}-${job}`;
63
61
  let deployId;
64
- if (!options.dashboardUpdate) shellExec(Cmd.delete(name));
65
- switch (job) {
66
- case 'dns':
67
- deployId = jobDeployId;
68
- break;
69
-
70
- default:
71
- deployId = deployList;
72
- break;
73
- }
74
- if (!options.dashboardUpdate)
75
- shellExec(Cmd.cron(deployId, job, name, confCronConfig.jobs[job].expression, options));
76
- UnderpostCron.NETWORK.push({
77
- deployId,
78
- jobId: job,
79
- expression: confCronConfig.jobs[job].expression,
80
- });
62
+ shellExec(Cmd.delete(name));
63
+ deployId = UnderpostCron.API.getRelatedDeployId(job);
64
+ shellExec(Cmd.cron(deployId, job, name, confCronConfig.jobs[job].expression, options));
81
65
  }
82
66
  }
83
- if (options.dashboardUpdate === true) await UnderpostCron.API.updateDashboardData();
84
67
  if (fs.existsSync(`./tmp/await-deploy`)) fs.remove(`./tmp/await-deploy`);
85
68
  return;
86
69
  }
@@ -89,30 +72,14 @@ class UnderpostCron {
89
72
  if (UnderpostCron.JOB[jobId]) await UnderpostCron.JOB[jobId].callback(deployList, options);
90
73
  }
91
74
  },
92
- async updateDashboardData() {
93
- try {
94
- const deployId = process.env.DEFAULT_DEPLOY_ID;
95
- const host = process.env.DEFAULT_DEPLOY_HOST;
96
- const path = process.env.DEFAULT_DEPLOY_PATH;
97
- const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
98
- const confServer = JSON.parse(fs.readFileSync(confServerPath, 'utf8'));
99
- const { db } = confServer[host][path];
100
-
101
- await DataBaseProvider.load({ apis: ['cron'], host, path, db });
102
-
103
- /** @type {import('../api/cron/cron.model.js').CronModel} */
104
- const Cron = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Cron;
105
-
106
- await Cron.deleteMany();
107
-
108
- for (const cronInstance of UnderpostCron.NETWORK) {
109
- logger.info('save', cronInstance);
110
- await new Cron(cronInstance).save();
111
- }
112
-
113
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
114
- } catch (error) {
115
- logger.error(error, error.stack);
75
+ getRelatedDeployId(jobId) {
76
+ switch (jobId) {
77
+ case 'dns':
78
+ return fs.readFileSync('./engine-private/deploy/dd.cron', 'utf8').trim();
79
+ case 'backup':
80
+ return fs.readFileSync('./engine-private/deploy/dd.router', 'utf8').trim();
81
+ default:
82
+ return fs.readFileSync('./engine-private/deploy/dd.cron', 'utf8').trim();
116
83
  }
117
84
  },
118
85
  };
package/src/cli/db.js CHANGED
@@ -3,6 +3,9 @@ import { loggerFactory } from '../server/logger.js';
3
3
  import { shellExec } from '../server/process.js';
4
4
  import fs from 'fs-extra';
5
5
  import UnderpostDeploy from './deploy.js';
6
+ import UnderpostCron from './cron.js';
7
+ import { DataBaseProvider } from '../db/DataBaseProvider.js';
8
+ import { loadReplicas, pathPortAssignmentFactory } from '../server/conf.js';
6
9
 
7
10
  const logger = loggerFactory(import.meta);
8
11
 
@@ -174,25 +177,6 @@ class UnderpostDB {
174
177
  }`,
175
178
  );
176
179
  }
177
- if (false) {
178
- const containerBaseBackupPath = '/backup';
179
- let timeFolder = shellExec(
180
- `sudo kubectl exec -i ${podName} -- sh -c "cd ${containerBaseBackupPath} && ls -a"`,
181
- {
182
- stdout: true,
183
- disableLog: false,
184
- silent: true,
185
- },
186
- ).split(`\n`);
187
- timeFolder = timeFolder[timeFolder.length - 2];
188
- if (timeFolder === '..') {
189
- logger.warn(`Cannot backup available`, { timeFolder });
190
- } else {
191
- shellExec(
192
- `sudo kubectl cp ${nameSpace}/${podName}:${containerBaseBackupPath}/${timeFolder}/${dbName} ${_toNewBsonPath}`,
193
- );
194
- }
195
- }
196
180
  }
197
181
  break;
198
182
  }
@@ -216,6 +200,152 @@ class UnderpostDB {
216
200
  }
217
201
  }
218
202
  },
203
+ async clusterMetadataFactory(
204
+ deployId = process.env.DEFAULT_DEPLOY_ID,
205
+ host = process.env.DEFAULT_DEPLOY_HOST,
206
+ path = process.env.DEFAULT_DEPLOY_PATH,
207
+ ) {
208
+ deployId = deployId ?? process.env.DEFAULT_DEPLOY_ID;
209
+ host = host ?? process.env.DEFAULT_DEPLOY_HOST;
210
+ path = path ?? process.env.DEFAULT_DEPLOY_PATH;
211
+ const env = 'production';
212
+ const deployList = fs.readFileSync('./engine-private/deploy/dd.router', 'utf8').split(',');
213
+
214
+ const { db } = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'))[host][
215
+ path
216
+ ];
217
+ try {
218
+ await DataBaseProvider.load({ apis: ['instance', 'cron'], host, path, db });
219
+
220
+ /** @type {import('../api/instance/instance.model.js').InstanceModel} */
221
+ const Instance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Instance;
222
+
223
+ await Instance.deleteMany();
224
+
225
+ for (const _deployId of deployList) {
226
+ const deployId = _deployId.trim();
227
+ if (!deployId) continue;
228
+ const confServer = loadReplicas(
229
+ JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
230
+ 'proxy',
231
+ );
232
+ const router = await UnderpostDeploy.API.routerFactory(deployId, env);
233
+ const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
234
+
235
+ for (const host of Object.keys(confServer)) {
236
+ for (const { path, port } of pathPortAssignmentData[host]) {
237
+ if (!confServer[host][path]) continue;
238
+
239
+ const { client, runtime, apis, peer } = confServer[host][path];
240
+ {
241
+ const body = {
242
+ deployId,
243
+ host,
244
+ path,
245
+ port,
246
+ client,
247
+ runtime,
248
+ apis,
249
+ };
250
+
251
+ logger.info('Instance save', body);
252
+ await new Instance(body).save();
253
+ }
254
+
255
+ if (peer) {
256
+ const body = {
257
+ deployId,
258
+ host,
259
+ path: path === '/' ? '/peer' : `${path}/peer`,
260
+ port: port + 1,
261
+ runtime: 'nodejs',
262
+ };
263
+
264
+ logger.info('Instance save', body);
265
+ await new Instance(body).save();
266
+ }
267
+ }
268
+ }
269
+ }
270
+ } catch (error) {
271
+ logger.error(error, error.stack);
272
+ }
273
+
274
+ try {
275
+ const cronDeployId = fs.readFileSync('./engine-private/deploy/dd.cron', 'utf8').trim();
276
+ const confCronPath = `./engine-private/conf/${cronDeployId}/conf.cron.json`;
277
+ const confCron = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
278
+
279
+ await DataBaseProvider.load({ apis: ['cron'], host, path, db });
280
+
281
+ /** @type {import('../api/cron/cron.model.js').CronModel} */
282
+ const Cron = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Cron;
283
+
284
+ await Cron.deleteMany();
285
+
286
+ for (const jobId of Object.keys(confCron.jobs)) {
287
+ const body = {
288
+ jobId,
289
+ deployId: UnderpostCron.API.getRelatedDeployId(jobId),
290
+ expression: confCron.jobs[jobId].expression,
291
+ enabled: confCron.jobs[jobId].enabled,
292
+ };
293
+ logger.info('Cron save', body);
294
+ await new Cron(body).save();
295
+ }
296
+ } catch (error) {
297
+ logger.error(error, error.stack);
298
+ }
299
+ await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
300
+ },
301
+ clusterMetadataBackupCallback(
302
+ deployId = process.env.DEFAULT_DEPLOY_ID,
303
+ host = process.env.DEFAULT_DEPLOY_HOST,
304
+ path = process.env.DEFAULT_DEPLOY_PATH,
305
+ options = {
306
+ generate: false,
307
+ itc: false,
308
+ import: false,
309
+ export: false,
310
+ instances: false,
311
+ crons: false,
312
+ },
313
+ ) {
314
+ deployId = deployId ?? process.env.DEFAULT_DEPLOY_ID;
315
+ host = host ?? process.env.DEFAULT_DEPLOY_HOST;
316
+ path = path ?? process.env.DEFAULT_DEPLOY_PATH;
317
+
318
+ if (options.generate === true) {
319
+ UnderpostDB.API.clusterMetadataFactory(deployId, host, path);
320
+ }
321
+
322
+ if (options.instances === true) {
323
+ const outputPath = './engine-private/instances';
324
+ if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
325
+ const collection = 'instances';
326
+ if (options.export === true)
327
+ shellExec(
328
+ `node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
329
+ );
330
+ if (options.import === true)
331
+ shellExec(
332
+ `node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
333
+ );
334
+ }
335
+ if (options.crons === true) {
336
+ const outputPath = './engine-private/crons';
337
+ if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
338
+ const collection = 'crons';
339
+ if (options.export === true)
340
+ shellExec(
341
+ `node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
342
+ );
343
+ if (options.import === true)
344
+ shellExec(
345
+ `node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
346
+ );
347
+ }
348
+ },
219
349
  };
220
350
  }
221
351