underpost 3.1.2 → 3.2.0

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 (98) hide show
  1. package/.env.example +0 -2
  2. package/.github/workflows/ghpkg.ci.yml +4 -4
  3. package/.github/workflows/npmpkg.ci.yml +38 -7
  4. package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -4
  5. package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
  6. package/.github/workflows/release.cd.yml +4 -4
  7. package/CHANGELOG.md +365 -1
  8. package/CLI-HELP.md +55 -3
  9. package/README.md +7 -3
  10. package/bin/build.js +18 -12
  11. package/bin/deploy.js +205 -225
  12. package/bin/file.js +3 -0
  13. package/conf.js +4 -10
  14. package/jsdoc.json +1 -1
  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 +72 -50
  19. package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
  20. package/manifests/deployment/playwright/deployment.yaml +1 -1
  21. package/nodemon.json +1 -1
  22. package/package.json +21 -14
  23. package/scripts/ports-ls.sh +2 -0
  24. package/scripts/rhel-grpc-setup.sh +56 -0
  25. package/src/api/file/file.ref.json +18 -0
  26. package/src/api/user/user.service.js +8 -7
  27. package/src/cli/cluster.js +7 -7
  28. package/src/cli/db.js +76 -242
  29. package/src/cli/deploy.js +104 -65
  30. package/src/cli/env.js +1 -0
  31. package/src/cli/fs.js +2 -1
  32. package/src/cli/index.js +50 -1
  33. package/src/cli/kubectl.js +211 -0
  34. package/src/cli/release.js +284 -0
  35. package/src/cli/repository.js +328 -112
  36. package/src/cli/run.js +283 -69
  37. package/src/cli/test.js +3 -3
  38. package/src/client/Default.index.js +3 -4
  39. package/src/client/components/core/Alert.js +2 -2
  40. package/src/client/components/core/AppStore.js +69 -0
  41. package/src/client/components/core/CalendarCore.js +2 -2
  42. package/src/client/components/core/Docs.js +9 -2
  43. package/src/client/components/core/DropDown.js +129 -17
  44. package/src/client/components/core/Keyboard.js +2 -2
  45. package/src/client/components/core/LogIn.js +2 -2
  46. package/src/client/components/core/LogOut.js +2 -2
  47. package/src/client/components/core/Modal.js +0 -1
  48. package/src/client/components/core/Panel.js +0 -1
  49. package/src/client/components/core/PanelForm.js +19 -19
  50. package/src/client/components/core/RichText.js +1 -2
  51. package/src/client/components/core/SocketIo.js +82 -29
  52. package/src/client/components/core/SocketIoHandler.js +75 -0
  53. package/src/client/components/core/Stream.js +143 -95
  54. package/src/client/components/core/Webhook.js +40 -7
  55. package/src/client/components/default/AppStoreDefault.js +5 -0
  56. package/src/client/components/default/LogInDefault.js +3 -3
  57. package/src/client/components/default/LogOutDefault.js +2 -2
  58. package/src/client/components/default/MenuDefault.js +5 -5
  59. package/src/client/components/default/SocketIoDefault.js +3 -51
  60. package/src/client/services/core/core.service.js +20 -8
  61. package/src/client/services/user/user.management.js +2 -2
  62. package/src/client/ssr/body/404.js +15 -11
  63. package/src/client/ssr/body/500.js +15 -11
  64. package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
  65. package/src/client/ssr/offline/NoNetworkConnection.js +11 -10
  66. package/src/client/ssr/pages/Test.js +11 -10
  67. package/src/index.js +24 -1
  68. package/src/runtime/express/Express.js +26 -9
  69. package/src/runtime/lampp/Dockerfile +9 -2
  70. package/src/runtime/lampp/Lampp.js +4 -3
  71. package/src/runtime/wp/Dockerfile +64 -0
  72. package/src/runtime/wp/Wp.js +497 -0
  73. package/src/server/auth.js +30 -6
  74. package/src/server/backup.js +19 -1
  75. package/src/server/client-build-docs.js +51 -110
  76. package/src/server/client-build.js +55 -64
  77. package/src/server/client-formatted.js +109 -57
  78. package/src/server/conf.js +19 -15
  79. package/src/server/ipfs-client.js +24 -1
  80. package/src/server/peer.js +8 -0
  81. package/src/server/runtime.js +25 -1
  82. package/src/server/start.js +21 -8
  83. package/src/ws/IoInterface.js +1 -10
  84. package/src/ws/IoServer.js +14 -33
  85. package/src/ws/core/channels/core.ws.chat.js +65 -20
  86. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  87. package/src/ws/core/channels/core.ws.stream.js +90 -31
  88. package/src/ws/core/core.ws.connection.js +12 -33
  89. package/src/ws/core/core.ws.emit.js +10 -26
  90. package/src/ws/core/core.ws.server.js +25 -58
  91. package/src/ws/default/channels/default.ws.main.js +53 -12
  92. package/src/ws/default/default.ws.connection.js +26 -13
  93. package/src/ws/default/default.ws.server.js +30 -12
  94. package/src/client/components/default/ElementsDefault.js +0 -38
  95. package/src/ws/core/management/core.ws.chat.js +0 -8
  96. package/src/ws/core/management/core.ws.mailer.js +0 -16
  97. package/src/ws/core/management/core.ws.stream.js +0 -8
  98. package/src/ws/default/management/default.ws.main.js +0 -8
