underpost 3.2.9 → 3.2.10

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 (81) hide show
  1. package/.github/workflows/npmpkg.ci.yml +1 -0
  2. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  3. package/.github/workflows/release.cd.yml +1 -0
  4. package/.vscode/settings.json +10 -5
  5. package/CHANGELOG.md +122 -1
  6. package/CLI-HELP.md +22 -7
  7. package/README.md +37 -8
  8. package/bin/build.js +26 -9
  9. package/bin/deploy.js +20 -21
  10. package/bin/file.js +31 -13
  11. package/bin/index.js +2 -1
  12. package/bin/vs.js +1 -1
  13. package/bump.config.js +26 -0
  14. package/conf.js +20 -4
  15. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  16. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  17. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  18. package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
  19. package/manifests/kind-config-dev.yaml +8 -0
  20. package/manifests/mongodb/pv-pvc.yaml +44 -8
  21. package/manifests/mongodb/statefulset.yaml +55 -68
  22. package/package.json +27 -12
  23. package/scripts/k3s-node-setup.sh +28 -9
  24. package/src/api/core/core.router.js +19 -14
  25. package/src/api/core/core.service.js +5 -5
  26. package/src/api/default/default.router.js +22 -18
  27. package/src/api/default/default.service.js +5 -5
  28. package/src/api/document/document.router.js +28 -23
  29. package/src/api/document/document.service.js +100 -23
  30. package/src/api/file/file.router.js +19 -13
  31. package/src/api/file/file.service.js +9 -7
  32. package/src/api/test/test.router.js +17 -12
  33. package/src/api/types.js +24 -0
  34. package/src/api/user/guest.service.js +5 -4
  35. package/src/api/user/user.router.js +297 -288
  36. package/src/api/user/user.service.js +100 -35
  37. package/src/cli/baremetal.js +20 -11
  38. package/src/cli/cluster.js +196 -55
  39. package/src/cli/db.js +59 -60
  40. package/src/cli/deploy.js +273 -159
  41. package/src/cli/fs.js +3 -1
  42. package/src/cli/index.js +16 -9
  43. package/src/cli/ipfs.js +4 -6
  44. package/src/cli/kubectl.js +4 -1
  45. package/src/cli/lxd.js +217 -135
  46. package/src/cli/release.js +289 -131
  47. package/src/cli/repository.js +58 -7
  48. package/src/cli/run.js +152 -25
  49. package/src/cli/test.js +9 -3
  50. package/src/client/Default.index.js +9 -3
  51. package/src/client/components/core/Auth.js +4 -0
  52. package/src/client/components/core/PanelForm.js +56 -52
  53. package/src/client/components/core/Worker.js +162 -363
  54. package/src/client/sw/core.sw.js +174 -112
  55. package/src/db/DataBaseProvider.js +120 -20
  56. package/src/db/mongo/MongoBootstrap.js +587 -0
  57. package/src/db/mongo/MongooseDB.js +126 -22
  58. package/src/index.js +1 -1
  59. package/src/runtime/express/Express.js +2 -2
  60. package/src/runtime/wp/Wp.js +8 -5
  61. package/src/server/auth.js +2 -2
  62. package/src/server/client-build-docs.js +1 -1
  63. package/src/server/client-build.js +94 -129
  64. package/src/server/conf.js +20 -65
  65. package/src/server/process.js +180 -19
  66. package/src/server/runtime.js +1 -1
  67. package/src/server/start.js +12 -4
  68. package/src/ws/IoInterface.js +16 -16
  69. package/src/ws/core/channels/core.ws.chat.js +11 -11
  70. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  71. package/src/ws/core/channels/core.ws.stream.js +19 -19
  72. package/src/ws/core/core.ws.connection.js +8 -8
  73. package/src/ws/core/core.ws.server.js +6 -5
  74. package/src/ws/default/channels/default.ws.main.js +10 -10
  75. package/src/ws/default/default.ws.connection.js +4 -4
  76. package/src/ws/default/default.ws.server.js +4 -3
  77. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  78. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  79. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  80. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  81. /package/src/client/ssr/{pages → views}/Test.js +0 -0
