underpost 3.0.2 → 3.1.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.production → .env.example} +20 -2
- package/.github/workflows/ghpkg.ci.yml +1 -1
- package/.github/workflows/gitlab.ci.yml +1 -1
- package/.github/workflows/npmpkg.ci.yml +22 -7
- package/.github/workflows/publish.ci.yml +5 -5
- package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -3
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +3 -2
- package/.vscode/extensions.json +9 -8
- package/.vscode/settings.json +3 -2
- package/CHANGELOG.md +468 -290
- package/CLI-HELP.md +72 -52
- package/README.md +2 -2
- package/bin/build.js +4 -2
- package/bin/deploy.js +150 -208
- package/bin/file.js +2 -1
- package/bin/vs.js +3 -3
- package/conf.js +30 -13
- 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 +52 -52
- package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
- package/manifests/pv-pvc-dd.yaml +1 -1
- package/package.json +53 -44
- package/scripts/k3s-node-setup.sh +1 -1
- package/src/api/document/document.service.js +1 -1
- package/src/api/file/file.controller.js +3 -1
- package/src/api/file/file.service.js +28 -5
- package/src/api/user/user.router.js +10 -5
- package/src/api/user/user.service.js +7 -7
- package/src/cli/baremetal.js +6 -10
- package/src/cli/cloud-init.js +0 -3
- package/src/cli/db.js +54 -71
- package/src/cli/deploy.js +64 -12
- package/src/cli/env.js +4 -4
- package/src/cli/fs.js +0 -2
- package/src/cli/image.js +0 -3
- package/src/cli/index.js +33 -13
- package/src/cli/monitor.js +5 -6
- package/src/cli/repository.js +322 -35
- package/src/cli/run.js +148 -71
- package/src/cli/secrets.js +0 -3
- package/src/cli/ssh.js +1 -1
- package/src/client/components/core/AgGrid.js +20 -5
- package/src/client/components/core/Content.js +22 -3
- package/src/client/components/core/Docs.js +21 -4
- package/src/client/components/core/FileExplorer.js +71 -4
- package/src/client/components/core/Input.js +1 -1
- package/src/client/components/core/Modal.js +22 -6
- package/src/client/components/core/PublicProfile.js +3 -3
- package/src/client/components/core/Router.js +34 -1
- package/src/client/components/core/Worker.js +1 -1
- package/src/client/public/default/sitemap +3 -3
- package/src/client/public/test/sitemap +3 -3
- package/src/client.build.js +0 -3
- package/src/client.dev.js +0 -3
- package/src/db/DataBaseProvider.js +17 -2
- package/src/db/mariadb/MariaDB.js +14 -9
- package/src/db/mongo/MongooseDB.js +17 -1
- package/src/index.js +1 -1
- package/src/proxy.js +0 -3
- package/src/runtime/express/Express.js +7 -1
- package/src/runtime/lampp/Lampp.js +6 -13
- package/src/server/auth.js +6 -9
- package/src/server/backup.js +2 -3
- package/src/server/client-build-docs.js +178 -3
- package/src/server/client-build-live.js +9 -18
- package/src/server/client-build.js +175 -38
- package/src/server/client-dev-server.js +14 -13
- package/src/server/conf.js +357 -149
- package/src/server/cron.js +2 -1
- package/src/server/dns.js +28 -12
- package/src/server/downloader.js +0 -2
- package/src/server/logger.js +27 -9
- package/src/server/peer.js +0 -2
- package/src/server/process.js +1 -50
- package/src/server/proxy.js +4 -8
- package/src/server/runtime.js +5 -8
- package/src/server/ssr.js +0 -3
- package/src/server/start.js +5 -5
- package/src/server/tls.js +0 -2
- package/src/server.js +0 -4
- package/.env.development +0 -43
- package/.env.test +0 -43
package/src/cli/db.js
CHANGED
|
@@ -6,15 +6,30 @@
|
|
|
6
6
|
* Supports MariaDB and MongoDB with import/export capabilities, Git integration, and multi-pod operations.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { mergeFile, splitFileFactory } from '../server/conf.js';
|
|
9
|
+
import { mergeFile, splitFileFactory, loadConfServerJson, resolveConfSecrets } from '../server/conf.js';
|
|
10
10
|
import { loggerFactory } from '../server/logger.js';
|
|
11
11
|
import { shellExec } from '../server/process.js';
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
13
|
import { DataBaseProvider } from '../db/DataBaseProvider.js';
|
|
14
|
-
import { loadReplicas, pathPortAssignmentFactory } from '../server/conf.js';
|
|
14
|
+
import { loadReplicas, pathPortAssignmentFactory, loadCronDeployEnv } from '../server/conf.js';
|
|
15
15
|
import Underpost from '../index.js';
|
|
16
16
|
const logger = loggerFactory(import.meta);
|
|
17
17
|
|
|
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
|
+
|
|
18
33
|
/**
|
|
19
34
|
* Constants for database operations
|
|
20
35
|
* @constant {number} MAX_BACKUP_RETENTION - Maximum number of backups to retain
|
|
@@ -133,10 +148,10 @@ class UnderpostDB {
|
|
|
133
148
|
const { context = '' } = options;
|
|
134
149
|
|
|
135
150
|
try {
|
|
136
|
-
logger.info(`Executing kubectl command`, { command, context });
|
|
137
|
-
return shellExec(command, { stdout: true });
|
|
151
|
+
logger.info(`Executing kubectl command`, { command: sanitizeCommand(command), context });
|
|
152
|
+
return shellExec(command, { stdout: true, disableLog: true });
|
|
138
153
|
} catch (error) {
|
|
139
|
-
logger.error(`kubectl command failed`, { command, error: error.message, context });
|
|
154
|
+
logger.error(`kubectl command failed`, { command: sanitizeCommand(command), error: error.message, context });
|
|
140
155
|
throw error;
|
|
141
156
|
}
|
|
142
157
|
},
|
|
@@ -200,11 +215,30 @@ class UnderpostDB {
|
|
|
200
215
|
const kubectlCmd = `sudo kubectl exec -n ${namespace} -i ${podName} -- sh -c "${command}"`;
|
|
201
216
|
return Underpost.db._executeKubectl(kubectlCmd, { context: `exec in pod ${podName}` });
|
|
202
217
|
} catch (error) {
|
|
203
|
-
logger.error('Failed to execute command in pod', {
|
|
218
|
+
logger.error('Failed to execute command in pod', {
|
|
219
|
+
podName,
|
|
220
|
+
command: sanitizeCommand(command),
|
|
221
|
+
error: error.message,
|
|
222
|
+
});
|
|
204
223
|
throw error;
|
|
205
224
|
}
|
|
206
225
|
},
|
|
207
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Helper: Resolves the latest backup timestamp from an existing backup directory.
|
|
229
|
+
* Scans the directory for numeric (epoch) sub-folders and returns the most recent one.
|
|
230
|
+
* @method _getLatestBackupTimestamp
|
|
231
|
+
* @memberof UnderpostDB
|
|
232
|
+
* @param {string} backupDir - Path to the host-folder backup directory.
|
|
233
|
+
* @return {string|null} The latest timestamp string, or null if none found.
|
|
234
|
+
*/
|
|
235
|
+
_getLatestBackupTimestamp(backupDir) {
|
|
236
|
+
if (!fs.existsSync(backupDir)) return null;
|
|
237
|
+
const entries = fs.readdirSync(backupDir).filter((e) => /^\d+$/.test(e));
|
|
238
|
+
if (entries.length === 0) return null;
|
|
239
|
+
return entries.sort((a, b) => parseInt(b) - parseInt(a))[0];
|
|
240
|
+
},
|
|
241
|
+
|
|
208
242
|
/**
|
|
209
243
|
* Helper: Manages Git repository for backups.
|
|
210
244
|
* @method _manageGitRepo
|
|
@@ -275,55 +309,6 @@ class UnderpostDB {
|
|
|
275
309
|
}
|
|
276
310
|
},
|
|
277
311
|
|
|
278
|
-
/**
|
|
279
|
-
* Helper: Manages backup timestamps and cleanup.
|
|
280
|
-
* @method _manageBackupTimestamps
|
|
281
|
-
* @memberof UnderpostDB
|
|
282
|
-
* @param {string} backupPath - Backup directory path.
|
|
283
|
-
* @param {number} newTimestamp - New backup timestamp.
|
|
284
|
-
* @param {boolean} shouldCleanup - Whether to cleanup old backups.
|
|
285
|
-
* @return {Object} Backup info with current and removed timestamps.
|
|
286
|
-
*/
|
|
287
|
-
_manageBackupTimestamps(backupPath, newTimestamp, shouldCleanup) {
|
|
288
|
-
try {
|
|
289
|
-
if (!fs.existsSync(backupPath)) {
|
|
290
|
-
fs.mkdirSync(backupPath, { recursive: true });
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Delete empty folders
|
|
294
|
-
shellExec(`cd ${backupPath} && find . -type d -empty -delete`);
|
|
295
|
-
|
|
296
|
-
const times = fs.readdirSync(backupPath);
|
|
297
|
-
const validTimes = times.map((t) => parseInt(t)).filter((t) => !isNaN(t));
|
|
298
|
-
|
|
299
|
-
const currentBackupTimestamp = validTimes.length > 0 ? Math.max(...validTimes) : null;
|
|
300
|
-
const removeBackupTimestamp = validTimes.length > 0 ? Math.min(...validTimes) : null;
|
|
301
|
-
|
|
302
|
-
// Cleanup old backups if we have too many
|
|
303
|
-
if (shouldCleanup && validTimes.length >= MAX_BACKUP_RETENTION && removeBackupTimestamp) {
|
|
304
|
-
const removeDir = `${backupPath}/${removeBackupTimestamp}`;
|
|
305
|
-
logger.info('Removing old backup', { path: removeDir });
|
|
306
|
-
fs.removeSync(removeDir);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Create new backup directory
|
|
310
|
-
if (shouldCleanup) {
|
|
311
|
-
const newBackupDir = `${backupPath}/${newTimestamp}`;
|
|
312
|
-
logger.info('Creating new backup directory', { path: newBackupDir });
|
|
313
|
-
fs.mkdirSync(newBackupDir, { recursive: true });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
-
current: currentBackupTimestamp,
|
|
318
|
-
removed: removeBackupTimestamp,
|
|
319
|
-
count: validTimes.length,
|
|
320
|
-
};
|
|
321
|
-
} catch (error) {
|
|
322
|
-
logger.error('Error managing backup timestamps', { backupPath, error: error.message });
|
|
323
|
-
return { current: null, removed: null, count: 0 };
|
|
324
|
-
}
|
|
325
|
-
},
|
|
326
|
-
|
|
327
312
|
/**
|
|
328
313
|
* Helper: Performs MariaDB import operation.
|
|
329
314
|
* @method _importMariaDB
|
|
@@ -624,7 +609,7 @@ class UnderpostDB {
|
|
|
624
609
|
logger.info('Getting MariaDB table statistics', { podName, dbName });
|
|
625
610
|
|
|
626
611
|
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`;
|
|
627
|
-
const output = shellExec(command, { stdout: true, silent: true });
|
|
612
|
+
const output = shellExec(command, { stdout: true, silent: true, disableLog: true });
|
|
628
613
|
|
|
629
614
|
if (!output || output.trim() === '') {
|
|
630
615
|
logger.warn('No tables found or empty output');
|
|
@@ -788,6 +773,7 @@ class UnderpostDB {
|
|
|
788
773
|
kind: false,
|
|
789
774
|
},
|
|
790
775
|
) {
|
|
776
|
+
loadCronDeployEnv();
|
|
791
777
|
const newBackupTimestamp = new Date().getTime();
|
|
792
778
|
const namespace = options.ns && typeof options.ns === 'string' ? options.ns : 'default';
|
|
793
779
|
|
|
@@ -844,7 +830,7 @@ class UnderpostDB {
|
|
|
844
830
|
continue;
|
|
845
831
|
}
|
|
846
832
|
|
|
847
|
-
const confServer =
|
|
833
|
+
const confServer = loadConfServerJson(confServerPath, { resolve: true });
|
|
848
834
|
|
|
849
835
|
// Build database configuration map
|
|
850
836
|
for (const host of Object.keys(confServer)) {
|
|
@@ -943,16 +929,11 @@ class UnderpostDB {
|
|
|
943
929
|
|
|
944
930
|
logger.info('Processing database', { hostFolder, provider, dbName, deployId });
|
|
945
931
|
|
|
946
|
-
const
|
|
947
|
-
const backupInfo = Underpost.db._manageBackupTimestamps(
|
|
948
|
-
backUpPath,
|
|
949
|
-
newBackupTimestamp,
|
|
950
|
-
options.export === true,
|
|
951
|
-
);
|
|
932
|
+
const latestBackupTimestamp = Underpost.db._getLatestBackupTimestamp(`../${repoName}/${hostFolder}`);
|
|
952
933
|
|
|
953
|
-
dbs[provider][dbName].currentBackupTimestamp =
|
|
934
|
+
dbs[provider][dbName].currentBackupTimestamp = latestBackupTimestamp;
|
|
954
935
|
|
|
955
|
-
const currentTimestamp =
|
|
936
|
+
const currentTimestamp = latestBackupTimestamp || newBackupTimestamp;
|
|
956
937
|
const sqlContainerPath = `/home/${dbName}.sql`;
|
|
957
938
|
const fromPartsPath = `../${repoName}/${hostFolder}/${currentTimestamp}/${dbName}-parths.json`;
|
|
958
939
|
const toSqlPath = `../${repoName}/${hostFolder}/${currentTimestamp}/${dbName}.sql`;
|
|
@@ -1149,6 +1130,7 @@ class UnderpostDB {
|
|
|
1149
1130
|
host = process.env.DEFAULT_DEPLOY_HOST,
|
|
1150
1131
|
path = process.env.DEFAULT_DEPLOY_PATH,
|
|
1151
1132
|
) {
|
|
1133
|
+
loadCronDeployEnv();
|
|
1152
1134
|
deployId = deployId ? deployId : process.env.DEFAULT_DEPLOY_ID;
|
|
1153
1135
|
host = host ? host : process.env.DEFAULT_DEPLOY_HOST;
|
|
1154
1136
|
path = path ? path : process.env.DEFAULT_DEPLOY_PATH;
|
|
@@ -1171,7 +1153,7 @@ class UnderpostDB {
|
|
|
1171
1153
|
throw new Error(`Server configuration not found: ${confServerPath}`);
|
|
1172
1154
|
}
|
|
1173
1155
|
|
|
1174
|
-
const { db } =
|
|
1156
|
+
const { db } = loadConfServerJson(confServerPath, { resolve: true })[host][path];
|
|
1175
1157
|
|
|
1176
1158
|
try {
|
|
1177
1159
|
await DataBaseProvider.load({ apis: ['instance', 'cron'], host, path, db });
|
|
@@ -1194,7 +1176,7 @@ class UnderpostDB {
|
|
|
1194
1176
|
continue;
|
|
1195
1177
|
}
|
|
1196
1178
|
|
|
1197
|
-
const confServer = loadReplicas(deployId,
|
|
1179
|
+
const confServer = loadReplicas(deployId, loadConfServerJson(confServerPath, { resolve: true }));
|
|
1198
1180
|
const router = await Underpost.deploy.routerFactory(deployId, env);
|
|
1199
1181
|
const pathPortAssignmentData = await pathPortAssignmentFactory(deployId, router, confServer);
|
|
1200
1182
|
|
|
@@ -1257,7 +1239,7 @@ class UnderpostDB {
|
|
|
1257
1239
|
}
|
|
1258
1240
|
}
|
|
1259
1241
|
} catch (error) {
|
|
1260
|
-
logger.error('Failed to create instance metadata', { error: error.message
|
|
1242
|
+
logger.error('Failed to create instance metadata', { error: error.message });
|
|
1261
1243
|
throw error;
|
|
1262
1244
|
}
|
|
1263
1245
|
|
|
@@ -1297,7 +1279,7 @@ class UnderpostDB {
|
|
|
1297
1279
|
await new Cron(body).save();
|
|
1298
1280
|
}
|
|
1299
1281
|
} catch (error) {
|
|
1300
|
-
logger.error('Failed to create cron metadata', { error: error.message
|
|
1282
|
+
logger.error('Failed to create cron metadata', { error: error.message });
|
|
1301
1283
|
}
|
|
1302
1284
|
|
|
1303
1285
|
await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
|
|
@@ -1325,6 +1307,7 @@ class UnderpostDB {
|
|
|
1325
1307
|
dryRun: false,
|
|
1326
1308
|
},
|
|
1327
1309
|
) {
|
|
1310
|
+
loadCronDeployEnv();
|
|
1328
1311
|
if (deployList === 'dd') deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
|
|
1329
1312
|
|
|
1330
1313
|
logger.info('Starting File collection cleanup', { deployList, options });
|
|
@@ -1359,7 +1342,7 @@ class UnderpostDB {
|
|
|
1359
1342
|
continue;
|
|
1360
1343
|
}
|
|
1361
1344
|
|
|
1362
|
-
const confServer =
|
|
1345
|
+
const confServer = loadConfServerJson(confServerPath, { resolve: true });
|
|
1363
1346
|
|
|
1364
1347
|
// Process each host+path combination
|
|
1365
1348
|
for (const host of Object.keys(confServer)) {
|
|
@@ -1499,7 +1482,6 @@ class UnderpostDB {
|
|
|
1499
1482
|
host,
|
|
1500
1483
|
path,
|
|
1501
1484
|
error: error.message,
|
|
1502
|
-
stack: error.stack,
|
|
1503
1485
|
});
|
|
1504
1486
|
}
|
|
1505
1487
|
}
|
|
@@ -1553,6 +1535,7 @@ class UnderpostDB {
|
|
|
1553
1535
|
crons: false,
|
|
1554
1536
|
},
|
|
1555
1537
|
) {
|
|
1538
|
+
loadCronDeployEnv();
|
|
1556
1539
|
deployId = deployId ? deployId : process.env.DEFAULT_DEPLOY_ID;
|
|
1557
1540
|
host = host ? host : process.env.DEFAULT_DEPLOY_HOST;
|
|
1558
1541
|
path = path ? path : process.env.DEFAULT_DEPLOY_PATH;
|
package/src/cli/deploy.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Config,
|
|
12
12
|
deployRangePortFactory,
|
|
13
13
|
getDataDeploy,
|
|
14
|
+
loadConfServerJson,
|
|
14
15
|
loadReplicas,
|
|
15
16
|
pathPortAssignmentFactory,
|
|
16
17
|
} from '../server/conf.js';
|
|
@@ -132,7 +133,7 @@ class UnderpostDeploy {
|
|
|
132
133
|
`npm install -g npm@11.2.0`,
|
|
133
134
|
`npm install -g underpost`,
|
|
134
135
|
`underpost secret underpost --create-from-file /etc/config/.env.${env}`,
|
|
135
|
-
`underpost start --build --run
|
|
136
|
+
`underpost start --build --run ${deployId} ${env}`,
|
|
136
137
|
];
|
|
137
138
|
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
138
139
|
if (!volumes)
|
|
@@ -230,7 +231,7 @@ spec:
|
|
|
230
231
|
if (!deployId) continue;
|
|
231
232
|
const confServer = loadReplicas(
|
|
232
233
|
deployId,
|
|
233
|
-
|
|
234
|
+
loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
|
|
234
235
|
);
|
|
235
236
|
const router = await Underpost.deploy.routerFactory(deployId, env);
|
|
236
237
|
const pathPortAssignmentData = await pathPortAssignmentFactory(deployId, router, confServer);
|
|
@@ -259,6 +260,27 @@ ${Underpost.deploy
|
|
|
259
260
|
}
|
|
260
261
|
fs.writeFileSync(`./engine-private/conf/${deployId}/build/${env}/deployment.yaml`, deploymentYamlParts, 'utf8');
|
|
261
262
|
|
|
263
|
+
const confVolume = fs.existsSync(`./engine-private/conf/${deployId}/conf.volume.json`)
|
|
264
|
+
? JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.volume.json`, 'utf8'))
|
|
265
|
+
: [];
|
|
266
|
+
if (confVolume.length > 0) {
|
|
267
|
+
let volumeYaml = '';
|
|
268
|
+
for (const deploymentVersion of deploymentVersions) {
|
|
269
|
+
for (const volume of confVolume) {
|
|
270
|
+
if (!volume.claimName) continue;
|
|
271
|
+
const pvcId = `${volume.claimName}-${deployId}-${env}-${deploymentVersion}`;
|
|
272
|
+
const pvId = pvcId.replace(/^pvc-/, 'pv-');
|
|
273
|
+
const hostPath = `/home/dd/engine/volume/${pvId}`;
|
|
274
|
+
volumeYaml += `---\n${Underpost.deploy.persistentVolumeFactory({
|
|
275
|
+
pvcId,
|
|
276
|
+
namespace: options.namespace,
|
|
277
|
+
hostPath,
|
|
278
|
+
})}\n`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
fs.writeFileSync(`./engine-private/conf/${deployId}/build/${env}/pv-pvc.yaml`, volumeYaml, 'utf8');
|
|
282
|
+
}
|
|
283
|
+
|
|
262
284
|
let proxyYaml = '';
|
|
263
285
|
let secretYaml = '';
|
|
264
286
|
const customServices = fs.existsSync(`./engine-private/conf/${deployId}/conf.services.json`)
|
|
@@ -335,7 +357,7 @@ ${Underpost.deploy
|
|
|
335
357
|
const yamlPath = `./engine-private/conf/${deployId}/build/${env}/secret.yaml`;
|
|
336
358
|
fs.writeFileSync(yamlPath, secretYaml, 'utf8');
|
|
337
359
|
} else {
|
|
338
|
-
const deploymentsFiles = ['Dockerfile', 'proxy.yaml', 'deployment.yaml'];
|
|
360
|
+
const deploymentsFiles = ['Dockerfile', 'proxy.yaml', 'deployment.yaml', 'pv-pvc.yaml'];
|
|
339
361
|
for (const file of deploymentsFiles) {
|
|
340
362
|
if (fs.existsSync(`./engine-private/conf/${deployId}/build/${env}/${file}`)) {
|
|
341
363
|
fs.copyFileSync(
|
|
@@ -385,7 +407,7 @@ spec:
|
|
|
385
407
|
// kubectl get deploy,sts,svc,configmap,secret -n default -o yaml --export > default.yaml
|
|
386
408
|
const hostTest = options?.hostTest
|
|
387
409
|
? options.hostTest
|
|
388
|
-
: Object.keys(
|
|
410
|
+
: Object.keys(loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`))[0];
|
|
389
411
|
const info = shellExec(`sudo kubectl get HTTPProxy/${hostTest} -n ${options.namespace} -o yaml`, {
|
|
390
412
|
silent: true,
|
|
391
413
|
stdout: true,
|
|
@@ -550,7 +572,7 @@ EOF`);
|
|
|
550
572
|
if (!(options.versions && typeof options.versions === 'string')) options.versions = 'blue,green';
|
|
551
573
|
if (!options.replicas) options.replicas = 1;
|
|
552
574
|
if (options.sync)
|
|
553
|
-
getDataDeploy({
|
|
575
|
+
await getDataDeploy({
|
|
554
576
|
buildSingleReplica: true,
|
|
555
577
|
});
|
|
556
578
|
if (options.buildManifest === true) await Underpost.deploy.buildManifest(deployList, env, options);
|
|
@@ -591,7 +613,7 @@ EOF`);
|
|
|
591
613
|
continue;
|
|
592
614
|
}
|
|
593
615
|
|
|
594
|
-
const confServer =
|
|
616
|
+
const confServer = loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`);
|
|
595
617
|
const confVolume = fs.existsSync(`./engine-private/conf/${deployId}/conf.volume.json`)
|
|
596
618
|
? JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.volume.json`, 'utf8'))
|
|
597
619
|
: [];
|
|
@@ -844,6 +866,7 @@ EOF`);
|
|
|
844
866
|
${Underpost.deploy.persistentVolumeFactory({
|
|
845
867
|
hostPath: rootVolumeHostPath,
|
|
846
868
|
pvcId,
|
|
869
|
+
namespace,
|
|
847
870
|
})}
|
|
848
871
|
EOF
|
|
849
872
|
`);
|
|
@@ -914,15 +937,44 @@ EOF
|
|
|
914
937
|
* @param {object} options - Options for the persistent volume and claim creation.
|
|
915
938
|
* @param {string} options.hostPath - Host path for the persistent volume.
|
|
916
939
|
* @param {string} options.pvcId - Persistent volume claim ID.
|
|
940
|
+
* @param {string} [options.namespace='default'] - Kubernetes namespace for the PVC claimRef.
|
|
917
941
|
* @returns {string} - YAML configuration for the persistent volume and claim.
|
|
918
942
|
* @memberof UnderpostDeploy
|
|
919
943
|
*/
|
|
920
|
-
persistentVolumeFactory({ hostPath, pvcId }) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
944
|
+
persistentVolumeFactory({ hostPath, pvcId, namespace = 'default' }) {
|
|
945
|
+
const pvId = pvcId.replace(/^pvc-/, 'pv-');
|
|
946
|
+
return `apiVersion: v1
|
|
947
|
+
kind: PersistentVolume
|
|
948
|
+
metadata:
|
|
949
|
+
name: ${pvId}
|
|
950
|
+
spec:
|
|
951
|
+
capacity:
|
|
952
|
+
storage: 5Gi
|
|
953
|
+
accessModes:
|
|
954
|
+
- ReadWriteOnce
|
|
955
|
+
persistentVolumeReclaimPolicy: Retain
|
|
956
|
+
storageClassName: manual
|
|
957
|
+
claimRef:
|
|
958
|
+
apiVersion: v1
|
|
959
|
+
kind: PersistentVolumeClaim
|
|
960
|
+
name: ${pvcId}
|
|
961
|
+
namespace: ${namespace}
|
|
962
|
+
hostPath:
|
|
963
|
+
path: ${hostPath}
|
|
964
|
+
type: DirectoryOrCreate
|
|
965
|
+
---
|
|
966
|
+
apiVersion: v1
|
|
967
|
+
kind: PersistentVolumeClaim
|
|
968
|
+
metadata:
|
|
969
|
+
name: ${pvcId}
|
|
970
|
+
spec:
|
|
971
|
+
accessModes:
|
|
972
|
+
- ReadWriteOnce
|
|
973
|
+
storageClassName: manual
|
|
974
|
+
volumeName: ${pvId}
|
|
975
|
+
resources:
|
|
976
|
+
requests:
|
|
977
|
+
storage: 5Gi`;
|
|
926
978
|
},
|
|
927
979
|
|
|
928
980
|
/**
|
package/src/cli/env.js
CHANGED
|
@@ -10,12 +10,10 @@ import { loggerFactory } from '../server/logger.js';
|
|
|
10
10
|
import dotenv from 'dotenv';
|
|
11
11
|
import { pbcopy } from '../server/process.js';
|
|
12
12
|
|
|
13
|
-
dotenv.config();
|
|
14
|
-
|
|
15
13
|
const logger = loggerFactory(import.meta);
|
|
16
14
|
|
|
17
15
|
/**
|
|
18
|
-
* @class
|
|
16
|
+
* @class UnderpostRootEnv
|
|
19
17
|
* @description Manages the environment variables of the underpost root.
|
|
20
18
|
* @memberof UnderpostEnv
|
|
21
19
|
*/
|
|
@@ -41,7 +39,9 @@ class UnderpostRootEnv {
|
|
|
41
39
|
if (options.build) {
|
|
42
40
|
const deployIdList = options.deployId
|
|
43
41
|
? [options.deployId]
|
|
44
|
-
: fs.
|
|
42
|
+
: fs.existsSync(`./engine-private/deploy/dd.router`)
|
|
43
|
+
? fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')
|
|
44
|
+
: [DEFAULT_DEPLOY_ID];
|
|
45
45
|
for (const deployId of deployIdList)
|
|
46
46
|
for (const envFile of ['test', 'development', 'production'])
|
|
47
47
|
_set(`./engine-private/conf/${deployId}/.env.${envFile}`, key, value);
|
package/src/cli/fs.js
CHANGED
|
@@ -6,14 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import { v2 as cloudinary } from 'cloudinary';
|
|
8
8
|
import { loggerFactory } from '../server/logger.js';
|
|
9
|
-
import dotenv from 'dotenv';
|
|
10
9
|
import AdmZip from 'adm-zip';
|
|
11
10
|
import * as dir from 'path';
|
|
12
11
|
import fs from 'fs-extra';
|
|
13
12
|
import Downloader from '../server/downloader.js';
|
|
14
13
|
import { shellExec } from '../server/process.js';
|
|
15
14
|
import Underpost from '../index.js';
|
|
16
|
-
dotenv.config();
|
|
17
15
|
|
|
18
16
|
const logger = loggerFactory(import.meta);
|
|
19
17
|
|
package/src/cli/image.js
CHANGED
|
@@ -5,14 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from 'fs-extra';
|
|
8
|
-
import dotenv from 'dotenv';
|
|
9
8
|
import { loggerFactory } from '../server/logger.js';
|
|
10
9
|
import Underpost from '../index.js';
|
|
11
10
|
import { getNpmRootPath, getUnderpostRootPath } from '../server/conf.js';
|
|
12
11
|
import { shellExec } from '../server/process.js';
|
|
13
12
|
|
|
14
|
-
dotenv.config();
|
|
15
|
-
|
|
16
13
|
const logger = loggerFactory(import.meta);
|
|
17
14
|
|
|
18
15
|
/**
|
package/src/cli/index.js
CHANGED
|
@@ -7,11 +7,10 @@ import { commitData } from '../client/components/core/CommonJs.js';
|
|
|
7
7
|
|
|
8
8
|
import Underpost from '../index.js';
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const underpostGlobalEnv = `${getUnderpostRootPath()}/.env`;
|
|
11
11
|
|
|
12
|
-
fs.existsSync(
|
|
13
|
-
|
|
14
|
-
: dotenv.config();
|
|
12
|
+
if (fs.existsSync(underpostGlobalEnv)) dotenv.config({ path: underpostGlobalEnv, override: true });
|
|
13
|
+
else dotenv.config();
|
|
15
14
|
|
|
16
15
|
const program = new Command();
|
|
17
16
|
|
|
@@ -27,6 +26,10 @@ program
|
|
|
27
26
|
.option('--build', 'Build the deployment to pwa-microservices-template (requires --deploy-id)')
|
|
28
27
|
.option('--clean-template', 'Clean the build directory (pwa-microservices-template)')
|
|
29
28
|
.option('--sync-conf', 'Sync configuration to private repositories (requires --deploy-id)')
|
|
29
|
+
.option(
|
|
30
|
+
'--sync-start',
|
|
31
|
+
"Sync start scripts in deploy ID package.json with root package.json (use 'dd' as --deploy-id to sync all dd.router)",
|
|
32
|
+
)
|
|
30
33
|
.option('--purge', 'Remove deploy ID conf and all related repositories (requires --deploy-id)')
|
|
31
34
|
.option('--dev', 'Sets the development cli context')
|
|
32
35
|
.option('--default-conf', 'Create default deploy ID conf env files')
|
|
@@ -34,6 +37,17 @@ program
|
|
|
34
37
|
.description('Initializes a new Underpost project, service, or configuration.')
|
|
35
38
|
.action(Underpost.repo.new);
|
|
36
39
|
|
|
40
|
+
program
|
|
41
|
+
.command('client')
|
|
42
|
+
.argument('[deploy-id]', 'The deployment ID to build.', 'dd-default')
|
|
43
|
+
.argument('[sub-conf]', 'The sub-configuration for the build.', '')
|
|
44
|
+
.argument('[host]', 'Comma-separated hosts to filter the build.', '')
|
|
45
|
+
.argument('[path]', 'Comma-separated paths to filter the build.', '')
|
|
46
|
+
.option('--sync-env-port', 'Sync environment port assignments across all deploy IDs')
|
|
47
|
+
.option('--single-replica', 'Build single replica folders instead of full client')
|
|
48
|
+
.description('Builds client assets, single replicas, and/or syncs environment ports.')
|
|
49
|
+
.action(Underpost.repo.client);
|
|
50
|
+
|
|
37
51
|
program
|
|
38
52
|
.command('start')
|
|
39
53
|
.argument('<deploy-id>', 'The unique identifier for the deployment configuration.')
|
|
@@ -51,7 +65,7 @@ program
|
|
|
51
65
|
.command('clone')
|
|
52
66
|
.argument(`<uri>`, 'The URI of the GitHub repository (e.g., "username/repository").')
|
|
53
67
|
.option('--bare', 'Performs a bare clone, downloading only the .git files.')
|
|
54
|
-
.option('
|
|
68
|
+
.option('--g8', 'Uses the g8 repository extension for cloning.')
|
|
55
69
|
.description('Clones a specified GitHub repository into the current directory.')
|
|
56
70
|
.action(Underpost.repo.clone);
|
|
57
71
|
|
|
@@ -60,7 +74,7 @@ program
|
|
|
60
74
|
.argument('<path>', 'The absolute or relative directory path where the repository is located.')
|
|
61
75
|
.argument(`<uri>`, 'The URI of the GitHub repository (e.g., "username/repository").')
|
|
62
76
|
.description('Pulls the latest changes from a specified GitHub repository.')
|
|
63
|
-
.option('
|
|
77
|
+
.option('--g8', 'Uses the g8 repository extension for pulling.')
|
|
64
78
|
.action(Underpost.repo.pull);
|
|
65
79
|
|
|
66
80
|
program
|
|
@@ -90,6 +104,7 @@ program
|
|
|
90
104
|
'--changelog-no-hash',
|
|
91
105
|
'Excludes commit hashes from the generated changelog entries (used with --changelog-build).',
|
|
92
106
|
)
|
|
107
|
+
.option('-b', 'Shows the current Git branch name.')
|
|
93
108
|
.description('Manages commits to a GitHub repository, supporting various commit types and options.')
|
|
94
109
|
.action(Underpost.repo.commit);
|
|
95
110
|
|
|
@@ -98,7 +113,7 @@ program
|
|
|
98
113
|
.argument('<path>', 'The absolute or relative directory path of the repository.')
|
|
99
114
|
.argument(`<uri>`, 'The URI of the GitHub repository (e.g., "username/repository").')
|
|
100
115
|
.option('-f', 'Forces the push, overwriting the remote repository history.')
|
|
101
|
-
.option('
|
|
116
|
+
.option('--g8', 'Uses the g8 repository extension for pushing.')
|
|
102
117
|
.description('Pushes committed changes from a local repository to a remote GitHub repository.')
|
|
103
118
|
.action(Underpost.repo.push);
|
|
104
119
|
|
|
@@ -112,11 +127,11 @@ program
|
|
|
112
127
|
.argument('[subConf]', 'Optional: The sub configuration to set.')
|
|
113
128
|
.description('Sets environment variables and configurations related to a specific deployment ID.')
|
|
114
129
|
.action((deployId, env, subConf) => {
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
130
|
+
if (deployId === 'root') {
|
|
131
|
+
const underpostRootDeployId = Underpost.env.get('DEPLOY_ID');
|
|
132
|
+
if (underpostRootDeployId) deployId = underpostRootDeployId;
|
|
133
|
+
}
|
|
134
|
+
if (env) process.env.NODE_ENV = env;
|
|
120
135
|
loadConf(deployId, subConf);
|
|
121
136
|
});
|
|
122
137
|
|
|
@@ -556,7 +571,6 @@ program
|
|
|
556
571
|
.option('--force', 'Forces operation, overriding any warnings or conflicts.')
|
|
557
572
|
.option('--tls', 'Enables TLS for the runner execution.')
|
|
558
573
|
.option('--reset', 'Resets the runner state before execution.')
|
|
559
|
-
.option('--terminal', 'Enables terminal mode for interactive script execution.')
|
|
560
574
|
.option('--dev-proxy-port-offset <port-offset>', 'Sets a custom port offset for development proxy.')
|
|
561
575
|
.option('--host-network', 'Enables host network mode for the runner execution.')
|
|
562
576
|
.option('--requests-memory <requests-memory>', 'Requests memory limit for the runner execution.')
|
|
@@ -612,6 +626,12 @@ program
|
|
|
612
626
|
'--create-job-now',
|
|
613
627
|
'After applying cron manifests, immediately create a Job from each CronJob (forwarded to cron runner).',
|
|
614
628
|
)
|
|
629
|
+
.option(
|
|
630
|
+
'--host-aliases <host-aliases>',
|
|
631
|
+
'Adds entries to the Pod /etc/hosts via hostAliases. ' +
|
|
632
|
+
'Format: semicolon-separated entries of "ip=hostname1,hostname2" ' +
|
|
633
|
+
'(e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote,bar.remote").',
|
|
634
|
+
)
|
|
615
635
|
.description('Runs specified scripts using various runners.')
|
|
616
636
|
.action(Underpost.run.callback);
|
|
617
637
|
|
package/src/cli/monitor.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @namespace UnderpostMonitor
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { loadReplicas, pathPortAssignmentFactory } from '../server/conf.js';
|
|
7
|
+
import { loadReplicas, pathPortAssignmentFactory, loadConfServerJson, loadCronDeployEnv } from '../server/conf.js';
|
|
8
8
|
import { loggerFactory } from '../server/logger.js';
|
|
9
9
|
import axios from 'axios';
|
|
10
10
|
import fs from 'fs-extra';
|
|
@@ -71,6 +71,7 @@ class UnderpostMonitor {
|
|
|
71
71
|
commanderOptions,
|
|
72
72
|
auxRouter,
|
|
73
73
|
) {
|
|
74
|
+
loadCronDeployEnv();
|
|
74
75
|
if (!options.namespace) options.namespace = 'default';
|
|
75
76
|
if (!options.replicas) options.replicas = '1';
|
|
76
77
|
if (deployId === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`)) {
|
|
@@ -100,7 +101,7 @@ class UnderpostMonitor {
|
|
|
100
101
|
|
|
101
102
|
const confServer = loadReplicas(
|
|
102
103
|
deployId,
|
|
103
|
-
|
|
104
|
+
loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
|
|
104
105
|
);
|
|
105
106
|
|
|
106
107
|
const pathPortAssignmentData = await pathPortAssignmentFactory(deployId, router, confServer);
|
|
@@ -127,7 +128,7 @@ class UnderpostMonitor {
|
|
|
127
128
|
|
|
128
129
|
const switchTraffic = (targetTraffic) => {
|
|
129
130
|
const nextTraffic = targetTraffic ?? (traffic === 'blue' ? 'green' : 'blue');
|
|
130
|
-
// Delegate traffic switching to
|
|
131
|
+
// Delegate traffic switching to deploy implementation so behavior is consistent
|
|
131
132
|
Underpost.deploy.switchTraffic(deployId, env, nextTraffic, options.replicas, options.namespace, options);
|
|
132
133
|
// Keep local traffic in sync with the environment
|
|
133
134
|
traffic = nextTraffic;
|
|
@@ -168,9 +169,7 @@ class UnderpostMonitor {
|
|
|
168
169
|
switch (options.type) {
|
|
169
170
|
case 'blue-green':
|
|
170
171
|
default: {
|
|
171
|
-
const confServer =
|
|
172
|
-
fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'),
|
|
173
|
-
);
|
|
172
|
+
const confServer = loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`);
|
|
174
173
|
|
|
175
174
|
const namespace = options.namespace;
|
|
176
175
|
Underpost.deploy.configMap(env, namespace);
|