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.
- package/.github/workflows/npmpkg.ci.yml +1 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -0
- package/.vscode/settings.json +10 -5
- package/CHANGELOG.md +122 -1
- package/CLI-HELP.md +22 -7
- package/README.md +37 -8
- package/bin/build.js +26 -9
- package/bin/deploy.js +20 -21
- package/bin/file.js +31 -13
- package/bin/index.js +2 -1
- package/bin/vs.js +1 -1
- package/bump.config.js +26 -0
- package/conf.js +20 -4
- 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 +4 -2
- package/manifests/kind-config-dev.yaml +8 -0
- package/manifests/mongodb/pv-pvc.yaml +44 -8
- package/manifests/mongodb/statefulset.yaml +55 -68
- package/package.json +27 -12
- package/scripts/k3s-node-setup.sh +28 -9
- package/src/api/core/core.router.js +19 -14
- package/src/api/core/core.service.js +5 -5
- package/src/api/default/default.router.js +22 -18
- package/src/api/default/default.service.js +5 -5
- package/src/api/document/document.router.js +28 -23
- package/src/api/document/document.service.js +100 -23
- package/src/api/file/file.router.js +19 -13
- package/src/api/file/file.service.js +9 -7
- package/src/api/test/test.router.js +17 -12
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +5 -4
- package/src/api/user/user.router.js +297 -288
- package/src/api/user/user.service.js +100 -35
- package/src/cli/baremetal.js +20 -11
- package/src/cli/cluster.js +196 -55
- package/src/cli/db.js +59 -60
- package/src/cli/deploy.js +273 -159
- package/src/cli/fs.js +3 -1
- package/src/cli/index.js +16 -9
- package/src/cli/ipfs.js +4 -6
- package/src/cli/kubectl.js +4 -1
- package/src/cli/lxd.js +217 -135
- package/src/cli/release.js +289 -131
- package/src/cli/repository.js +58 -7
- package/src/cli/run.js +152 -25
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +4 -0
- package/src/client/components/core/PanelForm.js +56 -52
- package/src/client/components/core/Worker.js +162 -363
- package/src/client/sw/core.sw.js +174 -112
- package/src/db/DataBaseProvider.js +120 -20
- package/src/db/mongo/MongoBootstrap.js +587 -0
- package/src/db/mongo/MongooseDB.js +126 -22
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +20 -65
- package/src/server/process.js +180 -19
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +12 -4
- package/src/ws/IoInterface.js +16 -16
- package/src/ws/core/channels/core.ws.chat.js +11 -11
- package/src/ws/core/channels/core.ws.mailer.js +29 -29
- package/src/ws/core/channels/core.ws.stream.js +19 -19
- package/src/ws/core/core.ws.connection.js +8 -8
- package/src/ws/core/core.ws.server.js +6 -5
- package/src/ws/default/channels/default.ws.main.js +10 -10
- package/src/ws/default/default.ws.connection.js +4 -4
- package/src/ws/default/default.ws.server.js +4 -3
- package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
- package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
- /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
- /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
- /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 {
|
|
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
|
|
272
|
-
|
|
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
|
|
361
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
1161
|
+
await DataBaseProviderService.load({ apis: ['cron'], host, path, db });
|
|
1163
1162
|
|
|
1164
1163
|
/** @type {import('../api/cron/cron.model.js').CronModel} */
|
|
1165
|
-
const 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
|
|
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
|
|
1276
|
+
dbProvider = await DataBaseProviderService.load({ apis, host, path, db });
|
|
1278
1277
|
break;
|
|
1279
1278
|
} catch (err) {
|
|
1280
1279
|
if (attempt === 3) throw err;
|