package/src/cli/db.js CHANGED
@@ -10,8 +10,9 @@ import { mergeFile, splitFileFactory, loadConfServerJson, resolveConfSecrets } f
10
10
  import { loggerFactory } from '../server/logger.js';
11
11
  import { shellExec } from '../server/process.js';
12
12
  import fs from 'fs-extra';
13
- import { DataBaseProvider } from '../db/DataBaseProvider.js';
13
+ import { DataBaseProviderService } from '../db/DataBaseProvider.js';
14
14
  import { loadReplicas, pathPortAssignmentFactory, loadCronDeployEnv } from '../server/conf.js';
15
+ import { MongoBootstrap } from '../db/mongo/MongoBootstrap.js';
15
16
  import Underpost from '../index.js';
16
17
  import { timer } from '../client/components/core/CommonJs.js';
17
18
  const logger = loggerFactory(import.meta);
@@ -229,9 +230,12 @@ class UnderpostDB {
229
230
  * @param {string} params.bsonPath - BSON directory path.
230
231
  * @param {boolean} params.drop - Whether to drop existing database.
231
232
  * @param {boolean} params.preserveUUID - Whether to preserve UUIDs.
233
+ * @param {string} [params.user=''] - MongoDB username for authenticated restore.
234
+ * @param {string} [params.password=''] - MongoDB password for authenticated restore.
235
+ * @param {string} [params.authDatabase='admin'] - Auth database for restore command.
232
236
  * @return {boolean} Success status.
233
237
  */
234
- _importMongoDB({ pod, namespace, dbName, bsonPath, drop, preserveUUID }) {
238
+ _importMongoDB({ pod, namespace, dbName, bsonPath, drop, preserveUUID, user = '', password = '', authDatabase = 'admin' }) {
235
239
  try {
236
240
  const podName = pod.NAME;
237
241
  const containerBsonPath = `/${dbName}`;
@@ -268,9 +272,10 @@ class UnderpostDB {
268
272
  }
269
273
 
270
274
  // Restore database
271
- const restoreCmd = `mongorestore -d ${dbName} ${containerBsonPath}${drop ? ' --drop' : ''}${
272
- preserveUUID ? ' --preserveUUID' : ''
273
- }`;
275
+ const authFlags = user && password
276
+ ? ` --username ${JSON.stringify(user)} --password ${JSON.stringify(password)} --authenticationDatabase ${JSON.stringify(authDatabase)}`
277
+ : '';
278
+ const restoreCmd = `mongorestore -d ${dbName} ${containerBsonPath}${drop ? ' --drop' : ''}${preserveUUID ? ' --preserveUUID' : ''}${authFlags}`;
274
279
  Underpost.kubectl.exec({ podName, namespace, command: restoreCmd });
275
280
 
276
281
  logger.info('Successfully imported MongoDB database', { podName, dbName });
@@ -291,9 +296,12 @@ class UnderpostDB {
291
296
  * @param {string} params.dbName - Database name.
292
297
  * @param {string} params.outputPath - Output directory path.
293
298
  * @param {string} [params.collections=''] - Comma-separated collection list.
299
+ * @param {string} [params.user=''] - MongoDB username for authenticated dump.
300
+ * @param {string} [params.password=''] - MongoDB password for authenticated dump.
301
+ * @param {string} [params.authDatabase='admin'] - Auth database for dump command.
294
302
  * @return {boolean} Success status.
295
303
  */
296
- _exportMongoDB({ pod, namespace, dbName, outputPath, collections = '' }) {
304
+ _exportMongoDB({ pod, namespace, dbName, outputPath, collections = '', user = '', password = '', authDatabase = 'admin' }) {
297
305
  try {
298
306
  const podName = pod.NAME;
299
307
  const containerBsonPath = `/${dbName}`;
@@ -308,14 +316,18 @@ class UnderpostDB {
308
316
  });
309
317
 
310
318
  // Dump database or specific collections
319
+ const authFlags = user && password
320
+ ? ` --username ${JSON.stringify(user)} --password ${JSON.stringify(password)} --authenticationDatabase ${JSON.stringify(authDatabase)}`
321
+ : '';
322
+
311
323
  if (collections) {
312
324
  const collectionList = collections.split(',').map((c) => c.trim());
313
325
  for (const collection of collectionList) {
314
- const dumpCmd = `mongodump -d ${dbName} --collection ${collection} -o /`;
326
+ const dumpCmd = `mongodump -d ${dbName} --collection ${collection} -o /${authFlags}`;
315
327
  Underpost.kubectl.exec({ podName, namespace, command: dumpCmd });
316
328
  }
317
329
  } else {
318
- const dumpCmd = `mongodump -d ${dbName} -o /`;
330
+ const dumpCmd = `mongodump -d ${dbName} -o /${authFlags}`;
319
331
  Underpost.kubectl.exec({ podName, namespace, command: dumpCmd });
320
332
  }
321
333
 
@@ -347,9 +359,12 @@ class UnderpostDB {
347
359
  * @param {string} params.podName - Pod name.
348
360
  * @param {string} params.namespace - Namespace.
349
361
  * @param {string} params.dbName - Database name.
362
+ * @param {string} [params.user=''] - MongoDB username for authenticated stats query.
363
+ * @param {string} [params.password=''] - MongoDB password for authenticated stats query.
364
+ * @param {string} [params.authDatabase='admin'] - Auth database for stats query.
350
365
  * @return {Object|null} Collection statistics or null on error.
351
366
  */
352
- _getMongoStats({ podName, namespace, dbName }) {
367
+ _getMongoStats({ podName, namespace, dbName, user = '', password = '', authDatabase = 'admin' }) {
353
368
  try {
354
369
  logger.info('Getting MongoDB collection statistics', { podName, dbName });
355
370
 
@@ -357,8 +372,11 @@ class UnderpostDB {
357
372
  const script = `db.getSiblingDB('${dbName}').getCollectionNames().map(function(c) { return { collection: c, count: db.getSiblingDB('${dbName}')[c].countDocuments() }; })`;
358
373
 
359
374
  // Execute the script
360
- const command = `sudo kubectl exec -n ${namespace} -i ${podName} -- mongosh --quiet --eval "${script}"`;
361
- const output = shellExec(command, { stdout: true, silent: true });
375
+ const authFlags = user && password
376
+ ? ` --authenticationDatabase ${JSON.stringify(authDatabase)} -u ${JSON.stringify(user)} -p ${JSON.stringify(password)}`
377
+ : '';
378
+ const command = `sudo kubectl exec -n ${namespace} -i ${podName} -- mongosh --quiet${authFlags} --eval "${script}"`;
379
+ const output = shellExec(command, { stdout: true, silent: true, silentOnError: true });
362
380
 
363
381
  if (!output || output.trim() === '') {
364
382
  logger.warn('No collections found or empty output');
@@ -415,7 +433,7 @@ class UnderpostDB {
415
433
  logger.info('Getting MariaDB table statistics', { podName, dbName });
416
434
 
417
435
  const command = `sudo kubectl exec -n ${namespace} -i ${podName} -- mariadb -u ${user} -p${password} ${dbName} -e "SELECT TABLE_NAME as 'table', TABLE_ROWS as 'count' FROM information_schema.TABLES WHERE TABLE_SCHEMA = '${dbName}' ORDER BY TABLE_NAME;" --skip-column-names --batch`;
418
- const output = shellExec(command, { stdout: true, silent: true, disableLog: true });
436
+ const output = shellExec(command, { stdout: true, silent: true, disableLog: true, silentOnError: true });
419
437
 
420
438
  if (!output || output.trim() === '') {
421
439
  logger.warn('No tables found or empty output');
@@ -475,47 +493,7 @@ class UnderpostDB {
475
493
  console.log('='.repeat(70) + '\n');
476
494
  },
477
495
 
478
- /**
479
- * Gets MongoDB primary pod name from replica set status.
480
- * @method getMongoPrimaryPodName
481
- * @memberof UnderpostDB
482
- * @param {Object} [options={}] - Options for getting primary pod.
483
- * @param {string} [options.namespace='default'] - Kubernetes namespace.
484
- * @param {string} [options.podName='mongodb-0'] - Initial pod name to query replica set status.
485
- * @return {string|null} Primary pod name or null if not found.
486
- */
487
- getMongoPrimaryPodName(options = { namespace: 'default', podName: 'mongodb-0' }) {
488
- const { namespace = 'default', podName = 'mongodb-0' } = options;
489
-
490
- try {
491
- logger.info('Checking for MongoDB primary pod', { namespace, checkingPod: podName });
492
-
493
- const command = `sudo kubectl exec -n ${namespace} -i ${podName} -- mongosh --quiet --eval 'rs.status().members.filter(m => m.stateStr=="PRIMARY").map(m=>m.name)'`;
494
- const output = shellExec(command, { stdout: true, silent: true });
495
496
 
496
- if (!output || output.trim() === '') {
497
- logger.warn('No primary pod found in replica set');
498
- return null;
499
- }
500
-
501
- // Parse the output to get the primary pod name
502
- // Output format: [ 'mongodb-0:27017' ] or [ 'mongodb-1.mongodb-service:27017' ] or similar
503
- const match = output.match(/['"]([^'"]+)['"]/);
504
- if (match && match[1]) {
505
- let primaryName = match[1].split(':')[0]; // Extract pod name without port
506
- // Remove service suffix if present (e.g., "mongodb-1.mongodb-service" -> "mongodb-1")
507
- primaryName = primaryName.split('.')[0];
508
- logger.info('Found MongoDB primary pod', { primaryPod: primaryName });
509
- return primaryName;
510
- }
511
-
512
- logger.warn('Could not parse primary pod from replica set status', { output });
513
- return null;
514
- } catch (error) {
515
- logger.error('Failed to get MongoDB primary pod', { error: error.message });
516
- return null;
517
- }
518
- },
519
497
 
520
498
  /**
521
499
  * Main callback: Initiates database backup workflow.
@@ -626,7 +604,13 @@ class UnderpostDB {
626
604
  });
627
605
 
628
606
  if (options.primaryPodEnsure) {
629
- const primaryPodName = Underpost.db.getMongoPrimaryPodName({ namespace, podName: options.primaryPodEnsure });
607
+ const primaryPodName = MongoBootstrap.getPrimaryPodName({
608
+ namespace,
609
+ podName: options.primaryPodEnsure,
610
+ username: process.env.MONGODB_USERNAME || process.env.DB_USER || '',
611
+ password: process.env.MONGODB_PASSWORD || process.env.DB_PASSWORD || '',
612
+ authDatabase: process.env.MONGODB_AUTH_DB || 'admin',
613
+ });
630
614
  if (!primaryPodName) {
631
615
  const baseCommand = options.dev ? 'node bin' : 'underpost';
632
616
  const baseClusterCommand = options.dev ? ' --dev' : '';
@@ -813,7 +797,13 @@ class UnderpostDB {
813
797
  podsToProcess = [];
814
798
  } else {
815
799
  const firstPod = targetPods[0].NAME;
816
- const primaryPodName = Underpost.db.getMongoPrimaryPodName({ namespace, podName: firstPod });
800
+ const primaryPodName = MongoBootstrap.getPrimaryPodName({
801
+ namespace,
802
+ podName: firstPod,
803
+ username: user,
804
+ password,
805
+ authDatabase: 'admin',
806
+ });
817
807
 
818
808
  if (primaryPodName) {
819
809
  const primaryPod = targetPods.find((p) => p.NAME === primaryPodName);
@@ -892,6 +882,9 @@ class UnderpostDB {
892
882
  podName: pod.NAME,
893
883
  namespace,
894
884
  dbName,
885
+ user,
886
+ password,
887
+ authDatabase: 'admin',
895
888
  });
896
889
  if (stats) {
897
890
  Underpost.db._displayStats({ provider, dbName, stats });
@@ -907,6 +900,9 @@ class UnderpostDB {
907
900
  bsonPath,
908
901
  drop: options.drop,
909
902
  preserveUUID: options.preserveUUID,
903
+ user,
904
+ password,
905
+ authDatabase: 'admin',
910
906
  });
911
907
  }
912
908
 
@@ -918,6 +914,9 @@ class UnderpostDB {
918
914
  dbName,
919
915
  outputPath,
920
916
  collections: options.collections,
917
+ user,
918
+ password,
919
+ authDatabase: 'admin',
921
920
  });
922
921
  exportSucceeded = exportSucceeded || success;
923
922
  }
@@ -1044,7 +1043,7 @@ class UnderpostDB {
1044
1043
  const retryDelay = 3000;
1045
1044
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
1046
1045
  try {
1047
- await DataBaseProvider.load({ apis: ['instance', 'cron'], host, path, db });
1046
+ await DataBaseProviderService.load({ apis: ['instance', 'cron'], host, path, db });
1048
1047
  break;
1049
1048
  } catch (err) {
1050
1049
  if (attempt === maxRetries) {
@@ -1058,7 +1057,7 @@ class UnderpostDB {
1058
1057
 
1059
1058
  try {
1060
1059
  /** @type {import('../api/instance/instance.model.js').InstanceModel} */
1061
- const Instance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Instance;
1060
+ const Instance = DataBaseProviderService.getModel('instance', { host, path });
1062
1061
 
1063
1062
  await Instance.deleteMany();
1064
1063
  logger.info('Cleared existing instance metadata');
@@ -1159,10 +1158,10 @@ class UnderpostDB {
1159
1158
 
1160
1159
  const confCron = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
1161
1160
 
1162
- await DataBaseProvider.load({ apis: ['cron'], host, path, db });
1161
+ await DataBaseProviderService.load({ apis: ['cron'], host, path, db });
1163
1162
 
1164
1163
  /** @type {import('../api/cron/cron.model.js').CronModel} */
1165
- const Cron = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Cron;
1164
+ const Cron = DataBaseProviderService.getModel('cron', { host, path });
1166
1165
 
1167
1166
  await Cron.deleteMany();
1168
1167
  logger.info('Cleared existing cron metadata');
@@ -1181,7 +1180,7 @@ class UnderpostDB {
1181
1180
  logger.error('Failed to create cron metadata', { error: error.message });
1182
1181
  }
1183
1182
 
1184
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
1183
+ await DataBaseProviderService.getProvider({ host, path }, 'mongoose').close();
1185
1184
  logger.info('Cluster metadata creation completed');
1186
1185
  } catch (error) {
1187
1186
  logger.error('Cluster metadata creation failed', { error: error.message });
@@ -1274,7 +1273,7 @@ class UnderpostDB {
1274
1273
  let dbProvider;
1275
1274
  for (let attempt = 1; attempt <= 3; attempt++) {
1276
1275
  try {
1277
- dbProvider = await DataBaseProvider.load({ apis, host, path, db });
1276
+ dbProvider = await DataBaseProviderService.load({ apis, host, path, db });
1278
1277
  break;
1279
1278
  } catch (err) {
1280
1279
  if (attempt === 3) throw err;