package/src/cli/db.js CHANGED
@@ -13,23 +13,9 @@ import fs from 'fs-extra';
13
13
  import { DataBaseProvider } from '../db/DataBaseProvider.js';
14
14
  import { loadReplicas, pathPortAssignmentFactory, loadCronDeployEnv } from '../server/conf.js';
15
15
  import Underpost from '../index.js';
16
+ import { timer } from '../client/components/core/CommonJs.js';
16
17
  const logger = loggerFactory(import.meta);
17
18
 
18
- /**
19
- * Redacts credentials from shell command strings before logging.
20
- * Masks passwords in `-p<password>`, `--password=<password>`, and `-P <password>` patterns.
21
- * @param {string} cmd - The raw command string.
22
- * @memberof UnderpostDB
23
- * @returns {string} The command with credentials replaced by `***`.
24
- */
25
- const sanitizeCommand = (cmd) => {
26
- if (typeof cmd !== 'string') return cmd;
27
- return cmd
28
- .replace(/-p['"]?[^\s'"]+/g, '-p***')
29
- .replace(/--password=['"]?[^\s'"]+/g, '--password=***')
30
- .replace(/-P\s+['"]?[^\s'"]+/g, '-P ***');
31
- };
32
-
33
19
  /**
34
20
  * Constants for database operations
35
21
  * @constant {number} MAX_BACKUP_RETENTION - Maximum number of backups to retain
@@ -98,132 +84,6 @@ class UnderpostDB {
98
84
  * @memberof UnderpostDB
99
85
  */
100
86
  static API = {
101
- /**
102
- * Helper: Gets filtered pods based on criteria.
103
- * @method _getFilteredPods
104
- * @memberof UnderpostDB
105
- * @param {Object} criteria - Filter criteria.
106
- * @param {string} [criteria.podNames] - Comma-separated pod name patterns.
107
- * @param {string} [criteria.namespace='default'] - Kubernetes namespace.
108
- * @param {string} [criteria.deployId] - Deployment ID pattern.
109
- * @return {Array<PodInfo>} Filtered pod list.
110
- */
111
- _getFilteredPods(criteria = {}) {
112
- const { podNames, namespace = 'default', deployId } = criteria;
113
-
114
- try {
115
- // Get all pods using Underpost.deploy.get
116
- let pods = Underpost.deploy.get(deployId || '', 'pods', namespace);
117
-
118
- // Filter by pod names if specified
119
- if (podNames) {
120
- const patterns = podNames.split(',').map((p) => p.trim());
121
- pods = pods.filter((pod) => {
122
- return patterns.some((pattern) => {
123
- // Support wildcards
124
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
125
- return regex.test(pod.NAME);
126
- });
127
- });
128
- }
129
-
130
- logger.info(`Found ${pods.length} pod(s) matching criteria`, { criteria, podNames: pods.map((p) => p.NAME) });
131
- return pods;
132
- } catch (error) {
133
- logger.error('Error filtering pods', { error: error.message, criteria });
134
- return [];
135
- }
136
- },
137
-
138
- /**
139
- * Helper: Executes kubectl command with error handling.
140
- * @method _executeKubectl
141
- * @memberof UnderpostDB
142
- * @param {string} command - kubectl command to execute.
143
- * @param {Object} [options={}] - Execution options.
144
- * @param {string} [options.context=''] - Command context for logging.
145
- * @return {string|null} Command output or null on error.
146
- */
147
- _executeKubectl(command, options = {}) {
148
- const { context = '' } = options;
149
-
150
- try {
151
- logger.info(`Executing kubectl command`, { command: sanitizeCommand(command), context });
152
- return shellExec(command, { stdout: true, disableLog: true });
153
- } catch (error) {
154
- logger.error(`kubectl command failed`, { command: sanitizeCommand(command), error: error.message, context });
155
- throw error;
156
- }
157
- },
158
-
159
- /**
160
- * Helper: Copies file to pod.
161
- * @method _copyToPod
162
- * @memberof UnderpostDB
163
- * @param {Object} params - Copy parameters.
164
- * @param {string} params.sourcePath - Source file path.
165
- * @param {string} params.podName - Target pod name.
166
- * @param {string} params.namespace - Pod namespace.
167
- * @param {string} params.destPath - Destination path in pod.
168
- * @return {boolean} Success status.
169
- */
170
- _copyToPod({ sourcePath, podName, namespace, destPath }) {
171
- try {
172
- const command = `sudo kubectl cp ${sourcePath} ${namespace}/${podName}:${destPath}`;
173
- Underpost.db._executeKubectl(command, { context: `copy to pod ${podName}` });
174
- return true;
175
- } catch (error) {
176
- logger.error('Failed to copy file to pod', { sourcePath, podName, destPath, error: error.message });
177
- return false;
178
- }
179
- },
180
-
181
- /**
182
- * Helper: Copies file from pod.
183
- * @method _copyFromPod
184
- * @memberof UnderpostDB
185
- * @param {Object} params - Copy parameters.
186
- * @param {string} params.podName - Source pod name.
187
- * @param {string} params.namespace - Pod namespace.
188
- * @param {string} params.sourcePath - Source path in pod.
189
- * @param {string} params.destPath - Destination file path.
190
- * @return {boolean} Success status.
191
- */
192
- _copyFromPod({ podName, namespace, sourcePath, destPath }) {
193
- try {
194
- const command = `sudo kubectl cp ${namespace}/${podName}:${sourcePath} ${destPath}`;
195
- Underpost.db._executeKubectl(command, { context: `copy from pod ${podName}` });
196
- return true;
197
- } catch (error) {
198
- logger.error('Failed to copy file from pod', { podName, sourcePath, destPath, error: error.message });
199
- return false;
200
- }
201
- },
202
-
203
- /**
204
- * Helper: Executes command in pod.
205
- * @method _execInPod
206
- * @memberof UnderpostDB
207
- * @param {Object} params - Execution parameters.
208
- * @param {string} params.podName - Pod name.
209
- * @param {string} params.namespace - Pod namespace.
210
- * @param {string} params.command - Command to execute.
211
- * @return {string|null} Command output or null.
212
- */
213
- _execInPod({ podName, namespace, command }) {
214
- try {
215
- const kubectlCmd = `sudo kubectl exec -n ${namespace} -i ${podName} -- sh -c "${command}"`;
216
- return Underpost.db._executeKubectl(kubectlCmd, { context: `exec in pod ${podName}` });
217
- } catch (error) {
218
- logger.error('Failed to execute command in pod', {
219
- podName,
220
- command: sanitizeCommand(command),
221
- error: error.message,
222
- });
223
- throw error;
224
- }
225
- },
226
-
227
87
  /**
228
88
  * Helper: Resolves the latest backup timestamp from an existing backup directory.
229
89
  * Scans the directory for numeric (epoch) sub-folders and returns the most recent one.
@@ -239,76 +99,6 @@ class UnderpostDB {
239
99
  return entries.sort((a, b) => parseInt(b) - parseInt(a))[0];
240
100
  },
241
101
 
242
- /**
243
- * Helper: Manages Git repository for backups.
244
- * @method _manageGitRepo
245
- * @memberof UnderpostDB
246
- * @param {Object} params - Git parameters.
247
- * @param {string} params.repoName - Repository name.
248
- * @param {string} params.operation - Operation (clone, pull, commit, push).
249
- * @param {string} [params.message=''] - Commit message.
250
- * @param {boolean} [params.forceClone=false] - Force remove and re-clone repository.
251
- * @return {boolean} Success status.
252
- */
253
- _manageGitRepo({ repoName, operation, message = '', forceClone = false }) {
254
- try {
255
- const username = process.env.GITHUB_USERNAME;
256
- if (!username) {
257
- logger.error('GITHUB_USERNAME environment variable not set');
258
- return false;
259
- }
260
-
261
- const repoPath = `../${repoName}`;
262
-
263
- switch (operation) {
264
- case 'clone':
265
- if (forceClone && fs.existsSync(repoPath)) {
266
- logger.info(`Force clone enabled, removing existing repository: ${repoName}`);
267
- fs.removeSync(repoPath);
268
- }
269
- if (!fs.existsSync(repoPath)) {
270
- shellExec(`cd .. && underpost clone ${username}/${repoName}`);
271
- logger.info(`Cloned repository: ${repoName}`);
272
- }
273
- break;
274
-
275
- case 'pull':
276
- if (fs.existsSync(repoPath)) {
277
- shellExec(`cd ${repoPath} && git checkout . && git clean -f -d`);
278
- shellExec(`cd ${repoPath} && underpost pull . ${username}/${repoName}`, {
279
- silent: true,
280
- });
281
- logger.info(`Pulled repository: ${repoName}`);
282
- }
283
- break;
284
-
285
- case 'commit':
286
- if (fs.existsSync(repoPath)) {
287
- shellExec(`cd ${repoPath} && git add .`);
288
- shellExec(`underpost cmt ${repoPath} backup '' '${message}'`);
289
- logger.info(`Committed to repository: ${repoName}`, { message });
290
- }
291
- break;
292
-
293
- case 'push':
294
- if (fs.existsSync(repoPath)) {
295
- shellExec(`cd ${repoPath} && underpost push . ${username}/${repoName}`, { silent: true });
296
- logger.info(`Pushed repository: ${repoName}`);
297
- }
298
- break;
299
-
300
- default:
301
- logger.warn(`Unknown git operation: ${operation}`);
302
- return false;
303
- }
304
-
305
- return true;
306
- } catch (error) {
307
- logger.error(`Git operation failed`, { repoName, operation, error: error.message });
308
- return false;
309
- }
310
- },
311
-
312
102
  /**
313
103
  * Helper: Performs MariaDB import operation.
314
104
  * @method _importMariaDB
@@ -329,8 +119,20 @@ class UnderpostDB {
329
119
 
330
120
  logger.info('Importing MariaDB database', { podName, dbName });
331
121
 
122
+ // Always ensure the database exists first — required for WP even when no backup is available
123
+ Underpost.kubectl.run(
124
+ `kubectl exec -n ${namespace} -i ${podName} -- mariadb -p${password} -e 'CREATE DATABASE IF NOT EXISTS ${dbName};'`,
125
+ { context: `create database ${dbName}` },
126
+ );
127
+
128
+ // If no SQL file is available, the empty database is enough — return early
129
+ if (!sqlPath || !fs.existsSync(sqlPath)) {
130
+ logger.warn('No SQL backup file found — empty database ensured', { podName, dbName, sqlPath });
131
+ return true;
132
+ }
133
+
332
134
  // Remove existing SQL file in container
333
- Underpost.db._execInPod({
135
+ Underpost.kubectl.exec({
334
136
  podName,
335
137
  namespace,
336
138
  command: `rm -rf ${containerSqlPath}`,
@@ -338,7 +140,7 @@ class UnderpostDB {
338
140
 
339
141
  // Copy SQL file to pod
340
142
  if (
341
- !Underpost.db._copyToPod({
143
+ !Underpost.kubectl.cpTo({
342
144
  sourcePath: sqlPath,
343
145
  podName,
344
146
  namespace,
@@ -348,15 +150,9 @@ class UnderpostDB {
348
150
  return false;
349
151
  }
350
152
 
351
- // Create database if it doesn't exist
352
- Underpost.db._executeKubectl(
353
- `kubectl exec -n ${namespace} -i ${podName} -- mariadb -p${password} -e 'CREATE DATABASE IF NOT EXISTS ${dbName};'`,
354
- { context: `create database ${dbName}` },
355
- );
356
-
357
153
  // Import SQL file
358
154
  const importCmd = `mariadb -u ${user} -p${password} ${dbName} < ${containerSqlPath}`;
359
- Underpost.db._execInPod({ podName, namespace, command: importCmd });
155
+ Underpost.kubectl.exec({ podName, namespace, command: importCmd });
360
156
 
361
157
  logger.info('Successfully imported MariaDB database', { podName, dbName });
362
158
  return true;
@@ -387,7 +183,7 @@ class UnderpostDB {
387
183
  logger.info('Exporting MariaDB database', { podName, dbName });
388
184
 
389
185
  // Remove existing SQL file in container
390
- Underpost.db._execInPod({
186
+ Underpost.kubectl.exec({
391
187
  podName,
392
188
  namespace,
393
189
  command: `rm -rf ${containerSqlPath}`,
@@ -395,11 +191,11 @@ class UnderpostDB {
395
191
 
396
192
  // Dump database
397
193
  const dumpCmd = `mariadb-dump --user=${user} --password=${password} --lock-tables ${dbName} > ${containerSqlPath}`;
398
- Underpost.db._execInPod({ podName, namespace, command: dumpCmd });
194
+ Underpost.kubectl.exec({ podName, namespace, command: dumpCmd });
399
195
 
400
196
  // Copy SQL file from pod
401
197
  if (
402
- !Underpost.db._copyFromPod({
198
+ !Underpost.kubectl.cpFrom({
403
199
  podName,
404
200
  namespace,
405
201
  sourcePath: containerSqlPath,
@@ -442,8 +238,18 @@ class UnderpostDB {
442
238
 
443
239
  logger.info('Importing MongoDB database', { podName, dbName });
444
240
 
241
+ // If no BSON directory is available, skip — MongoDB creates the DB on first write
242
+ if (!bsonPath || !fs.existsSync(bsonPath)) {
243
+ logger.warn('No BSON backup directory found — database will be created on first write', {
244
+ podName,
245
+ dbName,
246
+ bsonPath,
247
+ });
248
+ return true;
249
+ }
250
+
445
251
  // Remove existing BSON directory in container
446
- Underpost.db._execInPod({
252
+ Underpost.kubectl.exec({
447
253
  podName,
448
254
  namespace,
449
255
  command: `rm -rf ${containerBsonPath}`,
@@ -451,7 +257,7 @@ class UnderpostDB {
451
257
 
452
258
  // Copy BSON directory to pod
453
259
  if (
454
- !Underpost.db._copyToPod({
260
+ !Underpost.kubectl.cpTo({
455
261
  sourcePath: bsonPath,
456
262
  podName,
457
263
  namespace,
@@ -465,7 +271,7 @@ class UnderpostDB {
465
271
  const restoreCmd = `mongorestore -d ${dbName} ${containerBsonPath}${drop ? ' --drop' : ''}${
466
272
  preserveUUID ? ' --preserveUUID' : ''
467
273
  }`;
468
- Underpost.db._execInPod({ podName, namespace, command: restoreCmd });
274
+ Underpost.kubectl.exec({ podName, namespace, command: restoreCmd });
469
275
 
470
276
  logger.info('Successfully imported MongoDB database', { podName, dbName });
471
277
  return true;
@@ -495,7 +301,7 @@ class UnderpostDB {
495
301
  logger.info('Exporting MongoDB database', { podName, dbName, collections });
496
302
 
497
303
  // Remove existing BSON directory in container
498
- Underpost.db._execInPod({
304
+ Underpost.kubectl.exec({
499
305
  podName,
500
306
  namespace,
501
307
  command: `rm -rf ${containerBsonPath}`,
@@ -506,16 +312,16 @@ class UnderpostDB {
506
312
  const collectionList = collections.split(',').map((c) => c.trim());
507
313
  for (const collection of collectionList) {
508
314
  const dumpCmd = `mongodump -d ${dbName} --collection ${collection} -o /`;
509
- Underpost.db._execInPod({ podName, namespace, command: dumpCmd });
315
+ Underpost.kubectl.exec({ podName, namespace, command: dumpCmd });
510
316
  }
511
317
  } else {
512
318
  const dumpCmd = `mongodump -d ${dbName} -o /`;
513
- Underpost.db._execInPod({ podName, namespace, command: dumpCmd });
319
+ Underpost.kubectl.exec({ podName, namespace, command: dumpCmd });
514
320
  }
515
321
 
516
322
  // Copy BSON directory from pod
517
323
  if (
518
- !Underpost.db._copyFromPod({
324
+ !Underpost.kubectl.cpFrom({
519
325
  podName,
520
326
  namespace,
521
327
  sourcePath: containerBsonPath,
@@ -857,15 +663,15 @@ class UnderpostDB {
857
663
  if (!processedRepos.has(repoName)) {
858
664
  logger.info('Processing Git operations for repository', { repoName, deployId });
859
665
  if (options.git === true) {
860
- Underpost.db._manageGitRepo({ repoName, operation: 'clone', forceClone: options.forceClone });
861
- Underpost.db._manageGitRepo({ repoName, operation: 'pull' });
666
+ Underpost.repo.manageBackupRepo({ repoName, operation: 'clone', forceClone: options.forceClone });
667
+ Underpost.repo.manageBackupRepo({ repoName, operation: 'pull' });
862
668
  }
863
669
 
864
670
  if (options.macroRollbackExport) {
865
671
  // Only clone if not already done by git option above
866
672
  if (options.git !== true) {
867
- Underpost.db._manageGitRepo({ repoName, operation: 'clone', forceClone: options.forceClone });
868
- Underpost.db._manageGitRepo({ repoName, operation: 'pull' });
673
+ Underpost.repo.manageBackupRepo({ repoName, operation: 'clone', forceClone: options.forceClone });
674
+ Underpost.repo.manageBackupRepo({ repoName, operation: 'pull' });
869
675
  }
870
676
 
871
677
  const nCommits = parseInt(options.macroRollbackExport);
@@ -958,11 +764,15 @@ class UnderpostDB {
958
764
  deployId: provider === 'mariadb' ? 'mariadb' : 'mongo',
959
765
  };
960
766
 
961
- targetPods = Underpost.db._getFilteredPods(podCriteria);
767
+ targetPods = Underpost.kubectl.getFilteredPods(podCriteria);
962
768
 
963
769
  // Fallback to default if no custom pods specified
964
770
  if (targetPods.length === 0 && !options.podName) {
965
- const defaultPods = Underpost.deploy.get(provider === 'mariadb' ? 'mariadb' : 'mongo', 'pods', namespace);
771
+ const defaultPods = Underpost.kubectl.get(
772
+ provider === 'mariadb' ? 'mariadb' : 'mongo',
773
+ 'pods',
774
+ namespace,
775
+ );
966
776
  console.log('defaultPods', defaultPods);
967
777
  targetPods = defaultPods;
968
778
  }
@@ -1104,8 +914,8 @@ class UnderpostDB {
1104
914
  const commitMessage = `${new Date(newBackupTimestamp).toLocaleDateString()} ${new Date(
1105
915
  newBackupTimestamp,
1106
916
  ).toLocaleTimeString()}`;
1107
- Underpost.db._manageGitRepo({ repoName, operation: 'commit', message: commitMessage });
1108
- Underpost.db._manageGitRepo({ repoName, operation: 'push' });
917
+ Underpost.repo.manageBackupRepo({ repoName, operation: 'commit', message: commitMessage });
918
+ Underpost.repo.manageBackupRepo({ repoName, operation: 'push' });
1109
919
  processedRepos.add(`${repoName}-committed`);
1110
920
  }
1111
921
  }
@@ -1155,9 +965,23 @@ class UnderpostDB {
1155
965
 
1156
966
  const { db } = loadConfServerJson(confServerPath, { resolve: true })[host][path];
1157
967
 
1158
- try {
1159
- await DataBaseProvider.load({ apis: ['instance', 'cron'], host, path, db });
968
+ const maxRetries = 5;
969
+ const retryDelay = 3000;
970
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
971
+ try {
972
+ await DataBaseProvider.load({ apis: ['instance', 'cron'], host, path, db });
973
+ break;
974
+ } catch (err) {
975
+ if (attempt === maxRetries) {
976
+ logger.error('Failed to connect to database after retries', { attempts: maxRetries, error: err.message });
977
+ throw err;
978
+ }
979
+ logger.warn('Database connection failed, retrying...', { attempt, maxRetries, error: err.message });
980
+ await timer(retryDelay);
981
+ }
982
+ }
1160
983
 
984
+ try {
1161
985
  /** @type {import('../api/instance/instance.model.js').InstanceModel} */
1162
986
  const Instance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Instance;
1163
987
 
@@ -1363,8 +1187,18 @@ class UnderpostDB {
1363
1187
  // logger.info('Processing host+path with file api', { host, path, db: db.name });
1364
1188
 
1365
1189
  try {
1366
- // Connect to database
1367
- const dbProvider = await DataBaseProvider.load({ apis, host, path, db });
1190
+ // Connect to database with retry
1191
+ let dbProvider;
1192
+ for (let attempt = 1; attempt <= 3; attempt++) {
1193
+ try {
1194
+ dbProvider = await DataBaseProvider.load({ apis, host, path, db });
1195
+ break;
1196
+ } catch (err) {
1197
+ if (attempt === 3) throw err;
1198
+ logger.warn('Database connection failed, retrying...', { attempt, host, path, error: err.message });
1199
+ await timer(3000);
1200
+ }
1201
+ }
1368
1202
  if (!dbProvider || !dbProvider.models) {
1369
1203
  logger.error('Failed to load database provider', { host, path });
1370
1204
  continue;