underpost 2.8.64 → 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.
Files changed (51) hide show
  1. package/.vscode/extensions.json +3 -2
  2. package/.vscode/settings.json +2 -0
  3. package/CHANGELOG.md +24 -4
  4. package/README.md +39 -2
  5. package/bin/deploy.js +1205 -131
  6. package/bin/file.js +8 -0
  7. package/bin/index.js +1 -233
  8. package/cli.md +451 -0
  9. package/docker-compose.yml +1 -1
  10. package/jsdoc.json +1 -1
  11. package/manifests/calico-custom-resources.yaml +25 -0
  12. package/manifests/deployment/adminer/deployment.yaml +32 -0
  13. package/manifests/deployment/adminer/kustomization.yaml +7 -0
  14. package/manifests/deployment/adminer/service.yaml +13 -0
  15. package/manifests/mongodb-4.4/service-deployment.yaml +1 -1
  16. package/manifests/postgresql/configmap.yaml +9 -0
  17. package/manifests/postgresql/kustomization.yaml +10 -0
  18. package/manifests/postgresql/pv.yaml +15 -0
  19. package/manifests/postgresql/pvc.yaml +13 -0
  20. package/manifests/postgresql/service.yaml +10 -0
  21. package/manifests/postgresql/statefulset.yaml +37 -0
  22. package/manifests/valkey/statefulset.yaml +6 -4
  23. package/package.json +3 -9
  24. package/src/api/user/user.service.js +13 -10
  25. package/src/cli/cluster.js +113 -11
  26. package/src/cli/db.js +18 -8
  27. package/src/cli/deploy.js +157 -58
  28. package/src/cli/fs.js +14 -3
  29. package/src/cli/image.js +0 -68
  30. package/src/cli/index.js +312 -0
  31. package/src/cli/monitor.js +170 -26
  32. package/src/cli/repository.js +5 -2
  33. package/src/client/components/core/Account.js +3 -3
  34. package/src/client/components/core/CalendarCore.js +0 -1
  35. package/src/client/components/core/Css.js +0 -1
  36. package/src/client/components/core/CssCore.js +2 -0
  37. package/src/client/components/core/EventsUI.js +1 -1
  38. package/src/client/components/core/JoyStick.js +2 -2
  39. package/src/client/components/core/Modal.js +1 -0
  40. package/src/client/components/core/RichText.js +1 -11
  41. package/src/index.js +9 -8
  42. package/src/mailer/MailerProvider.js +3 -0
  43. package/src/server/client-build.js +13 -0
  44. package/src/server/conf.js +48 -0
  45. package/src/server/dns.js +47 -17
  46. package/src/server/json-schema.js +77 -0
  47. package/src/server/peer.js +2 -2
  48. package/src/server/proxy.js +4 -4
  49. package/src/server/runtime.js +24 -9
  50. package/src/server/start.js +122 -0
  51. package/src/server/valkey.js +25 -11
package/src/cli/deploy.js CHANGED
@@ -13,15 +13,31 @@ import { shellExec } from '../server/process.js';
13
13
  import fs from 'fs-extra';
14
14
  import dotenv from 'dotenv';
15
15
  import { DataBaseProvider } from '../db/DataBaseProvider.js';
16
+ import UnderpostRootEnv from './env.js';
17
+ import UnderpostCluster from './cluster.js';
16
18
 
17
19
  const logger = loggerFactory(import.meta);
18
20
 
