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.
- package/.env.example +0 -2
- package/.github/workflows/ghpkg.ci.yml +4 -4
- package/.github/workflows/npmpkg.ci.yml +38 -7
- package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -4
- package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
- package/.github/workflows/release.cd.yml +4 -4
- package/CHANGELOG.md +365 -1
- package/CLI-HELP.md +55 -3
- package/README.md +7 -3
- package/bin/build.js +18 -12
- package/bin/deploy.js +205 -225
- package/bin/file.js +3 -0
- package/conf.js +4 -10
- package/jsdoc.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +72 -50
- package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/nodemon.json +1 -1
- package/package.json +21 -14
- package/scripts/ports-ls.sh +2 -0
- package/scripts/rhel-grpc-setup.sh +56 -0
- package/src/api/file/file.ref.json +18 -0
- package/src/api/user/user.service.js +8 -7
- package/src/cli/cluster.js +7 -7
- package/src/cli/db.js +76 -242
- package/src/cli/deploy.js +104 -65
- package/src/cli/env.js +1 -0
- package/src/cli/fs.js +2 -1
- package/src/cli/index.js +50 -1
- package/src/cli/kubectl.js +211 -0
- package/src/cli/release.js +284 -0
- package/src/cli/repository.js +328 -112
- package/src/cli/run.js +283 -69
- package/src/cli/test.js +3 -3
- package/src/client/Default.index.js +3 -4
- package/src/client/components/core/Alert.js +2 -2
- package/src/client/components/core/AppStore.js +69 -0
- package/src/client/components/core/CalendarCore.js +2 -2
- package/src/client/components/core/Docs.js +9 -2
- package/src/client/components/core/DropDown.js +129 -17
- package/src/client/components/core/Keyboard.js +2 -2
- package/src/client/components/core/LogIn.js +2 -2
- package/src/client/components/core/LogOut.js +2 -2
- package/src/client/components/core/Modal.js +0 -1
- package/src/client/components/core/Panel.js +0 -1
- package/src/client/components/core/PanelForm.js +19 -19
- package/src/client/components/core/RichText.js +1 -2
- package/src/client/components/core/SocketIo.js +82 -29
- package/src/client/components/core/SocketIoHandler.js +75 -0
- package/src/client/components/core/Stream.js +143 -95
- package/src/client/components/core/Webhook.js +40 -7
- package/src/client/components/default/AppStoreDefault.js +5 -0
- package/src/client/components/default/LogInDefault.js +3 -3
- package/src/client/components/default/LogOutDefault.js +2 -2
- package/src/client/components/default/MenuDefault.js +5 -5
- package/src/client/components/default/SocketIoDefault.js +3 -51
- package/src/client/services/core/core.service.js +20 -8
- package/src/client/services/user/user.management.js +2 -2
- package/src/client/ssr/body/404.js +15 -11
- package/src/client/ssr/body/500.js +15 -11
- package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
- package/src/client/ssr/offline/NoNetworkConnection.js +11 -10
- package/src/client/ssr/pages/Test.js +11 -10
- package/src/index.js +24 -1
- package/src/runtime/express/Express.js +26 -9
- package/src/runtime/lampp/Dockerfile +9 -2
- package/src/runtime/lampp/Lampp.js +4 -3
- package/src/runtime/wp/Dockerfile +64 -0
- package/src/runtime/wp/Wp.js +497 -0
- package/src/server/auth.js +30 -6
- package/src/server/backup.js +19 -1
- package/src/server/client-build-docs.js +51 -110
- package/src/server/client-build.js +55 -64
- package/src/server/client-formatted.js +109 -57
- package/src/server/conf.js +19 -15
- package/src/server/ipfs-client.js +24 -1
- package/src/server/peer.js +8 -0
- package/src/server/runtime.js +25 -1
- package/src/server/start.js +21 -8
- package/src/ws/IoInterface.js +1 -10
- package/src/ws/IoServer.js +14 -33
- package/src/ws/core/channels/core.ws.chat.js +65 -20
- package/src/ws/core/channels/core.ws.mailer.js +113 -32
- package/src/ws/core/channels/core.ws.stream.js +90 -31
- package/src/ws/core/core.ws.connection.js +12 -33
- package/src/ws/core/core.ws.emit.js +10 -26
- package/src/ws/core/core.ws.server.js +25 -58
- package/src/ws/default/channels/default.ws.main.js +53 -12
- package/src/ws/default/default.ws.connection.js +26 -13
- package/src/ws/default/default.ws.server.js +30 -12
- package/src/client/components/default/ElementsDefault.js +0 -38
- package/src/ws/core/management/core.ws.chat.js +0 -8
- package/src/ws/core/management/core.ws.mailer.js +0 -16
- package/src/ws/core/management/core.ws.stream.js +0 -8
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
194
|
+
Underpost.kubectl.exec({ podName, namespace, command: dumpCmd });
|
|
399
195
|
|
|
400
196
|
// Copy SQL file from pod
|
|
401
197
|
if (
|
|
402
|
-
!Underpost.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
315
|
+
Underpost.kubectl.exec({ podName, namespace, command: dumpCmd });
|
|
510
316
|
}
|
|
511
317
|
} else {
|
|
512
318
|
const dumpCmd = `mongodump -d ${dbName} -o /`;
|
|
513
|
-
Underpost.
|
|
319
|
+
Underpost.kubectl.exec({ podName, namespace, command: dumpCmd });
|
|
514
320
|
}
|
|
515
321
|
|
|
516
322
|
// Copy BSON directory from pod
|
|
517
323
|
if (
|
|
518
|
-
!Underpost.
|
|
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.
|
|
861
|
-
Underpost.
|
|
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.
|
|
868
|
-
Underpost.
|
|
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.
|
|
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.
|
|
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.
|
|
1108
|
-
Underpost.
|
|
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
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
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;
|