@underpostnet/underpost 2.97.0 → 2.97.5

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 (78) hide show
  1. package/README.md +2 -2
  2. package/baremetal/commission-workflows.json +33 -3
  3. package/bin/deploy.js +1 -1
  4. package/cli.md +7 -2
  5. package/conf.js +3 -0
  6. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  7. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  8. package/package.json +1 -1
  9. package/packer/scripts/fuse-tar-root +3 -3
  10. package/scripts/disk-clean.sh +23 -23
  11. package/scripts/gpu-diag.sh +2 -2
  12. package/scripts/ip-info.sh +11 -11
  13. package/scripts/maas-upload-boot-resource.sh +1 -1
  14. package/scripts/nvim.sh +1 -1
  15. package/scripts/packer-setup.sh +13 -13
  16. package/scripts/rocky-setup.sh +2 -2
  17. package/scripts/rpmfusion-ffmpeg-setup.sh +4 -4
  18. package/scripts/ssl.sh +7 -7
  19. package/src/api/core/core.service.js +0 -5
  20. package/src/api/default/default.service.js +7 -5
  21. package/src/api/document/document.model.js +30 -1
  22. package/src/api/document/document.router.js +6 -0
  23. package/src/api/document/document.service.js +423 -51
  24. package/src/api/file/file.model.js +112 -4
  25. package/src/api/file/file.ref.json +42 -0
  26. package/src/api/file/file.service.js +380 -32
  27. package/src/api/user/user.model.js +38 -1
  28. package/src/api/user/user.router.js +96 -63
  29. package/src/api/user/user.service.js +81 -48
  30. package/src/cli/baremetal.js +689 -329
  31. package/src/cli/cluster.js +50 -52
  32. package/src/cli/db.js +424 -166
  33. package/src/cli/deploy.js +1 -1
  34. package/src/cli/index.js +12 -1
  35. package/src/cli/lxd.js +3 -3
  36. package/src/cli/repository.js +1 -1
  37. package/src/cli/run.js +2 -1
  38. package/src/cli/ssh.js +10 -10
  39. package/src/client/components/core/Account.js +327 -36
  40. package/src/client/components/core/AgGrid.js +3 -0
  41. package/src/client/components/core/Auth.js +9 -3
  42. package/src/client/components/core/Chat.js +2 -2
  43. package/src/client/components/core/Content.js +159 -78
  44. package/src/client/components/core/Css.js +16 -2
  45. package/src/client/components/core/CssCore.js +16 -12
  46. package/src/client/components/core/FileExplorer.js +115 -8
  47. package/src/client/components/core/Input.js +204 -11
  48. package/src/client/components/core/LogIn.js +42 -20
  49. package/src/client/components/core/Modal.js +257 -177
  50. package/src/client/components/core/Panel.js +324 -27
  51. package/src/client/components/core/PanelForm.js +280 -73
  52. package/src/client/components/core/PublicProfile.js +888 -0
  53. package/src/client/components/core/Router.js +117 -15
  54. package/src/client/components/core/SearchBox.js +1117 -0
  55. package/src/client/components/core/SignUp.js +26 -7
  56. package/src/client/components/core/SocketIo.js +6 -3
  57. package/src/client/components/core/Translate.js +98 -0
  58. package/src/client/components/core/Validator.js +15 -0
  59. package/src/client/components/core/windowGetDimensions.js +6 -6
  60. package/src/client/components/default/MenuDefault.js +59 -12
  61. package/src/client/components/default/RoutesDefault.js +1 -0
  62. package/src/client/services/core/core.service.js +163 -1
  63. package/src/client/services/default/default.management.js +451 -64
  64. package/src/client/services/default/default.service.js +13 -6
  65. package/src/client/services/document/document.service.js +23 -0
  66. package/src/client/services/file/file.service.js +43 -16
  67. package/src/client/services/user/user.service.js +13 -9
  68. package/src/db/DataBaseProvider.js +1 -1
  69. package/src/db/mongo/MongooseDB.js +1 -1
  70. package/src/index.js +1 -1
  71. package/src/mailer/MailerProvider.js +4 -4
  72. package/src/runtime/express/Express.js +2 -1
  73. package/src/runtime/lampp/Lampp.js +2 -2
  74. package/src/server/auth.js +3 -6
  75. package/src/server/data-query.js +449 -0
  76. package/src/server/dns.js +4 -4
  77. package/src/server/object-layer.js +0 -3
  78. package/src/ws/IoInterface.js +2 -2
package/src/cli/db.js CHANGED
@@ -74,16 +74,27 @@ const MAX_BACKUP_RETENTION = 5;
74
74
  * Provides comprehensive database management including import/export, multi-pod targeting,
75
75
  * Git integration, and cluster metadata management.
76
76
  */