19
21
  class UnderpostDeploy {
20
22
  static NETWORK = {};
21
23
  static API = {
22
- sync(deployList) {
23
- const deployGroupId = 'dd.tmp';
24
+ sync(deployList, { versions, replicas }) {
25
+ const deployGroupId = 'dd.router';
24
26
  fs.writeFileSync(`./engine-private/deploy/${deployGroupId}`, deployList, 'utf8');
27
+ const totalPods = deployList.split(',').length * versions.split(',').length * parseInt(replicas);
28
+ const limitFactor = 0.8;
29
+ const reserveFactor = 0.05;
30
+ const resources = UnderpostCluster.API.getResourcesCapacity();
31
+ const memory = parseInt(resources.memory.value / totalPods);
32
+ const cpu = parseInt(resources.cpu.value / totalPods);
33
+ UnderpostRootEnv.API.set(
34
+ 'resources.requests.memory',
35
+ `${parseInt(memory * reserveFactor)}${resources.memory.unit}`,
36
+ );
37
+ UnderpostRootEnv.API.set('resources.requests.cpu', `${parseInt(cpu * reserveFactor)}${resources.cpu.unit}`);
38
+ UnderpostRootEnv.API.set('resources.limits.memory', `${parseInt(memory * limitFactor)}${resources.memory.unit}`);
39
+ UnderpostRootEnv.API.set('resources.limits.cpu', `${parseInt(cpu * limitFactor)}${resources.cpu.unit}`);
40
+ UnderpostRootEnv.API.set('total-pods', totalPods);
25
41
  return getDataDeploy({
26
42
  buildSingleReplica: true,
27
43
  deployGroupId,
@@ -35,52 +51,43 @@ class UnderpostDeploy {
35
51
  await Config.build(undefined, 'proxy', deployList);
36
52
  return buildPortProxyRouter(env === 'development' ? 80 : 443, buildProxyRouter());
37
53
  },
38
- async buildManifest(deployList, env, version) {
39
- for (const _deployId of deployList.split(',')) {
40
- const deployId = _deployId.trim();
41
- if (!deployId) continue;
42
- const confServer = loadReplicas(
43
- JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
44
- 'proxy',
45
- );
46
- const router = await UnderpostDeploy.API.routerFactory(deployId, env);
47
- const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
48
- const { fromPort, toPort } = deployRangePortFactory(router);
49
-
50
- fs.mkdirSync(`./engine-private/conf/${deployId}/build/${env}`, { recursive: true });
51
- if (env === 'development') fs.mkdirSync(`./manifests/deployment/${deployId}-${env}`, { recursive: true });
52
-
53
- logger.info('port range', { deployId, fromPort, toPort });
54
- // const customImg = `underpost-engine:${version && typeof version === 'string' ? version : '0.0.0'}`;
55
- // lifecycle:
56
- // postStart:
57
- // exec:
58
- // command:
59
- // - /bin/sh
60
- // - -c
61
- // - >
62
- // sleep 20 &&
63
- // npm install -g underpost &&
64
- // underpost secret underpost --create-from-file /etc/config/.env.${env}
65
- const deploymentYamlParts = `apiVersion: apps/v1
54
+ deploymentYamlServiceFactory({ deployId, env, port, deploymentVersions }) {
55
+ return deploymentVersions
56
+ .map(
57
+ (version, i) => ` - name: ${deployId}-${env}-${version}-service
58
+ port: ${port}
59
+ weight: ${i === 0 ? 100 : 0}
60
+ `,
61
+ )
62
+ .join('');
63
+ },
64
+ deploymentYamlPartsFactory({ deployId, env, suffix, resources, replicas }) {
65
+ return `apiVersion: apps/v1
66
66
  kind: Deployment
67
67
  metadata:
68
- name: ${deployId}-${env}
68
+ name: ${deployId}-${env}-${suffix}
69
69
  labels:
70
- app: ${deployId}-${env}
70
+ app: ${deployId}-${env}-${suffix}
71
71
  spec:
72
- replicas: 2
72
+ replicas: ${replicas}
73
73
  selector:
74
74
  matchLabels:
75
- app: ${deployId}-${env}
75
+ app: ${deployId}-${env}-${suffix}
76
76
  template:
77
77
  metadata:
78
78
  labels:
79
- app: ${deployId}-${env}
79
+ app: ${deployId}-${env}-${suffix}
80
80
  spec:
81
81
  containers:
82
- - name: ${deployId}-${env}
82
+ - name: ${deployId}-${env}-${suffix}
83
83
  image: localhost/debian:underpost
84
+ resources:
85
+ requests:
86
+ memory: "${resources.requests.memory}"
87
+ cpu: "${resources.requests.cpu}"
88
+ limits:
89
+ memory: "${resources.limits.memory}"
90
+ cpu: "${resources.limits.cpu}"
84
91
  command:
85
92
  - /bin/sh
86
93
  - -c
@@ -88,7 +95,7 @@ spec:
88
95
  npm install -g npm@11.2.0 &&
89
96
  npm install -g underpost &&
90
97
  underpost secret underpost --create-from-file /etc/config/.env.${env} &&
91
- underpost dockerfile-node-script --build --run ${deployId} ${env}
98
+ underpost start --build --run ${deployId} ${env}
92
99
  volumeMounts:
93
100
  - name: config-volume
94
101
  mountPath: /etc/config
@@ -96,27 +103,50 @@ spec:
96
103
  - name: config-volume
97
104
  configMap:
98
105
  name: underpost-config
99
- # image: localhost/${deployId}-${env}:${version && typeof version === 'string' ? version : '0.0.0'}
100
106
  ---
101
107
  apiVersion: v1
102
108
  kind: Service
103
109
  metadata:
104
- name: ${deployId}-${env}-service
110
+ name: ${deployId}-${env}-${suffix}-service
105
111
  spec:
106
112
  selector:
107
- app: ${deployId}-${env}
113
+ app: ${deployId}-${env}-${suffix}
108
114
  ports:
109
- type: LoadBalancer`.split('ports:');
110
- deploymentYamlParts[1] =
111
- buildKindPorts(fromPort, toPort) +
112
- ` type: LoadBalancer
113
- `;
115
+ {{ports}} type: LoadBalancer`;
116
+ },
117
+ async buildManifest(deployList, env, options) {
118
+ const resources = UnderpostDeploy.API.resourcesFactory();
119
+ const replicas = options.replicas;
114
120
 
115
- fs.writeFileSync(
116
- `./engine-private/conf/${deployId}/build/${env}/deployment.yaml`,
117
- deploymentYamlParts.join(`ports:
118
- `),
121
+ for (const _deployId of deployList.split(',')) {
122
+ const deployId = _deployId.trim();
123
+ if (!deployId) continue;
124
+ const confServer = loadReplicas(
125
+ JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
126
+ 'proxy',
119
127
  );
128
+ const router = await UnderpostDeploy.API.routerFactory(deployId, env);
129
+ const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
130
+ const { fromPort, toPort } = deployRangePortFactory(router);
131
+ const deploymentVersions = options.versions.split(',');
132
+ fs.mkdirSync(`./engine-private/conf/${deployId}/build/${env}`, { recursive: true });
133
+ if (env === 'development') fs.mkdirSync(`./manifests/deployment/${deployId}-${env}`, { recursive: true });
134
+
135
+ logger.info('port range', { deployId, fromPort, toPort });
136
+
137
+ let deploymentYamlParts = '';
138
+ for (const deploymentVersion of deploymentVersions) {
139
+ deploymentYamlParts += `---
140
+ ${UnderpostDeploy.API.deploymentYamlPartsFactory({
141
+ deployId,
142
+ env,
143
+ suffix: deploymentVersion,
144
+ resources,
145
+ replicas,
146
+ }).replace('{{ports}}', buildKindPorts(fromPort, toPort))}
147
+ `;
148
+ }
149
+ fs.writeFileSync(`./engine-private/conf/${deployId}/build/${env}/deployment.yaml`, deploymentYamlParts, 'utf8');
120
150
 
121
151
  let proxyYaml = '';
122
152
  let secretYaml = '';
@@ -163,8 +193,13 @@ spec:
163
193
  - prefix: ${path}
164
194
  enableWebsockets: true
165
195
  services:
166
- - name: ${deployId}-${env}-service
167
- port: ${port}`;
196
+ ${UnderpostDeploy.API.deploymentYamlServiceFactory({
197
+ deployId,
198
+ env,
199
+ port,
200
+ deploymentVersions:
201
+ options.traffic && typeof options.traffic === 'string' ? options.traffic.split(',') : ['blue'],
202
+ })}`;
168
203
  }
169
204
  }
170
205
  const yamlPath = `./engine-private/conf/${deployId}/build/${env}/proxy.yaml`;
@@ -185,6 +220,14 @@ spec:
185
220
  }
186
221
  }
187
222
  },
223
+ getCurrentTraffic(deployId) {
224
+ // kubectl get deploy,sts,svc,configmap,secret -n default -o yaml --export > default.yaml
225
+ const hostTest = Object.keys(
226
+ JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
227
+ )[0];
228
+ const info = shellExec(`sudo kubectl get HTTPProxy/${hostTest} -o yaml`, { silent: true, stdout: true });
229
+ return info.match('blue') ? 'blue' : info.match('green') ? 'green' : null;
230
+ },
188
231
  async callback(
189
232
  deployList = 'default',
190
233
  env = 'development',
@@ -196,8 +239,13 @@ spec:
196
239
  infoUtil: false,
197
240
  expose: false,
198
241
  cert: false,
199
- version: '',
242
+ versions: '',
243
+ traffic: '',
200
244
  dashboardUpdate: false,
245
+ replicas: '',
246
+ disableUpdateDeployment: false,
247
+ infoTraffic: false,
248
+ rebuildClientsBundle: false,
201
249
  },
202
250
  ) {
203
251
  if (options.infoUtil === true)
@@ -205,11 +253,28 @@ spec:
205
253
  kubectl rollout restart deployment/deployment-name
206
254
  kubectl rollout undo deployment/deployment-name
207
255
  kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
208
- `);
256
+ kubectl get pods -w
257
+ kubectl patch statefulset service-valkey --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"valkey/valkey:latest"}]'
258
+ kubectl patch statefulset service-valkey -p '{"spec":{"template":{"spec":{"containers":[{"name":"service-valkey","imagePullPolicy":"Never"}]}}}}'
259
+ `);
209
260
  if (deployList === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`))
210
261
  deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
211
- if (options.sync) UnderpostDeploy.API.sync(deployList);
212
- if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env, options.version);
262
+ if (options.infoTraffic === true) {
263
+ for (const _deployId of deployList.split(',')) {
264
+ const deployId = _deployId.trim();
265
+ logger.info('', {
266
+ deployId,
267
+ env,
268
+ traffic: UnderpostDeploy.API.getCurrentTraffic(deployId),
269
+ });
270
+ }
271
+ return;
272
+ }
273
+ if (options.rebuildClientsBundle === true) await UnderpostDeploy.API.rebuildClientsBundle(deployList);
274
+ if (!(options.versions && typeof options.versions === 'string')) options.versions = 'blue,green';
275
+ if (!options.replicas) options.replicas = 1;
276
+ if (options.sync) UnderpostDeploy.API.sync(deployList, options);
277
+ if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env, options);
213
278
  if (options.infoRouter === true) logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
214
279
  if (options.dashboardUpdate === true) await UnderpostDeploy.API.updateDashboardData(deployList, env, options);
215
280
  if (options.infoRouter === true) return;
@@ -236,8 +301,12 @@ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
236
301
  shellExec(`sudo kubectl port-forward -n default svc/${svc.NAME} ${port}:${port}`, { async: true });
237
302
  continue;
238
303
  }
239
- shellExec(`sudo kubectl delete svc ${deployId}-${env}-service`);
240
- shellExec(`sudo kubectl delete deployment ${deployId}-${env}`);
304
+
305
+ if (!options.disableUpdateDeployment)
306
+ for (const version of options.versions.split(',')) {
307
+ shellExec(`sudo kubectl delete svc ${deployId}-${env}-${version}-service`);
308
+ shellExec(`sudo kubectl delete deployment ${deployId}-${env}-${version}`);
309
+ }
241
310
 
242
311
  const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
243
312
  for (const host of Object.keys(confServer)) {
@@ -252,7 +321,7 @@ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
252
321
  : `manifests/deployment/${deployId}-${env}`;
253
322
 
254
323
  if (!options.remove === true) {
255
- shellExec(`sudo kubectl apply -f ./${manifestsPath}/deployment.yaml`);
324
+ if (!options.disableUpdateDeployment) shellExec(`sudo kubectl apply -f ./${manifestsPath}/deployment.yaml`);
256
325
  shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml`);
257
326
  if (env === 'production' && options.cert === true)
258
327
  shellExec(`sudo kubectl apply -f ./${manifestsPath}/secret.yaml`);
@@ -315,6 +384,36 @@ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
315
384
 
316
385
  return result;
317
386
  },
387
+ rebuildClientsBundle(deployList) {
388
+ for (const _deployId of deployList.split(',')) {
389
+ const deployId = _deployId.trim();
390
+ const repoName = `engine-${deployId.split('-')[1]}`;
391
+
392
+ shellExec(`underpost script set ${deployId}-client-build '
393
+ cd /home/dd/engine &&
394
+ git checkout . &&
395
+ underpost pull . underpostnet/${repoName} &&
396
+ underpost pull ./engine-private underpostnet/${repoName}-private &&
397
+ underpost env ${deployId} production &&
398
+ node bin/deploy build-full-client ${deployId}
399
+ '`);
400
+
401
+ shellExec(`node bin script run ${deployId}-client-build --itc --pod-name ${deployId}`);
402
+ }
403
+ },
404
+ resourcesFactory() {
405
+ return {
406
+ requests: {
407
+ memory: UnderpostRootEnv.API.get('resources.requests.memory'),
408
+ cpu: UnderpostRootEnv.API.get('resources.requests.cpu'),
409
+ },
410
+ limits: {
411
+ memory: UnderpostRootEnv.API.get('resources.limits.memory'),
412
+ cpu: UnderpostRootEnv.API.get('resources.limits.cpu'),
413
+ },
414
+ totalPods: UnderpostRootEnv.API.get('total-pods'),
415
+ };
416
+ },
318
417
  async updateDashboardData(deployList, env, options) {
319
418
  try {
320
419
  const deployId = process.env.DEFAULT_DEPLOY_ID;
package/src/cli/fs.js CHANGED
@@ -24,7 +24,7 @@ class UnderpostFileStorage {
24
24
  getStorageConf(options) {
25
25
  let storage, storageConf;
26
26
  if (options.deployId && typeof options.deployId === 'string') {
27
- storageConf = `./engine-private/conf/${options.deployId}/storage.json`;
27
+ storageConf = options.storageFilePath ?? `./engine-private/conf/${options.deployId}/storage.json`;
28
28
  if (!fs.existsSync(storageConf)) fs.writeFileSync(storageConf, JSON.stringify({}), 'utf8');
29
29
  storage = JSON.parse(fs.readFileSync(storageConf, 'utf8'));
30
30
  }
@@ -35,7 +35,15 @@ class UnderpostFileStorage {
35
35
  },
36
36
  async recursiveCallback(
37
37
  path,
38
- options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false },
38
+ options = {
39
+ rm: false,
40
+ recursive: false,
41
+ deployId: '',
42
+ force: false,
43
+ pull: false,
44
+ git: false,
45
+ storageFilePath: '',
46
+ },
39
47
  ) {
40
48
  const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
41
49
  const deleteFiles = options.pull === true ? [] : UnderpostRepository.API.getDeleteFiles(path);
@@ -85,7 +93,10 @@ class UnderpostFileStorage {
85
93
  if (options.rm === true) return await UnderpostFileStorage.API.delete(path, options);
86
94
  return await UnderpostFileStorage.API.upload(path, options);
87
95
  },
88
- async upload(path, options = { rm: false, recursive: false, deployId: '', force: false, pull: false }) {
96
+ async upload(
97
+ path,
98
+ options = { rm: false, recursive: false, deployId: '', force: false, pull: false, storageFilePath: '' },
99
+ ) {
89
100
  UnderpostFileStorage.API.cloudinaryConfig();
90
101
  const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
91
102
  // path = UnderpostFileStorage.API.file2Zip(path);
package/src/cli/image.js CHANGED
@@ -60,74 +60,6 @@ class UnderpostImage {
60
60
  if (podmanSave === true) shellExec(`podman save -o ${tarFile} ${podManImg}`);
61
61
  if (kindLoad === true) shellExec(`sudo kind load image-archive ${tarFile}`);
62
62
  },
63
- async script(deployId = 'default', env = 'development', options = { run: false, build: false }) {
64
- if (options.build === true) {
65
- const buildBasePath = `/home/dd`;
66
- const repoName = `engine-${deployId.split('-')[1]}`;
67
- shellExec(`cd ${buildBasePath} && underpost clone underpostnet/${repoName}`);
68
- shellExec(`cd ${buildBasePath} && sudo mv ./${repoName} ./engine`);
69
- shellExec(`cd ${buildBasePath}/engine && underpost clone underpostnet/${repoName}-private`);
70
- shellExec(`cd ${buildBasePath}/engine && sudo mv ./${repoName}-private ./engine-private`);
71
- shellCd(`${buildBasePath}/engine`);
72
- shellExec(`npm install`);
73
- shellExec(`node bin/deploy conf ${deployId} ${env}`);
74
- if (fs.existsSync('./engine-private/itc-scripts')) {
75
- const itcScripts = await fs.readdir('./engine-private/itc-scripts');
76
- for (const itcScript of itcScripts)
77
- if (itcScript.match(deployId)) shellExec(`node ./engine-private/itc-scripts/${itcScript}`);
78
- }
79
- switch (deployId) {
80
- default:
81
- {
82
- {
83
- const originPath = `./src/db/mongo/MongooseDB.js`;
84
- fs.writeFileSync(
85
- originPath,
86
- fs.readFileSync(originPath, 'utf8').replaceAll(
87
- `connect: async (host, name) => {`,
88
- `connect: async (host, name) => {
89
- host = 'mongodb://mongodb-0.mongodb-service:27017';
90
- `,
91
- ),
92
- 'utf8',
93
- );
94
- }
95
-
96
- {
97
- const originPath = `./src/server/valkey.js`;
98
- fs.writeFileSync(
99
- originPath,
100
- fs.readFileSync(originPath, 'utf8').replaceAll(
101
- ` // port: 6379,
102
- // host: 'service-valkey.default.svc.cluster.local',`,
103
- ` port: 6379,
104
- host: 'service-valkey.default.svc.cluster.local',`,
105
- ),
106
- 'utf8',
107
- );
108
- }
109
- }
110
- break;
111
- }
112
- shellExec(`node bin/deploy build-full-client ${deployId}`);
113
- }
114
- if (options.run === true) {
115
- const runCmd = env === 'production' ? 'run prod-img' : 'run dev-img';
116
- if (fs.existsSync(`./engine-private/replica`)) {
117
- const replicas = await fs.readdir(`./engine-private/replica`);
118
- for (const replica of replicas) {
119
- if (!replica.match(deployId)) continue;
120
- shellExec(`node bin/deploy conf ${replica} ${env}`);
121
- shellExec(`npm ${runCmd} deploy deploy-id:${replica}`, { async: true });
122
- await awaitDeployMonitor(true);
123
- }
124
- }
125
- shellExec(`node bin/deploy conf ${deployId} ${env}`);
126
- shellExec(`npm ${runCmd} deploy deploy-id:${deployId}`, { async: true });
127
- await awaitDeployMonitor(true);
128
- await UnderpostMonitor.API.callback(deployId, env, { itc: true });
129
- }
130
- },
131
63
  },
132
64
  };
133
65
  }