underpost 3.2.10 → 3.2.11
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/.vscode/extensions.json +9 -9
- package/.vscode/settings.json +12 -1
- package/CHANGELOG.md +74 -1
- package/CLI-HELP.md +80 -26
- package/README.md +3 -3
- package/bin/build.js +9 -6
- package/bin/build.template.js +187 -0
- package/bin/deploy.js +29 -18
- package/conf.js +1 -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 +2 -2
- package/manifests/lxd/lxd-admin-profile.yaml +12 -3
- package/manifests/mongodb-4.4/headless-service.yaml +10 -0
- package/manifests/mongodb-4.4/kustomization.yaml +3 -1
- package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
- package/manifests/mongodb-4.4/statefulset.yaml +79 -0
- package/manifests/mongodb-4.4/storage-class.yaml +9 -0
- package/manifests/valkey/statefulset.yaml +1 -1
- package/manifests/valkey/valkey-nodeport.yaml +17 -0
- package/package.json +3 -3
- package/scripts/ipxe-setup.sh +52 -49
- package/scripts/k3s-node-setup.sh +84 -68
- package/scripts/lxd-vm-setup.sh +193 -8
- package/scripts/maas-nat-firewalld.sh +145 -0
- package/src/cli/baremetal.js +115 -93
- package/src/cli/cluster.js +548 -221
- package/src/cli/deploy.js +131 -166
- package/src/cli/fs.js +11 -3
- package/src/cli/index.js +75 -17
- package/src/cli/lxd.js +1034 -240
- package/src/cli/monitor.js +9 -3
- package/src/cli/release.js +72 -36
- package/src/cli/repository.js +10 -16
- package/src/cli/run.js +70 -53
- package/src/cli/secrets.js +11 -2
- package/src/client/components/core/Auth.js +4 -3
- package/src/client/components/core/ClientEvents.js +76 -0
- package/src/client/components/core/EventBus.js +4 -0
- package/src/client/components/core/Modal.js +82 -41
- package/src/db/DataBaseProvider.js +9 -9
- package/src/db/mariadb/MariaDB.js +2 -1
- package/src/db/mongo/MongoBootstrap.js +592 -522
- package/src/db/mongo/MongooseDB.js +19 -15
- package/src/index.js +1 -1
- package/src/server/conf.js +62 -15
- package/src/server/proxy.js +9 -2
- package/src/server/start.js +7 -3
- package/src/server/valkey.js +2 -0
- package/bin/file.js +0 -220
- package/bin/vs.js +0 -74
- package/bin/zed.js +0 -84
package/src/cli/monitor.js
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
* @namespace UnderpostMonitor
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
loadReplicas,
|
|
9
|
+
pathPortAssignmentFactory,
|
|
10
|
+
loadConfServerJson,
|
|
11
|
+
loadCronDeployEnv,
|
|
12
|
+
etcHostFactory,
|
|
13
|
+
} from '../server/conf.js';
|
|
8
14
|
import { loggerFactory } from '../server/logger.js';
|
|
9
15
|
import axios from 'axios';
|
|
10
16
|
import fs from 'fs-extra';
|
|
@@ -144,7 +150,7 @@ class UnderpostMonitor {
|
|
|
144
150
|
if (path.match('peer') || path.match('socket')) continue;
|
|
145
151
|
const urlTest = `http${env === 'development' ? '' : 's'}://${host}${path}`;
|
|
146
152
|
if (env === 'development') {
|
|
147
|
-
const { renderHosts } =
|
|
153
|
+
const { renderHosts } = etcHostFactory([host]);
|
|
148
154
|
logger.info('renderHosts', renderHosts);
|
|
149
155
|
}
|
|
150
156
|
await axios.get(urlTest, { timeout: 10000 }).catch((error) => {
|
|
@@ -200,7 +206,7 @@ class UnderpostMonitor {
|
|
|
200
206
|
let monitorPodName;
|
|
201
207
|
const monitorCallBack = (resolve, reject) => {
|
|
202
208
|
if (env === 'development') {
|
|
203
|
-
const { renderHosts } =
|
|
209
|
+
const { renderHosts } = etcHostFactory([]);
|
|
204
210
|
logger.info('renderHosts', renderHosts);
|
|
205
211
|
}
|
|
206
212
|
const envMsTimeout = Underpost.env.get(`${deployId}-${env}-monitor-ms`);
|
package/src/cli/release.js
CHANGED
|
@@ -13,6 +13,7 @@ import fs from 'fs-extra';
|
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import dotenv from 'dotenv';
|
|
15
15
|
import { pbcopy, shellCd, shellExec } from '../server/process.js';
|
|
16
|
+
import { Dns } from '../server/dns.js';
|
|
16
17
|
import { loggerFactory } from '../server/logger.js';
|
|
17
18
|
import { timer } from '../client/components/core/CommonJs.js';
|
|
18
19
|
import Underpost from '../index.js';
|
|
@@ -76,10 +77,7 @@ const buildVersionBumpTargets = () => [
|
|
|
76
77
|
{
|
|
77
78
|
dir: 'src/client/public/cyberia-docs',
|
|
78
79
|
match: /\.md$/,
|
|
79
|
-
patterns: [
|
|
80
|
-
/(\*\*(?:Current )?[Vv]ersion:\*\* )\d+\.\d+\.\d+/g,
|
|
81
|
-
/(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
|
|
82
|
-
],
|
|
80
|
+
patterns: [/(\*\*(?:Current )?[Vv]ersion:\*\* )\d+\.\d+\.\d+/g, /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g],
|
|
83
81
|
recursive: true,
|
|
84
82
|
},
|
|
85
83
|
{
|
|
@@ -99,10 +97,7 @@ const buildVersionBumpTargets = () => [
|
|
|
99
97
|
{
|
|
100
98
|
dir: 'manifests/deployment',
|
|
101
99
|
match: /deployment\.yaml$/,
|
|
102
|
-
patterns: [
|
|
103
|
-
/(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
|
|
104
|
-
/(engine\.version: )\d+\.\d+\.\d+/g,
|
|
105
|
-
],
|
|
100
|
+
patterns: [/(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g, /(engine\.version: )\d+\.\d+\.\d+/g],
|
|
106
101
|
recursive: true,
|
|
107
102
|
},
|
|
108
103
|
{
|
|
@@ -240,13 +235,72 @@ const printBumpReport = (report, { dryRun }) => {
|
|
|
240
235
|
* (e.g. overwriting package.json). Skips VSCode internals and the current process.
|
|
241
236
|
*/
|
|
242
237
|
function killDevServers() {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
for (const port of [4001, 4002, 4003, 3000]) shellExec(`node bin run kill ${port}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const TEMPLATE_PATH = '../pwa-microservices-template';
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Runs a command in an ISOLATED environment. `release build` loads the engine's
|
|
245
|
+
* `dd-cron/.env.production` (DEPLOY_ID=dd-cron, DB creds, secrets, …) into its own `process.env`,
|
|
246
|
+
* which `shellExec` children would otherwise inherit — and the template's `dotenv.config()` runs
|
|
247
|
+
* without override, so it could never reclaim those keys. `env -i` starts the child with an empty
|
|
248
|
+
* environment; only PATH/HOME-class essentials are re-added so node/npm/git still resolve. The
|
|
249
|
+
* template then reads only its own `.env`, resolving `dd-default` exactly like a fresh clone.
|
|
250
|
+
*/
|
|
251
|
+
const ISOLATED_ENV = 'env -i HOME="$HOME" PATH="$PATH" USER="$USER" LOGNAME="$LOGNAME" TERM="$TERM" LANG="$LANG"';
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Builds the pwa-microservices-template from scratch and smoke-tests its default workflow.
|
|
255
|
+
*
|
|
256
|
+
* 1. Cleans the engine, pulls latest, and rebuilds the template via `bin/build.template`.
|
|
257
|
+
* 2. Derives `.env` + `.env.example` from the template `.env.example` (DHCP host IP + file
|
|
258
|
+
* logs), both written from the same `dd-default` content so the default startup workflow
|
|
259
|
+
* bootstraps `engine-private` from scratch out of them. `.env` is rewritten because it is
|
|
260
|
+
* gitignored (git clean never removes it) and may otherwise hold a stale `dd-cron`.
|
|
261
|
+
* 3. Installs deps, then builds + runs the template `dev` server in an ISOLATED env
|
|
262
|
+
* (see `ISOLATED_ENV`) so it resolves `dd-default` like a fresh clone, and asserts the
|
|
263
|
+
* startup log is error-free.
|
|
264
|
+
*
|
|
265
|
+
* @returns {boolean} true when the template started cleanly, false otherwise.
|
|
266
|
+
*/
|
|
267
|
+
async function buildAndTestTemplate() {
|
|
268
|
+
killDevServers();
|
|
269
|
+
Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
|
|
270
|
+
shellExec(`node bin pull . ${process.env.GITHUB_USERNAME}/engine`);
|
|
271
|
+
shellExec(`npm run update:template`);
|
|
272
|
+
shellExec(`node bin run shared-dir ${TEMPLATE_PATH}`);
|
|
273
|
+
|
|
274
|
+
const dhcpHostIp = Dns.getLocalIPv4Address();
|
|
275
|
+
logger.info(`DHCP host IP for template test: ${dhcpHostIp}`);
|
|
276
|
+
let envContent = fs.readFileSync(`${TEMPLATE_PATH}/.env.example`, 'utf8');
|
|
277
|
+
if (dhcpHostIp) envContent = envContent.replace(/127\.0\.0\.1/g, dhcpHostIp);
|
|
278
|
+
envContent = envContent.replace(/^ENABLE_FILE_LOGS=.*/m, 'ENABLE_FILE_LOGS=true');
|
|
279
|
+
// fs.writeFileSync(`${TEMPLATE_PATH}/.env`, envContent, 'utf8');
|
|
280
|
+
fs.writeFileSync(`${TEMPLATE_PATH}/.env.example`, envContent, 'utf8');
|
|
281
|
+
shellExec(`cd ${TEMPLATE_PATH} && npm install`);
|
|
282
|
+
shellExec(`cd ${TEMPLATE_PATH} && node bin env clean`);
|
|
283
|
+
// Build + run in an isolated env so the template resolves dd-default from its own .env and
|
|
284
|
+
// never inherits the engine's dd-cron deploy selection. See ISOLATED_ENV above.
|
|
285
|
+
//
|
|
286
|
+
// ENABLE_FILE_LOGS must be passed inline: src/server.js builds start.js's logger at import
|
|
287
|
+
// time, before Config.build() loads the template .env, so the var has to be present in the
|
|
288
|
+
// process env from the start for logs/start.js/all.log (the success probe below) to be written.
|
|
289
|
+
shellExec(`cd ${TEMPLATE_PATH} && ${ISOLATED_ENV} npm run build`);
|
|
290
|
+
shellExec(`cd ${TEMPLATE_PATH} && ${ISOLATED_ENV} ENABLE_FILE_LOGS=true timeout 5s npm run dev`, { async: true });
|
|
291
|
+
await timer(5500);
|
|
292
|
+
|
|
293
|
+
const templateLogPath = `${TEMPLATE_PATH}/logs/start.js/all.log`;
|
|
294
|
+
const runnerResult = fs.existsSync(templateLogPath) ? fs.readFileSync(templateLogPath, 'utf8') : '';
|
|
295
|
+
logger.info('Test template runner result');
|
|
296
|
+
killDevServers();
|
|
297
|
+
shellCd(`/home/dd/engine`);
|
|
298
|
+
Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
|
|
299
|
+
if (!runnerResult || runnerResult.toLowerCase().match('error')) {
|
|
300
|
+
logger.error('Test template runner result failed');
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
250
304
|
}
|
|
251
305
|
|
|
252
306
|
/**
|
|
@@ -298,26 +352,8 @@ class UnderpostRelease {
|
|
|
298
352
|
logger.info(`Release build — bumping ${version} → ${newVersion}${dryRun ? ' (dry-run)' : ''}`);
|
|
299
353
|
|
|
300
354
|
if (!dryRun) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
shellExec(`node bin pull . ${process.env.GITHUB_USERNAME}/engine`);
|
|
304
|
-
shellExec(`npm run update:template`);
|
|
305
|
-
shellExec(`cd ../pwa-microservices-template && npm install && npm run build`);
|
|
306
|
-
console.log(fs.existsSync(`../pwa-microservices-template/engine-private/conf/dd-default`));
|
|
307
|
-
shellExec(`cd ../pwa-microservices-template && ENABLE_FILE_LOGS=true timeout 5s npm run dev`, {
|
|
308
|
-
async: true,
|
|
309
|
-
});
|
|
310
|
-
await timer(5500);
|
|
311
|
-
const templateRunnerResult = fs.readFileSync(`../pwa-microservices-template/logs/start.js/all.log`, 'utf8');
|
|
312
|
-
logger.info('Test template runner result');
|
|
313
|
-
console.log(templateRunnerResult);
|
|
314
|
-
if (!templateRunnerResult || templateRunnerResult.toLowerCase().match('error')) {
|
|
315
|
-
logger.error('Test template runner result failed');
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
killDevServers();
|
|
319
|
-
shellCd(`/home/dd/engine`);
|
|
320
|
-
Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
|
|
355
|
+
const templateOk = await buildAndTestTemplate();
|
|
356
|
+
if (!templateOk) return;
|
|
321
357
|
}
|
|
322
358
|
|
|
323
359
|
// ── Canonical version files: delegate to bumpp (package.json, package-lock.json,
|
|
@@ -424,7 +460,7 @@ class UnderpostRelease {
|
|
|
424
460
|
* Runs the pwa-microservices-template update and push flow locally.
|
|
425
461
|
*
|
|
426
462
|
* Always removes and re-clones pwa-microservices-template, then:
|
|
427
|
-
* 1. Runs update:template (node bin/
|
|
463
|
+
* 1. Runs update:template (node bin/build.template) to sync engine sources.
|
|
428
464
|
* 2. Installs dependencies and builds the template.
|
|
429
465
|
* 3. Commits and pushes to the pwa-microservices-template remote repository.
|
|
430
466
|
*
|
package/src/cli/repository.js
CHANGED
|
@@ -924,8 +924,6 @@ class UnderpostRepository {
|
|
|
924
924
|
shellExec(`cd ${privateRepoPath} && underpost pull . ${process.env.GITHUB_USERNAME}/${privateRepoName}`, {
|
|
925
925
|
silent: true,
|
|
926
926
|
});
|
|
927
|
-
shellExec(`underpost run secret`);
|
|
928
|
-
shellExec(`underpost run underpost-config`);
|
|
929
927
|
const packageJsonDeploy = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/package.json`, 'utf8'));
|
|
930
928
|
const packageJsonEngine = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
|
|
931
929
|
if (packageJsonDeploy.version !== packageJsonEngine.version) {
|
|
@@ -1051,9 +1049,9 @@ Prevent build private config repo.`,
|
|
|
1051
1049
|
*/
|
|
1052
1050
|
clean(options = { paths: [''] }) {
|
|
1053
1051
|
for (const path of options.paths) {
|
|
1054
|
-
shellExec(`cd ${path} && git reset`, { silent: true });
|
|
1055
|
-
shellExec(`cd ${path} && git checkout .`, { silent: true });
|
|
1056
|
-
shellExec(`cd ${path} && git clean -f -d`, { silent: true });
|
|
1052
|
+
shellExec(`cd ${path} && git reset`, { silentOnError: true, silent: true, disableLog: true });
|
|
1053
|
+
shellExec(`cd ${path} && git checkout .`, { silentOnError: true, silent: true, disableLog: true });
|
|
1054
|
+
shellExec(`cd ${path} && git clean -f -d`, { silentOnError: true, silent: true, disableLog: true });
|
|
1057
1055
|
}
|
|
1058
1056
|
},
|
|
1059
1057
|
|
|
@@ -1107,7 +1105,7 @@ Prevent build private config repo.`,
|
|
|
1107
1105
|
|
|
1108
1106
|
try {
|
|
1109
1107
|
// Fetch directory contents recursively
|
|
1110
|
-
const copiedFiles = await this.
|
|
1108
|
+
const copiedFiles = await this.fetchAndCopyGitHubDirectory({
|
|
1111
1109
|
apiUrl,
|
|
1112
1110
|
targetPath,
|
|
1113
1111
|
basePath: directoryPath,
|
|
@@ -1143,7 +1141,7 @@ Prevent build private config repo.`,
|
|
|
1143
1141
|
* @returns {Promise<array>} Array of copied file paths.
|
|
1144
1142
|
* @memberof UnderpostRepository
|
|
1145
1143
|
*/
|
|
1146
|
-
async
|
|
1144
|
+
async fetchAndCopyGitHubDirectory(options) {
|
|
1147
1145
|
const { apiUrl, targetPath, basePath, branch } = options;
|
|
1148
1146
|
const copiedFiles = [];
|
|
1149
1147
|
|
|
@@ -1178,14 +1176,12 @@ Prevent build private config repo.`,
|
|
|
1178
1176
|
|
|
1179
1177
|
logger.info(`Found ${contents.length} items in directory: ${basePath}`);
|
|
1180
1178
|
|
|
1181
|
-
// Process each item in the directory
|
|
1182
1179
|
for (const item of contents) {
|
|
1183
1180
|
const itemTargetPath = `${targetPath}/${item.name}`;
|
|
1184
1181
|
|
|
1185
1182
|
if (item.type === 'file') {
|
|
1186
1183
|
logger.info(`Downloading file: ${item.path}`);
|
|
1187
1184
|
|
|
1188
|
-
// Download file content
|
|
1189
1185
|
const fileResponse = await fetch(item.download_url);
|
|
1190
1186
|
if (!fileResponse.ok) {
|
|
1191
1187
|
logger.error(`Failed to download: ${item.download_url}`);
|
|
@@ -1195,16 +1191,14 @@ Prevent build private config repo.`,
|
|
|
1195
1191
|
const fileContent = await fileResponse.text();
|
|
1196
1192
|
fs.writeFileSync(itemTargetPath, fileContent);
|
|
1197
1193
|
|
|
1198
|
-
logger.info(
|
|
1194
|
+
logger.info(`Saved: ${itemTargetPath}`);
|
|
1199
1195
|
copiedFiles.push(itemTargetPath);
|
|
1200
1196
|
} else if (item.type === 'dir') {
|
|
1201
|
-
logger.info(
|
|
1197
|
+
logger.info(`Processing directory: ${item.path}`);
|
|
1202
1198
|
|
|
1203
|
-
// Create subdirectory
|
|
1204
1199
|
fs.mkdirSync(itemTargetPath, { recursive: true });
|
|
1205
1200
|
|
|
1206
|
-
|
|
1207
|
-
const subFiles = await this._fetchAndCopyGitHubDirectory({
|
|
1201
|
+
const subFiles = await this.fetchAndCopyGitHubDirectory({
|
|
1208
1202
|
apiUrl: item.url,
|
|
1209
1203
|
targetPath: itemTargetPath,
|
|
1210
1204
|
basePath: item.path,
|
|
@@ -1212,7 +1206,7 @@ Prevent build private config repo.`,
|
|
|
1212
1206
|
});
|
|
1213
1207
|
|
|
1214
1208
|
copiedFiles.push(...subFiles);
|
|
1215
|
-
logger.info(
|
|
1209
|
+
logger.info(`Completed directory: ${item.path} (${subFiles.length} files)`);
|
|
1216
1210
|
} else {
|
|
1217
1211
|
logger.warn(`Skipping unknown item type '${item.type}': ${item.path}`);
|
|
1218
1212
|
}
|
|
@@ -1384,7 +1378,7 @@ Prevent build private config repo.`,
|
|
|
1384
1378
|
}
|
|
1385
1379
|
shellExec(`cd "${repoPath}" && git config user.name '${gitUsername}'`);
|
|
1386
1380
|
shellExec(`cd "${repoPath}" && git config user.email '${gitEmail}'`);
|
|
1387
|
-
|
|
1381
|
+
shellExec(`cd "${repoPath}" && git config core.filemode false`);
|
|
1388
1382
|
if (origin) {
|
|
1389
1383
|
const currentRemote = shellExec(`cd "${repoPath}" && git remote get-url origin`, {
|
|
1390
1384
|
stdout: true,
|
package/src/cli/run.js
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
awaitDeployMonitor,
|
|
11
11
|
buildKindPorts,
|
|
12
12
|
Config,
|
|
13
|
+
cronDeployIdResolve,
|
|
14
|
+
etcHostFactory,
|
|
13
15
|
getNpmRootPath,
|
|
14
16
|
isDeployRunnerContext,
|
|
15
17
|
loadConfServerJson,
|
|
@@ -116,6 +118,7 @@ const logger = loggerFactory(import.meta);
|
|
|
116
118
|
* @property {boolean} copy - Whether to copy the command to the clipboard instead of executing it.
|
|
117
119
|
* @property {boolean} skipFullBuild - Whether to skip the full client bundle build during deployment (supported by: sync, template-deploy).
|
|
118
120
|
* @property {boolean} pullBundle - Whether to pull the bundle before running. Use together with --skip-full-build to skip the local build entirely (supported by: sync, template-deploy).
|
|
121
|
+
* @property {boolean} remove - Whether to remove/teardown resources instead of creating them (e.g. delete-expose for k3s proxy devices in dev-cluster).
|
|
119
122
|
* @memberof UnderpostRun
|
|
120
123
|
*/
|
|
121
124
|
const DEFAULT_OPTION = {
|
|
@@ -183,6 +186,7 @@ const DEFAULT_OPTION = {
|
|
|
183
186
|
copy: false,
|
|
184
187
|
skipFullBuild: false,
|
|
185
188
|
pullBundle: false,
|
|
189
|
+
remove: false,
|
|
186
190
|
};
|
|
187
191
|
|
|
188
192
|
/**
|
|
@@ -212,28 +216,39 @@ class UnderpostRun {
|
|
|
212
216
|
'dev-cluster': (path, options = DEFAULT_OPTION) => {
|
|
213
217
|
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
214
218
|
const mongoHosts = ['mongodb-0.mongodb-service'];
|
|
219
|
+
let primaryMongoHost = 'mongodb-0.mongodb-service';
|
|
215
220
|
if (!options.expose) {
|
|
216
221
|
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --reset`);
|
|
217
222
|
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''}`);
|
|
218
223
|
|
|
219
224
|
shellExec(
|
|
220
|
-
`${baseCommand} cluster${options.dev ? ' --dev' : ''} --
|
|
225
|
+
`${baseCommand} cluster${options.dev ? ' --dev' : ''} --mongodb4 --service-host ${mongoHosts.join(
|
|
221
226
|
',',
|
|
222
227
|
)} --pull-image`,
|
|
223
228
|
);
|
|
224
229
|
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --valkey --pull-image`);
|
|
225
230
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
231
|
+
if (options.k3s) {
|
|
232
|
+
if (options.remove) {
|
|
233
|
+
shellExec(`${baseCommand} lxd --delete-expose k3s-control:27017`);
|
|
234
|
+
shellExec(`${baseCommand} lxd --delete-expose k3s-control:6379`);
|
|
235
|
+
} else {
|
|
236
|
+
shellExec(`${baseCommand} lxd --expose k3s-control:27017 --node-port 32017`);
|
|
237
|
+
shellExec(`${baseCommand} lxd --expose k3s-control:6379 --node-port 32079`);
|
|
238
|
+
}
|
|
239
|
+
shellExec(`lxc config device show k3s-control`);
|
|
240
|
+
} else {
|
|
230
241
|
try {
|
|
231
|
-
const primaryPodName =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
242
|
+
const primaryPodName =
|
|
243
|
+
MongoBootstrap.getPrimaryPodName({
|
|
244
|
+
namespace: options.namespace,
|
|
245
|
+
podName: 'mongodb-0',
|
|
246
|
+
disableAuth: options.dev,
|
|
247
|
+
}) || 'mongodb-0';
|
|
248
|
+
shellExec(
|
|
249
|
+
`${baseCommand} deploy --expose --namespace ${options.namespace} --disable-update-underpost-config mongo`,
|
|
250
|
+
{ async: true },
|
|
251
|
+
);
|
|
237
252
|
shellExec(
|
|
238
253
|
`${baseCommand} deploy --expose --namespace ${options.namespace} --disable-update-underpost-config valkey`,
|
|
239
254
|
{ async: true },
|
|
@@ -244,10 +259,9 @@ class UnderpostRun {
|
|
|
244
259
|
default: primaryMongoHost,
|
|
245
260
|
});
|
|
246
261
|
}
|
|
247
|
-
|
|
248
|
-
const hostListenResult = Underpost.deploy.etcHostFactory([primaryMongoHost]);
|
|
249
|
-
logger.info(hostListenResult.renderHosts);
|
|
250
262
|
}
|
|
263
|
+
const hostListenResult = etcHostFactory([primaryMongoHost]);
|
|
264
|
+
logger.info(hostListenResult.renderHosts);
|
|
251
265
|
},
|
|
252
266
|
|
|
253
267
|
/**
|
|
@@ -532,6 +546,7 @@ class UnderpostRun {
|
|
|
532
546
|
*/
|
|
533
547
|
clean: (path = '', options = DEFAULT_OPTION) => {
|
|
534
548
|
Underpost.repo.clean({ paths: path ? path.split(',') : ['/home/dd/engine', '/home/dd/engine/engine-private'] });
|
|
549
|
+
if (options.dev) shellExec(`node bin run shared-dir ${path ? path : '/home/dd/engine'}`);
|
|
535
550
|
},
|
|
536
551
|
/**
|
|
537
552
|
* @method pull
|
|
@@ -1159,7 +1174,7 @@ EOF
|
|
|
1159
1174
|
);
|
|
1160
1175
|
}
|
|
1161
1176
|
if (options.etcHosts) {
|
|
1162
|
-
const hostListenResult =
|
|
1177
|
+
const hostListenResult = etcHostFactory(etcHosts);
|
|
1163
1178
|
logger.info(hostListenResult.renderHosts);
|
|
1164
1179
|
}
|
|
1165
1180
|
},
|
|
@@ -1531,7 +1546,8 @@ EOF`);
|
|
|
1531
1546
|
`git config user.name '${username}' && ` +
|
|
1532
1547
|
`git config user.email '${email}' && ` +
|
|
1533
1548
|
`git config credential.interactive always &&` +
|
|
1534
|
-
`git config pull.rebase false
|
|
1549
|
+
`git config pull.rebase false && ` +
|
|
1550
|
+
`git config core.filemode false`,
|
|
1535
1551
|
{
|
|
1536
1552
|
disableLog: true,
|
|
1537
1553
|
silent: true,
|
|
@@ -1908,7 +1924,7 @@ EOF`);
|
|
|
1908
1924
|
);
|
|
1909
1925
|
} else logger.error(`Service pod ${podToMonitor} failed to start in time.`);
|
|
1910
1926
|
if (options.etcHosts === true) {
|
|
1911
|
-
const hostListenResult =
|
|
1927
|
+
const hostListenResult = etcHostFactory([host]);
|
|
1912
1928
|
logger.info(hostListenResult.renderHosts);
|
|
1913
1929
|
}
|
|
1914
1930
|
},
|
|
@@ -1926,7 +1942,7 @@ EOF`);
|
|
|
1926
1942
|
const confServer = loadConfServerJson(`./engine-private/conf/${options.deployId}/conf.server.json`);
|
|
1927
1943
|
hosts.push(...Object.keys(confServer));
|
|
1928
1944
|
}
|
|
1929
|
-
const hostListenResult =
|
|
1945
|
+
const hostListenResult = etcHostFactory(hosts);
|
|
1930
1946
|
logger.info(hostListenResult.renderHosts);
|
|
1931
1947
|
},
|
|
1932
1948
|
|
|
@@ -2255,11 +2271,14 @@ EOF`);
|
|
|
2255
2271
|
port = parseInt(port);
|
|
2256
2272
|
sumPortOffSet = parseInt(sumPortOffSet);
|
|
2257
2273
|
for (const sumPort of range(0, sumPortOffSet))
|
|
2258
|
-
shellExec(
|
|
2259
|
-
|
|
2260
|
-
|
|
2274
|
+
shellExec(
|
|
2275
|
+
`PIDS=$(lsof -t -i:${parseInt(port) + parseInt(sumPort)}); [ -n "$PIDS" ] && sudo kill -9 $PIDS || true`,
|
|
2276
|
+
{
|
|
2277
|
+
silentOnError: true,
|
|
2278
|
+
},
|
|
2279
|
+
);
|
|
2261
2280
|
} else
|
|
2262
|
-
shellExec(`
|
|
2281
|
+
shellExec(`PIDS=$(lsof -t -i:${_path}); [ -n "$PIDS" ] && sudo kill -9 $PIDS || true`, {
|
|
2263
2282
|
silentOnError: true,
|
|
2264
2283
|
});
|
|
2265
2284
|
}
|
|
@@ -2308,9 +2327,10 @@ EOF`);
|
|
|
2308
2327
|
* @memberof UnderpostRun
|
|
2309
2328
|
*/
|
|
2310
2329
|
secret: (path, options = DEFAULT_OPTION) => {
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2313
|
-
|
|
2330
|
+
const cronDeployId = cronDeployIdResolve() || 'dd-cron';
|
|
2331
|
+
Underpost.secret.underpost.createFromEnvFile(
|
|
2332
|
+
`/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${options.dev ? 'development' : 'production'}`,
|
|
2333
|
+
);
|
|
2314
2334
|
},
|
|
2315
2335
|
/**
|
|
2316
2336
|
* @method underpost-config
|
|
@@ -2620,7 +2640,28 @@ EOF`;
|
|
|
2620
2640
|
},
|
|
2621
2641
|
|
|
2622
2642
|
/**
|
|
2623
|
-
* @method
|
|
2643
|
+
* @method monitor-ui
|
|
2644
|
+
* @description Installs and enables the Cockpit KVM Dashboard (cockpit, cockpit-machines, libvirt)
|
|
2645
|
+
* and opens the cockpit firewall service. With `--remove`, closes the firewall service instead.
|
|
2646
|
+
* @param {string} path - Unused.
|
|
2647
|
+
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
2648
|
+
* `options.remove` — when true, removes the cockpit firewall rule instead of adding it.
|
|
2649
|
+
* @memberof UnderpostRun
|
|
2650
|
+
*/
|
|
2651
|
+
'monitor-ui': (path, options = DEFAULT_OPTION) => {
|
|
2652
|
+
if (options.remove) {
|
|
2653
|
+
shellExec(`sudo firewall-cmd --zone=public --remove-service=cockpit --permanent`);
|
|
2654
|
+
shellExec(`sudo firewall-cmd --reload`);
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
shellExec(`sudo dnf install -y cockpit cockpit-machines libvirt`);
|
|
2658
|
+
shellExec(`sudo systemctl enable --now cockpit.socket libvirtd`);
|
|
2659
|
+
shellExec(`sudo firewall-cmd --permanent --add-service=cockpit`);
|
|
2660
|
+
shellExec(`sudo firewall-cmd --reload`);
|
|
2661
|
+
},
|
|
2662
|
+
|
|
2663
|
+
/**
|
|
2664
|
+
* @method shared-dir
|
|
2624
2665
|
* @description Run once for initial shared-directory setup. Creates the group, adds the user,
|
|
2625
2666
|
* creates the directory, sets ownership, applies the SGID bit, and configures default ACLs so
|
|
2626
2667
|
* all future files inside the directory automatically inherit group write permissions.
|
|
@@ -2631,7 +2672,7 @@ EOF`;
|
|
|
2631
2672
|
* Key fields: `options.user` (default `'admin'`), `options.group` (default `'engine-dev'`).
|
|
2632
2673
|
* @memberof UnderpostRun
|
|
2633
2674
|
*/
|
|
2634
|
-
'
|
|
2675
|
+
'shared-dir': (path = '/home/dd/engine', options = DEFAULT_OPTION) => {
|
|
2635
2676
|
const dir = path || '/home/dd/engine';
|
|
2636
2677
|
const user = options.user || 'admin';
|
|
2637
2678
|
const group = options.group || 'engine-dev';
|
|
@@ -2648,30 +2689,6 @@ EOF`;
|
|
|
2648
2689
|
|
|
2649
2690
|
logger.info(`[setup-shared-dir] Shared directory setup complete: ${dir}`);
|
|
2650
2691
|
},
|
|
2651
|
-
|
|
2652
|
-
/**
|
|
2653
|
-
* @method reload-shared-dir
|
|
2654
|
-
* @description Re-applies recursive permissions and ACLs to repair permission drift on an
|
|
2655
|
-
* already-configured shared directory. Does **not** recreate the group, add users, or modify
|
|
2656
|
-
* ownership. Use this after VS Code permission errors or when existing files lose group write
|
|
2657
|
-
* access due to tool or process interference.
|
|
2658
|
-
* @param {string} path - Target directory to repair (defaults to `/home/dd/engine`).
|
|
2659
|
-
* Customise via the `path` argument or leave empty to use the default.
|
|
2660
|
-
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
2661
|
-
* Key fields: `options.group` (default `'engine-dev'`).
|
|
2662
|
-
* @memberof UnderpostRun
|
|
2663
|
-
*/
|
|
2664
|
-
'reload-shared-dir': (path = '/home/dd/engine', options = DEFAULT_OPTION) => {
|
|
2665
|
-
const dir = path || '/home/dd/engine';
|
|
2666
|
-
const group = options.group || 'engine-dev';
|
|
2667
|
-
|
|
2668
|
-
logger.info(`[reload-shared-dir] dir=${dir} group=${group}`);
|
|
2669
|
-
|
|
2670
|
-
shellExec(`sudo chmod -R 2775 ${dir}`);
|
|
2671
|
-
shellExec(`sudo setfacl -R -m g:${group}:rwx ${dir}`);
|
|
2672
|
-
|
|
2673
|
-
logger.info(`[reload-shared-dir] Shared directory permissions reloaded: ${dir}`);
|
|
2674
|
-
},
|
|
2675
2692
|
};
|
|
2676
2693
|
|
|
2677
2694
|
static API = {
|
|
@@ -2728,14 +2745,14 @@ EOF`;
|
|
|
2728
2745
|
if (options.replicas === '' || options.replicas === null || options.replicas === undefined)
|
|
2729
2746
|
options.replicas = 1;
|
|
2730
2747
|
options.npmRoot = npmRoot;
|
|
2731
|
-
logger.info(
|
|
2748
|
+
logger.info(`Executing runner`, { runner, namespace: options.namespace });
|
|
2732
2749
|
if (!Underpost.run.RUNNERS.includes(runner)) throw new Error(`Runner not found: ${runner}`);
|
|
2733
2750
|
const result = await Underpost.run.CALL(runner, path, options);
|
|
2734
2751
|
return result;
|
|
2735
2752
|
} catch (error) {
|
|
2736
2753
|
console.log(error);
|
|
2737
2754
|
logger.error(error);
|
|
2738
|
-
|
|
2755
|
+
process.exit(1);
|
|
2739
2756
|
}
|
|
2740
2757
|
},
|
|
2741
2758
|
};
|
package/src/cli/secrets.js
CHANGED
|
@@ -26,17 +26,26 @@ class UnderpostSecret {
|
|
|
26
26
|
* @memberof UnderpostSecret
|
|
27
27
|
*/
|
|
28
28
|
underpost: {
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @method createFromEnvFile
|
|
31
|
+
* @description Reads application secrets from a .env file and writes them to the underpost .env file. Used for local development and testing.
|
|
32
|
+
* @param {string} envPath - The path to the .env file to read secrets from. Defaults to './.env'.
|
|
33
|
+
* @memberof UnderpostSecret
|
|
34
|
+
*/
|
|
35
|
+
createFromEnvFile(envPath = './.env') {
|
|
30
36
|
Underpost.env.clean();
|
|
31
37
|
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
32
38
|
for (const key of Object.keys(envObj)) {
|
|
33
39
|
Underpost.env.set(key, envObj[key]);
|
|
34
40
|
}
|
|
35
41
|
},
|
|
36
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* @method createFromContainerEnv
|
|
44
|
+
* @description Reads application secrets from process.env (injected via envFrom: secretRef)
|
|
37
45
|
* and writes them to the underpost .env file, filtering out known system and
|
|
38
46
|
* Kubernetes-injected environment variables. Replaces the fragile shell-based
|
|
39
47
|
* `printenv | grep -vE` pattern with a maintainable Node.js blocklist.
|
|
48
|
+
* @memberof UnderpostSecret
|
|
40
49
|
*/
|
|
41
50
|
createFromContainerEnv() {
|
|
42
51
|
Underpost.env.clean();
|
|
@@ -319,10 +319,11 @@ class Auth {
|
|
|
319
319
|
// Close any open login/signup modals
|
|
320
320
|
if (s(`.modal-log-in`)) s(`.btn-close-modal-log-in`).click();
|
|
321
321
|
if (s(`.modal-sign-up`)) s(`.btn-close-modal-sign-up`).click();
|
|
322
|
-
if (!s(`.main-body-btn-ui-open`).classList.contains('hide'))
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
if (!s(`.main-body-btn-ui-open`).classList.contains('hide')) s(`.main-body-btn-ui-open`).click();
|
|
323
|
+
if (!s(`.main-body-btn-ui-bar-custom-open`).classList.contains('hide')) {
|
|
324
|
+
SearchBox.Data.skipOpen = true;
|
|
325
325
|
s(`.main-body-btn-ui-bar-custom-open`).click();
|
|
326
|
+
}
|
|
326
327
|
});
|
|
327
328
|
}
|
|
328
329
|
|
|
@@ -45,6 +45,79 @@ const AppointmentEventType = {
|
|
|
45
45
|
submitted: 'appointment:submitted',
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
const ModalEventType = {
|
|
49
|
+
close: 'modal:close',
|
|
50
|
+
menu: 'modal:menu',
|
|
51
|
+
collapseMenu: 'modal:collapse-menu',
|
|
52
|
+
extendMenu: 'modal:extend-menu',
|
|
53
|
+
dragEnd: 'modal:drag-end',
|
|
54
|
+
observer: 'modal:observer',
|
|
55
|
+
click: 'modal:click',
|
|
56
|
+
expandUi: 'modal:expand-ui',
|
|
57
|
+
barUiOpen: 'modal:bar-ui-open',
|
|
58
|
+
barUiClose: 'modal:bar-ui-close',
|
|
59
|
+
reload: 'modal:reload',
|
|
60
|
+
home: 'modal:home',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const ModalListenerChannels = {
|
|
64
|
+
onCloseListener: ModalEventType.close,
|
|
65
|
+
onMenuListener: ModalEventType.menu,
|
|
66
|
+
onCollapseMenuListener: ModalEventType.collapseMenu,
|
|
67
|
+
onExtendMenuListener: ModalEventType.extendMenu,
|
|
68
|
+
onDragEndListener: ModalEventType.dragEnd,
|
|
69
|
+
onObserverListener: ModalEventType.observer,
|
|
70
|
+
onClickListener: ModalEventType.click,
|
|
71
|
+
onExpandUiListener: ModalEventType.expandUi,
|
|
72
|
+
onBarUiOpen: ModalEventType.barUiOpen,
|
|
73
|
+
onBarUiClose: ModalEventType.barUiClose,
|
|
74
|
+
onReloadModalListener: ModalEventType.reload,
|
|
75
|
+
onHome: ModalEventType.home,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const createModalEventChannel = (bus, type) => {
|
|
79
|
+
const busKey = (key) => `${type}::${key}`;
|
|
80
|
+
return new Proxy(
|
|
81
|
+
{},
|
|
82
|
+
{
|
|
83
|
+
get(_target, prop) {
|
|
84
|
+
if (typeof prop === 'symbol') return undefined;
|
|
85
|
+
const key = busKey(prop);
|
|
86
|
+
if (!bus.has(key)) return undefined;
|
|
87
|
+
return (detail) => bus.emitKey(key, detail);
|
|
88
|
+
},
|
|
89
|
+
set(_target, prop, value) {
|
|
90
|
+
if (typeof prop !== 'symbol' && typeof value === 'function') bus.on(type, value, { key: busKey(prop) });
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
93
|
+
deleteProperty(_target, prop) {
|
|
94
|
+
if (typeof prop !== 'symbol') bus.off(busKey(prop));
|
|
95
|
+
return true;
|
|
96
|
+
},
|
|
97
|
+
has(_target, prop) {
|
|
98
|
+
return typeof prop !== 'symbol' && bus.has(busKey(prop));
|
|
99
|
+
},
|
|
100
|
+
ownKeys() {
|
|
101
|
+
const prefix = `${type}::`;
|
|
102
|
+
return bus.keysOf(type).map((key) => String(key).slice(prefix.length));
|
|
103
|
+
},
|
|
104
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
105
|
+
if (typeof prop !== 'symbol' && bus.has(busKey(prop)))
|
|
106
|
+
return { enumerable: true, configurable: true, writable: true, value: undefined };
|
|
107
|
+
return undefined;
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// One EventBus per modal id, surfaced through the legacy channel names.
|
|
114
|
+
const createModalEvents = () => {
|
|
115
|
+
const bus = new EventBus();
|
|
116
|
+
const channels = {};
|
|
117
|
+
for (const [name, type] of Object.entries(ModalListenerChannels)) channels[name] = createModalEventChannel(bus, type);
|
|
118
|
+
return { bus, channels };
|
|
119
|
+
};
|
|
120
|
+
|
|
48
121
|
const authLoginEvents = new EventBus();
|
|
49
122
|
const authLogoutEvents = new EventBus();
|
|
50
123
|
const authSignupEvents = new EventBus();
|
|
@@ -70,6 +143,9 @@ export {
|
|
|
70
143
|
KeyboardEventType,
|
|
71
144
|
AccountEventType,
|
|
72
145
|
AppointmentEventType,
|
|
146
|
+
ModalEventType,
|
|
147
|
+
ModalListenerChannels,
|
|
148
|
+
createModalEvents,
|
|
73
149
|
authLoginEvents,
|
|
74
150
|
authLogoutEvents,
|
|
75
151
|
authSignupEvents,
|