77
+ /**
78
+ * UnderpostDB class for managing database operations and backups.
79
+ * @class UnderpostDB
80
+ * @memberof UnderpostDB
81
+ */
77
82
  class UnderpostDB {
83
+ /**
84
+ * Static API object containing all database operation methods.
85
+ * @static
86
+ * @memberof UnderpostDB
87
+ */
78
88
  static API = {
79
89
  /**
80
- * Helper: Gets filtered pods based on criteria
81
- * @private
82
- * @param {Object} criteria - Filter criteria
83
- * @param {string} [criteria.podNames] - Comma-separated pod name patterns
84
- * @param {string} [criteria.namespace='default'] - Kubernetes namespace
85
- * @param {string} [criteria.deployId] - Deployment ID pattern
86
- * @returns {Array<PodInfo>} Filtered pod list
90
+ * Helper: Gets filtered pods based on criteria.
91
+ * @method _getFilteredPods
92
+ * @memberof UnderpostDB
93
+ * @param {Object} criteria - Filter criteria.
94
+ * @param {string} [criteria.podNames] - Comma-separated pod name patterns.
95
+ * @param {string} [criteria.namespace='default'] - Kubernetes namespace.
96
+ * @param {string} [criteria.deployId] - Deployment ID pattern.
97
+ * @return {Array<PodInfo>} Filtered pod list.
87
98
  */
88
99
  _getFilteredPods(criteria = {}) {
89
100
  const { podNames, namespace = 'default', deployId } = criteria;
@@ -113,12 +124,13 @@ class UnderpostDB {
113
124
  },
114
125
 
115
126
  /**
116
- * Helper: Executes kubectl command with error handling
117
- * @private
118
- * @param {string} command - kubectl command to execute
119
- * @param {Object} options - Execution options
120
- * @param {string} [options.context=''] - Command context for logging
121
- * @returns {string|null} Command output or null on error
127
+ * Helper: Executes kubectl command with error handling.
128
+ * @method _executeKubectl
129
+ * @memberof UnderpostDB
130
+ * @param {string} command - kubectl command to execute.
131
+ * @param {Object} [options={}] - Execution options.
132
+ * @param {string} [options.context=''] - Command context for logging.
133
+ * @return {string|null} Command output or null on error.
122
134
  */
123
135
  _executeKubectl(command, options = {}) {
124
136
  const { context = '' } = options;
@@ -133,14 +145,15 @@ class UnderpostDB {
133
145
  },
134
146
 
135
147
  /**
136
- * Helper: Copies file to pod
137
- * @private
138
- * @param {Object} params - Copy parameters
139
- * @param {string} params.sourcePath - Source file path
140
- * @param {string} params.podName - Target pod name
141
- * @param {string} params.namespace - Pod namespace
142
- * @param {string} params.destPath - Destination path in pod
143
- * @returns {boolean} Success status
148
+ * Helper: Copies file to pod.
149
+ * @method _copyToPod
150
+ * @memberof UnderpostDB
151
+ * @param {Object} params - Copy parameters.
152
+ * @param {string} params.sourcePath - Source file path.
153
+ * @param {string} params.podName - Target pod name.
154
+ * @param {string} params.namespace - Pod namespace.
155
+ * @param {string} params.destPath - Destination path in pod.
156
+ * @return {boolean} Success status.
144
157
  */
145
158
  _copyToPod({ sourcePath, podName, namespace, destPath }) {
146
159
  try {
@@ -154,14 +167,15 @@ class UnderpostDB {
154
167
  },
155
168
 
156
169
  /**
157
- * Helper: Copies file from pod
158
- * @private
159
- * @param {Object} params - Copy parameters
160
- * @param {string} params.podName - Source pod name
161
- * @param {string} params.namespace - Pod namespace
162
- * @param {string} params.sourcePath - Source path in pod
163
- * @param {string} params.destPath - Destination file path
164
- * @returns {boolean} Success status
170
+ * Helper: Copies file from pod.
171
+ * @method _copyFromPod
172
+ * @memberof UnderpostDB
173
+ * @param {Object} params - Copy parameters.
174
+ * @param {string} params.podName - Source pod name.
175
+ * @param {string} params.namespace - Pod namespace.
176
+ * @param {string} params.sourcePath - Source path in pod.
177
+ * @param {string} params.destPath - Destination file path.
178
+ * @return {boolean} Success status.
165
179
  */
166
180
  _copyFromPod({ podName, namespace, sourcePath, destPath }) {
167
181
  try {
@@ -175,13 +189,14 @@ class UnderpostDB {
175
189
  },
176
190
 
177
191
  /**
178
- * Helper: Executes command in pod
179
- * @private
180
- * @param {Object} params - Execution parameters
181
- * @param {string} params.podName - Pod name
182
- * @param {string} params.namespace - Pod namespace
183
- * @param {string} params.command - Command to execute
184
- * @returns {string|null} Command output or null
192
+ * Helper: Executes command in pod.
193
+ * @method _execInPod
194
+ * @memberof UnderpostDB
195
+ * @param {Object} params - Execution parameters.
196
+ * @param {string} params.podName - Pod name.
197
+ * @param {string} params.namespace - Pod namespace.
198
+ * @param {string} params.command - Command to execute.
199
+ * @return {string|null} Command output or null.
185
200
  */
186
201
  _execInPod({ podName, namespace, command }) {
187
202
  try {
@@ -194,14 +209,15 @@ class UnderpostDB {
194
209
  },
195
210
 
196
211
  /**
197
- * Helper: Manages Git repository for backups
198
- * @private
199
- * @param {Object} params - Git parameters
200
- * @param {string} params.repoName - Repository name
201
- * @param {string} params.operation - Operation (clone, pull, commit, push)
202
- * @param {string} [params.message=''] - Commit message
203
- * @param {boolean} [params.forceClone=false] - Force remove and re-clone repository
204
- * @returns {boolean} Success status
212
+ * Helper: Manages Git repository for backups.
213
+ * @method _manageGitRepo
214
+ * @memberof UnderpostDB
215
+ * @param {Object} params - Git parameters.
216
+ * @param {string} params.repoName - Repository name.
217
+ * @param {string} params.operation - Operation (clone, pull, commit, push).
218
+ * @param {string} [params.message=''] - Commit message.
219
+ * @param {boolean} [params.forceClone=false] - Force remove and re-clone repository.
220
+ * @return {boolean} Success status.
205
221
  */
206
222
  _manageGitRepo({ repoName, operation, message = '', forceClone = false }) {
207
223
  try {
@@ -263,12 +279,13 @@ class UnderpostDB {
263
279
  },
264
280
 
265
281
  /**
266
- * Helper: Manages backup timestamps and cleanup
267
- * @private
268
- * @param {string} backupPath - Backup directory path
269
- * @param {number} newTimestamp - New backup timestamp
270
- * @param {boolean} shouldCleanup - Whether to cleanup old backups
271
- * @returns {Object} Backup info with current and removed timestamps
282
+ * Helper: Manages backup timestamps and cleanup.
283
+ * @method _manageBackupTimestamps
284
+ * @memberof UnderpostDB
285
+ * @param {string} backupPath - Backup directory path.
286
+ * @param {number} newTimestamp - New backup timestamp.
287
+ * @param {boolean} shouldCleanup - Whether to cleanup old backups.
288
+ * @return {Object} Backup info with current and removed timestamps.
272
289
  */
273
290
  _manageBackupTimestamps(backupPath, newTimestamp, shouldCleanup) {
274
291
  try {
@@ -311,16 +328,17 @@ class UnderpostDB {
311
328
  },
312
329
 
313
330
  /**
314
- * Helper: Performs MariaDB import operation
315
- * @private
316
- * @param {Object} params - Import parameters
317
- * @param {PodInfo} params.pod - Target pod
318
- * @param {string} params.namespace - Namespace
319
- * @param {string} params.dbName - Database name
320
- * @param {string} params.user - Database user
321
- * @param {string} params.password - Database password
322
- * @param {string} params.sqlPath - SQL file path
323
- * @returns {boolean} Success status
331
+ * Helper: Performs MariaDB import operation.
332
+ * @method _importMariaDB
333
+ * @memberof UnderpostDB
334
+ * @param {Object} params - Import parameters.
335
+ * @param {PodInfo} params.pod - Target pod.
336
+ * @param {string} params.namespace - Namespace.
337
+ * @param {string} params.dbName - Database name.
338
+ * @param {string} params.user - Database user.
339
+ * @param {string} params.password - Database password.
340
+ * @param {string} params.sqlPath - SQL file path.
341
+ * @return {boolean} Success status.
324
342
  */
325
343
  _importMariaDB({ pod, namespace, dbName, user, password, sqlPath }) {
326
344
  try {
@@ -367,16 +385,17 @@ class UnderpostDB {
367
385
  },
368
386
 
369
387
  /**
370
- * Helper: Performs MariaDB export operation
371
- * @private
372
- * @param {Object} params - Export parameters
373
- * @param {PodInfo} params.pod - Source pod
374
- * @param {string} params.namespace - Namespace
375
- * @param {string} params.dbName - Database name
376
- * @param {string} params.user - Database user
377
- * @param {string} params.password - Database password
378
- * @param {string} params.outputPath - Output file path
379
- * @returns {boolean} Success status
388
+ * Helper: Performs MariaDB export operation.
389
+ * @method _exportMariaDB
390
+ * @memberof UnderpostDB
391
+ * @param {Object} params - Export parameters.
392
+ * @param {PodInfo} params.pod - Source pod.
393
+ * @param {string} params.namespace - Namespace.
394
+ * @param {string} params.dbName - Database name.
395
+ * @param {string} params.user - Database user.
396
+ * @param {string} params.password - Database password.
397
+ * @param {string} params.outputPath - Output file path.
398
+ * @return {Promise<boolean>} A promise that resolves with success status.
380
399
  */
381
400
  async _exportMariaDB({ pod, namespace, dbName, user, password, outputPath }) {
382
401
  try {
@@ -422,16 +441,17 @@ class UnderpostDB {
422
441
  },
423
442
 
424
443
  /**
425
- * Helper: Performs MongoDB import operation
426
- * @private
427
- * @param {Object} params - Import parameters
428
- * @param {PodInfo} params.pod - Target pod
429
- * @param {string} params.namespace - Namespace
430
- * @param {string} params.dbName - Database name
431
- * @param {string} params.bsonPath - BSON directory path
432
- * @param {boolean} params.drop - Whether to drop existing database
433
- * @param {boolean} params.preserveUUID - Whether to preserve UUIDs
434
- * @returns {boolean} Success status
444
+ * Helper: Performs MongoDB import operation.
445
+ * @method _importMongoDB
446
+ * @memberof UnderpostDB
447
+ * @param {Object} params - Import parameters.
448
+ * @param {PodInfo} params.pod - Target pod.
449
+ * @param {string} params.namespace - Namespace.
450
+ * @param {string} params.dbName - Database name.
451
+ * @param {string} params.bsonPath - BSON directory path.
452
+ * @param {boolean} params.drop - Whether to drop existing database.
453
+ * @param {boolean} params.preserveUUID - Whether to preserve UUIDs.
454
+ * @return {boolean} Success status.
435
455
  */
436
456
  _importMongoDB({ pod, namespace, dbName, bsonPath, drop, preserveUUID }) {
437
457
  try {
@@ -474,15 +494,16 @@ class UnderpostDB {
474
494
  },
475
495
 
476
496
  /**
477
- * Helper: Performs MongoDB export operation
478
- * @private
479
- * @param {Object} params - Export parameters
480
- * @param {PodInfo} params.pod - Source pod
481
- * @param {string} params.namespace - Namespace
482
- * @param {string} params.dbName - Database name
483
- * @param {string} params.outputPath - Output directory path
484
- * @param {string} [params.collections=''] - Comma-separated collection list
485
- * @returns {boolean} Success status
497
+ * Helper: Performs MongoDB export operation.
498
+ * @method _exportMongoDB
499
+ * @memberof UnderpostDB
500
+ * @param {Object} params - Export parameters.
501
+ * @param {PodInfo} params.pod - Source pod.
502
+ * @param {string} params.namespace - Namespace.
503
+ * @param {string} params.dbName - Database name.
504
+ * @param {string} params.outputPath - Output directory path.
505
+ * @param {string} [params.collections=''] - Comma-separated collection list.
506
+ * @return {boolean} Success status.
486
507
  */
487
508
  _exportMongoDB({ pod, namespace, dbName, outputPath, collections = '' }) {
488
509
  try {
@@ -531,13 +552,14 @@ class UnderpostDB {
531
552
  },
532
553
 
533
554
  /**
534
- * Helper: Gets MongoDB collection statistics
535
- * @private
536
- * @param {Object} params - Parameters
537
- * @param {string} params.podName - Pod name
538
- * @param {string} params.namespace - Namespace
539
- * @param {string} params.dbName - Database name
540
- * @returns {Object|null} Collection statistics or null on error
555
+ * Helper: Gets MongoDB collection statistics.
556
+ * @method _getMongoStats
557
+ * @memberof UnderpostDB
558
+ * @param {Object} params - Parameters.
559
+ * @param {string} params.podName - Pod name.
560
+ * @param {string} params.namespace - Namespace.
561
+ * @param {string} params.dbName - Database name.
562
+ * @return {Object|null} Collection statistics or null on error.
541
563
  */
542
564
  _getMongoStats({ podName, namespace, dbName }) {
543
565
  try {
@@ -589,15 +611,16 @@ class UnderpostDB {
589
611
  },
590
612
 
591
613
  /**
592
- * Helper: Gets MariaDB table statistics
593
- * @private
594
- * @param {Object} params - Parameters
595
- * @param {string} params.podName - Pod name
596
- * @param {string} params.namespace - Namespace
597
- * @param {string} params.dbName - Database name
598
- * @param {string} params.user - Database user
599
- * @param {string} params.password - Database password
600
- * @returns {Object|null} Table statistics or null on error
614
+ * Helper: Gets MariaDB table statistics.
615
+ * @method _getMariaDBStats
616
+ * @memberof UnderpostDB
617
+ * @param {Object} params - Parameters.
618
+ * @param {string} params.podName - Pod name.
619
+ * @param {string} params.namespace - Namespace.
620
+ * @param {string} params.dbName - Database name.
621
+ * @param {string} params.user - Database user.
622
+ * @param {string} params.password - Database password.
623
+ * @return {Object|null} Table statistics or null on error.
601
624
  */
602
625
  _getMariaDBStats({ podName, namespace, dbName, user, password }) {
603
626
  try {
@@ -627,12 +650,14 @@ class UnderpostDB {
627
650
  },
628
651
 
629
652
  /**
630
- * Helper: Displays database statistics in table format
631
- * @private
632
- * @param {Object} params - Parameters
633
- * @param {string} params.provider - Database provider
634
- * @param {string} params.dbName - Database name
635
- * @param {Array<Object>} params.stats - Statistics array
653
+ * Helper: Displays database statistics in table format.
654
+ * @method _displayStats
655
+ * @memberof UnderpostDB
656
+ * @param {Object} params - Parameters.
657
+ * @param {string} params.provider - Database provider.
658
+ * @param {string} params.dbName - Database name.
659
+ * @param {Array<Object>} params.stats - Statistics array.
660
+ * @return {void}
636
661
  */
637
662
  _displayStats({ provider, dbName, stats }) {
638
663
  if (!stats || stats.length === 0) {
@@ -663,13 +688,13 @@ class UnderpostDB {
663
688
  },
664
689
 
665
690
  /**
666
- * Public API: Gets MongoDB primary pod name
667
- * @public
668
- * @param {Object} options - Options for getting primary pod
669
- * @param {string} [options.namespace='default'] - Kubernetes namespace
670
- * @param {string} [options.podName='mongodb-0'] - Initial pod name to query replica set status
671
- * @returns {string|null} Primary pod name or null if not found
691
+ * Gets MongoDB primary pod name from replica set status.
692
+ * @method getMongoPrimaryPodName
672
693
  * @memberof UnderpostDB
694
+ * @param {Object} [options={}] - Options for getting primary pod.
695
+ * @param {string} [options.namespace='default'] - Kubernetes namespace.
696
+ * @param {string} [options.podName='mongodb-0'] - Initial pod name to query replica set status.
697
+ * @return {string|null} Primary pod name or null if not found.
673
698
  */
674
699
  getMongoPrimaryPodName(options = { namespace: 'default', podName: 'mongodb-0' }) {
675
700
  const { namespace = 'default', podName = 'mongodb-0' } = options;
@@ -705,36 +730,38 @@ class UnderpostDB {
705
730
  },
706
731
 
707
732
  /**
708
- * Main callback: Initiates database backup workflow
709
- * @method callback
710
- * @description Orchestrates the backup process for multiple deployments, handling
733
+ * Main callback: Initiates database backup workflow.
734
+ * Orchestrates the backup process for multiple deployments, handling
711
735
  * database connections, backup storage, and optional Git integration for version control.
712
736
  * Supports targeting multiple specific pods, nodes, and namespaces with advanced filtering.
713
- * @param {string} [deployList='default'] - Comma-separated list of deployment IDs
714
- * @param {Object} options - Backup options
715
- * @param {boolean} [options.import=false] - Whether to perform import operation
716
- * @param {boolean} [options.export=false] - Whether to perform export operation
717
- * @param {string} [options.podName=''] - Comma-separated pod name patterns to target
718
- * @param {string} [options.ns='default'] - Kubernetes namespace
719
- * @param {string} [options.collections=''] - Comma-separated MongoDB collections for export
720
- * @param {string} [options.outPath=''] - Output path for backups
721
- * @param {boolean} [options.drop=false] - Whether to drop existing database on import
722
- * @param {boolean} [options.preserveUUID=false] - Whether to preserve UUIDs on MongoDB import
723
- * @param {boolean} [options.git=false] - Whether to use Git for backup versioning
724
- * @param {string} [options.hosts=''] - Comma-separated list of hosts to filter databases
725
- * @param {string} [options.paths=''] - Comma-separated list of paths to filter databases
726
- * @param {boolean} [options.allPods=false] - Whether to target all pods in deployment
727
- * @param {boolean} [options.primaryPod=false] - Whether to target MongoDB primary pod only
728
- * @param {string} [options.primaryPodEnsure=''] - Pod name to ensure MongoDB primary pod is running
729
- * @param {boolean} [options.stats=false] - Whether to display database statistics
730
- * @param {number} [options.macroRollbackExport=1] - Number of commits to rollback in macro export
731
- * @param {boolean} [options.forceClone=false] - Whether to force re-clone Git repository
732
- * @param {boolean} [options.dev=false] - Development mode flag
733
- * @param {boolean} [options.k3s=false] - k3s cluster flag
734
- * @param {boolean} [options.kubeadm=false] - kubeadm cluster flag
735
- * @param {boolean} [options.kind=false] - kind cluster flag
736
- * @returns {Promise<void>} Resolves when operation is complete
737
+ * @method callback
737
738
  * @memberof UnderpostDB
739
+ * @param {string} [deployList='default'] - Comma-separated list of deployment IDs.
740
+ * @param {Object} [options={}] - Backup options.
741
+ * @param {boolean} [options.import=false] - Whether to perform import operation.
742
+ * @param {boolean} [options.export=false] - Whether to perform export operation.
743
+ * @param {string} [options.podName=''] - Comma-separated pod name patterns to target.
744
+ * @param {string} [options.ns='default'] - Kubernetes namespace.
745
+ * @param {string} [options.collections=''] - Comma-separated MongoDB collections for export.
746
+ * @param {string} [options.outPath=''] - Output path for backups.
747
+ * @param {boolean} [options.drop=false] - Whether to drop existing database on import.
748
+ * @param {boolean} [options.preserveUUID=false] - Whether to preserve UUIDs on MongoDB import.
749
+ * @param {boolean} [options.git=false] - Whether to use Git for backup versioning.
750
+ * @param {string} [options.hosts=''] - Comma-separated list of hosts to filter databases.
751
+ * @param {string} [options.paths=''] - Comma-separated list of paths to filter databases.
752
+ * @param {boolean} [options.allPods=false] - Whether to target all pods in deployment.
753
+ * @param {boolean} [options.primaryPod=false] - Whether to target MongoDB primary pod only.
754
+ * @param {string} [options.primaryPodEnsure=''] - Pod name to ensure MongoDB primary pod is running.
755
+ * @param {boolean} [options.stats=false] - Whether to display database statistics.
756
+ * @param {number} [options.macroRollbackExport=1] - Number of commits to rollback in macro export.
757
+ * @param {boolean} [options.forceClone=false] - Whether to force re-clone Git repository.
758
+ * @param {boolean} [options.cleanFsCollection=false] - Clean orphaned File documents flag.
759
+ * @param {boolean} [options.cleanFsDryRun=false] - Dry run mode flag (use with cleanFsCollection).
760
+ * @param {boolean} [options.dev=false] - Development mode flag.
761
+ * @param {boolean} [options.k3s=false] - k3s cluster flag.
762
+ * @param {boolean} [options.kubeadm=false] - kubeadm cluster flag.
763
+ * @param {boolean} [options.kind=false] - kind cluster flag.
764
+ * @return {Promise<void>} Resolves when operation is complete.
738
765
  */
739
766
  async callback(
740
767
  deployList = 'default',
@@ -756,6 +783,8 @@ class UnderpostDB {
756
783
  stats: false,
757
784
  macroRollbackExport: 1,
758
785
  forceClone: false,
786
+ cleanFsCollection: false,
787
+ cleanFsDryRun: false,
759
788
  dev: false,
760
789
  k3s: false,
761
790
  kubeadm: false,
@@ -767,6 +796,17 @@ class UnderpostDB {
767
796
 
768
797
  if (deployList === 'dd') deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
769
798
 
799
+ // Handle clean-fs-collection operation
800
+ if (options.cleanFsCollection || options.cleanFsDryRun) {
801
+ logger.info('Starting File collection cleanup operation', { deployList });
802
+ await UnderpostDB.API.cleanFsCollection(deployList, {
803
+ hosts: options.hosts,
804
+ paths: options.paths,
805
+ dryRun: options.cleanFsDryRun,
806
+ });
807
+ return;
808
+ }
809
+
770
810
  logger.info('Starting database operation', {
771
811
  deployList,
772
812
  namespace,
@@ -1100,16 +1140,16 @@ class UnderpostDB {
1100
1140
  },
1101
1141
 
1102
1142
  /**
1103
- * Creates cluster metadata for the specified deployment
1104
- * @method clusterMetadataFactory
1105
- * @description Loads database configuration and initializes cluster metadata including
1143
+ * Creates cluster metadata for the specified deployment.
1144
+ * Loads database configuration and initializes cluster metadata including
1106
1145
  * instances and cron jobs. This method populates the database with deployment information.
1107
- * @param {string} [deployId=process.env.DEFAULT_DEPLOY_ID] - The deployment ID
1108
- * @param {string} [host=process.env.DEFAULT_DEPLOY_HOST] - The host identifier
1109
- * @param {string} [path=process.env.DEFAULT_DEPLOY_PATH] - The path identifier
1110
- * @returns {Promise<void>}
1146
+ * @method clusterMetadataFactory
1111
1147
  * @memberof UnderpostDB
1112
- * @throws {Error} If database configuration is invalid or connection fails
1148
+ * @param {string} [deployId=process.env.DEFAULT_DEPLOY_ID] - The deployment ID.
1149
+ * @param {string} [host=process.env.DEFAULT_DEPLOY_HOST] - The host identifier.
1150
+ * @param {string} [path=process.env.DEFAULT_DEPLOY_PATH] - The path identifier.
1151
+ * @return {Promise<void>} Resolves when metadata creation is complete.
1152
+ * @throws {Error} If database configuration is invalid or connection fails.
1113
1153
  */
1114
1154
  async clusterMetadataFactory(
1115
1155
  deployId = process.env.DEFAULT_DEPLOY_ID,
@@ -1272,22 +1312,240 @@ class UnderpostDB {
1272
1312
  },
1273
1313
 
1274
1314
  /**
1275
- * Handles backup of cluster metadata
1276
- * @method clusterMetadataBackupCallback
1277
- * @description Orchestrates backup and restore operations for cluster metadata including
1315
+ * Cleans orphaned File references from database collections.
1316
+ * Iterates over all deploy-ids and checks if File documents are actually referenced
1317
+ * by other collections. Removes File documents that are not referenced anywhere.
1318
+ * @method cleanFsCollection
1319
+ * @memberof UnderpostDB
1320
+ * @param {string} [deployList='dd'] - Comma-separated list of deployment IDs.
1321
+ * @param {Object} [options={}] - Clean operation options.
1322
+ * @param {string} [options.hosts=''] - Comma-separated list of hosts to filter.
1323
+ * @param {string} [options.paths=''] - Comma-separated list of paths to filter.
1324
+ * @param {boolean} [options.dryRun=false] - If true, only reports what would be deleted.
1325
+ * @return {Promise<void>} Resolves when clean operation is complete.
1326
+ */
1327
+ async cleanFsCollection(
1328
+ deployList = 'dd',
1329
+ options = {
1330
+ hosts: '',
1331
+ paths: '',
1332
+ dryRun: false,
1333
+ },
1334
+ ) {
1335
+ if (deployList === 'dd') deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
1336
+
1337
+ logger.info('Starting File collection cleanup', { deployList, options });
1338
+
1339
+ // Load file.ref.json to know which models reference File
1340
+ const fileRefPath = './src/api/file/file.ref.json';
1341
+ if (!fs.existsSync(fileRefPath)) {
1342
+ logger.error('file.ref.json not found', { path: fileRefPath });
1343
+ return;
1344
+ }
1345
+
1346
+ const fileRefData = JSON.parse(fs.readFileSync(fileRefPath, 'utf8'));
1347
+ logger.info('Loaded file reference configuration', { apis: fileRefData.length });
1348
+
1349
+ // Filter hosts and paths if specified
1350
+ const filterHosts = options.hosts ? options.hosts.split(',').map((h) => h.trim()) : [];
1351
+ const filterPaths = options.paths ? options.paths.split(',').map((p) => p.trim()) : [];
1352
+
1353
+ // Track all connections to close them at the end
1354
+ const connectionsToClose = [];
1355
+
1356
+ for (const _deployId of deployList.split(',')) {
1357
+ const deployId = _deployId.trim();
1358
+ if (!deployId) continue;
1359
+
1360
+ logger.info('Processing deployment for File cleanup', { deployId });
1361
+
1362
+ // Load server configuration
1363
+ const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
1364
+ if (!fs.existsSync(confServerPath)) {
1365
+ logger.error('Configuration file not found', { path: confServerPath });
1366
+ continue;
1367
+ }
1368
+
1369
+ const confServer = JSON.parse(fs.readFileSync(confServerPath, 'utf8'));
1370
+
1371
+ // Process each host+path combination
1372
+ for (const host of Object.keys(confServer)) {
1373
+ if (filterHosts.length > 0 && !filterHosts.includes(host)) continue;
1374
+
1375
+ for (const path of Object.keys(confServer[host])) {
1376
+ if (filterPaths.length > 0 && !filterPaths.includes(path)) continue;
1377
+
1378
+ const { db, apis } = confServer[host][path];
1379
+ if (!db || !apis) continue;
1380
+
1381
+ // Check if 'file' api is in the apis list
1382
+ if (!apis.includes('file')) {
1383
+ logger.info('Skipping - no file api in configuration', { host, path });
1384
+ continue;
1385
+ }
1386
+
1387
+ // logger.info('Processing host+path with file api', { host, path, db: db.name });
1388
+
1389
+ try {
1390
+ // Connect to database
1391
+ const dbProvider = await DataBaseProvider.load({ apis, host, path, db });
1392
+ if (!dbProvider || !dbProvider.models) {
1393
+ logger.error('Failed to load database provider', { host, path });
1394
+ continue;
1395
+ }
1396
+
1397
+ const { models } = dbProvider;
1398
+
1399
+ // Track this connection for cleanup
1400
+ connectionsToClose.push({ host, path, dbProvider });
1401
+
1402
+ // Check if File model exists
1403
+ if (!models.File) {
1404
+ logger.warn('File model not loaded', { host, path });
1405
+ continue;
1406
+ }
1407
+
1408
+ // Get all File documents
1409
+ const allFiles = await models.File.find({}, '_id').lean();
1410
+ logger.info('Found File documents', { count: allFiles.length, host, path });
1411
+
1412
+ if (allFiles.length === 0) continue;
1413
+
1414
+ // Track which File IDs are referenced
1415
+ const referencedFileIds = new Set();
1416
+
1417
+ // Check each API from file.ref.json
1418
+ for (const refConfig of fileRefData) {
1419
+ const { api, model: modelFields } = refConfig;
1420
+
1421
+ // Check if this API is loaded in current context
1422
+ const modelName = api
1423
+ .split('-')
1424
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1425
+ .join('');
1426
+ const Model = models[modelName];
1427
+
1428
+ if (!Model) {
1429
+ logger.debug('Model not loaded in current context', { api, modelName, host, path });
1430
+ continue;
1431
+ }
1432
+
1433
+ logger.info('Checking references in model', { api, modelName });
1434
+
1435
+ // Helper function to recursively check field references
1436
+ const checkFieldReferences = async (fieldPath, fieldConfig) => {
1437
+ for (const [fieldName, fieldValue] of Object.entries(fieldConfig)) {
1438
+ const currentPath = fieldPath ? `${fieldPath}.${fieldName}` : fieldName;
1439
+
1440
+ if (fieldValue === true) {
1441
+ // This is a File reference field
1442
+ const query = {};
1443
+ query[currentPath] = { $exists: true, $ne: null };
1444
+
1445
+ const docs = await Model.find(query, currentPath).lean();
1446
+
1447
+ for (const doc of docs) {
1448
+ // Navigate to the nested field
1449
+ const parts = currentPath.split('.');
1450
+ let value = doc;
1451
+ for (const part of parts) {
1452
+ value = value?.[part];
1453
+ }
1454
+
1455
+ if (value) {
1456
+ if (Array.isArray(value)) {
1457
+ value.forEach((id) => id && referencedFileIds.add(id.toString()));
1458
+ } else {
1459
+ referencedFileIds.add(value.toString());
1460
+ }
1461
+ }
1462
+ }
1463
+
1464
+ logger.info('Found references', {
1465
+ model: modelName,
1466
+ field: currentPath,
1467
+ count: docs.length,
1468
+ });
1469
+ } else if (typeof fieldValue === 'object') {
1470
+ // Nested object, recurse
1471
+ await checkFieldReferences(currentPath, fieldValue);
1472
+ }
1473
+ }
1474
+ };
1475
+
1476
+ await checkFieldReferences('', modelFields);
1477
+ }
1478
+
1479
+ logger.info('Total referenced File IDs', { count: referencedFileIds.size, host, path });
1480
+
1481
+ // Find orphaned files
1482
+ const orphanedFiles = allFiles.filter((file) => !referencedFileIds.has(file._id.toString()));
1483
+
1484
+ if (orphanedFiles.length === 0) {
1485
+ logger.info('No orphaned files found', { host, path });
1486
+ } else {
1487
+ logger.info('Found orphaned files', { count: orphanedFiles.length, host, path });
1488
+
1489
+ if (options.dryRun) {
1490
+ logger.info('Dry run - would delete files', {
1491
+ count: orphanedFiles.length,
1492
+ ids: orphanedFiles.map((f) => f._id.toString()),
1493
+ });
1494
+ } else {
1495
+ const orphanedIds = orphanedFiles.map((f) => f._id);
1496
+ const deleteResult = await models.File.deleteMany({ _id: { $in: orphanedIds } });
1497
+ logger.info('Deleted orphaned files', {
1498
+ deletedCount: deleteResult.deletedCount,
1499
+ host,
1500
+ path,
1501
+ });
1502
+ }
1503
+ }
1504
+ } catch (error) {
1505
+ logger.error('Error processing host+path', {
1506
+ host,
1507
+ path,
1508
+ error: error.message,
1509
+ stack: error.stack,
1510
+ });
1511
+ }
1512
+ }
1513
+ }
1514
+ }
1515
+
1516
+ // Close all connections
1517
+ logger.info('Closing all database connections', { count: connectionsToClose.length });
1518
+ for (const { host, path, dbProvider } of connectionsToClose) {
1519
+ try {
1520
+ if (dbProvider && dbProvider.close) {
1521
+ await dbProvider.close();
1522
+ logger.info('Connection closed', { host, path });
1523
+ }
1524
+ } catch (error) {
1525
+ logger.error('Error closing connection', { host, path, error: error.message });
1526
+ }
1527
+ }
1528
+
1529
+ logger.info('File collection cleanup completed');
1530
+ },
1531
+
1532
+ /**
1533
+ * Handles backup of cluster metadata.
1534
+ * Orchestrates backup and restore operations for cluster metadata including
1278
1535
  * instances and cron jobs. Supports import/export and metadata generation.
1279
- * @param {string} [deployId=process.env.DEFAULT_DEPLOY_ID] - The deployment ID
1280
- * @param {string} [host=process.env.DEFAULT_DEPLOY_HOST] - The host identifier
1281
- * @param {string} [path=process.env.DEFAULT_DEPLOY_PATH] - The path identifier
1282
- * @param {Object} [options] - Backup operation options
1283
- * @param {boolean} [options.generate=false] - Generate cluster metadata
1284
- * @param {boolean} [options.itc=false] - Execute in container context
1285
- * @param {boolean} [options.import=false] - Import metadata from backup
1286
- * @param {boolean} [options.export=false] - Export metadata to backup
1287
- * @param {boolean} [options.instances=false] - Process instances collection
1288
- * @param {boolean} [options.crons=false] - Process crons collection
1289
- * @returns {void}
1536
+ * @method clusterMetadataBackupCallback
1290
1537
  * @memberof UnderpostDB
1538
+ * @param {string} [deployId=process.env.DEFAULT_DEPLOY_ID] - The deployment ID.
1539
+ * @param {string} [host=process.env.DEFAULT_DEPLOY_HOST] - The host identifier.
1540
+ * @param {string} [path=process.env.DEFAULT_DEPLOY_PATH] - The path identifier.
1541
+ * @param {Object} [options={}] - Backup operation options.
1542
+ * @param {boolean} [options.generate=false] - Generate cluster metadata.
1543
+ * @param {boolean} [options.itc=false] - Execute in container context.
1544
+ * @param {boolean} [options.import=false] - Import metadata from backup.
1545
+ * @param {boolean} [options.export=false] - Export metadata to backup.
1546
+ * @param {boolean} [options.instances=false] - Process instances collection.
1547
+ * @param {boolean} [options.crons=false] - Process crons collection.
1548
+ * @return {Promise<void>} Resolves when backup operation is complete.
1291
1549
  */
1292
1550
  async clusterMetadataBackupCallback(
1293
1551
  deployId = process.env.DEFAULT_DEPLOY_ID,