underpost 3.2.90 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.github/workflows/ghpkg.ci.yml +7 -1
  2. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
  3. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  4. package/.github/workflows/release.cd.yml +1 -9
  5. package/CHANGELOG.md +110 -1
  6. package/CLI-HELP.md +139 -9
  7. package/README.md +5 -2
  8. package/bin/build.js +7 -5
  9. package/bin/deploy.js +1 -1
  10. package/deploy/lib/logging.sh +96 -0
  11. package/deploy/pwa-microservices-template/deploy.sh +72 -0
  12. package/deploy/release/deploy.sh +62 -0
  13. package/docker-compose.yml +1 -1
  14. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
  15. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  16. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
  17. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  18. package/package.json +5 -5
  19. package/scripts/audit-selinux.sh +64 -0
  20. package/scripts/coverall-test.sh +24 -0
  21. package/scripts/gpu-diag.sh +0 -0
  22. package/scripts/ip-info.sh +0 -0
  23. package/scripts/k3s-node-setup.sh +18 -15
  24. package/scripts/kubeadm-node-setup.sh +12 -23
  25. package/scripts/link-local-underpost-cli.sh +0 -0
  26. package/scripts/lxd-vm-setup.sh +0 -0
  27. package/scripts/maas-nat-firewalld.sh +0 -0
  28. package/scripts/nat-iptables.sh +2 -0
  29. package/scripts/rhel-grpc-setup.sh +0 -0
  30. package/scripts/rocky-kickstart.sh +25 -9
  31. package/scripts/test-monitor.sh +1 -1
  32. package/src/cli/baremetal.js +1 -2
  33. package/src/cli/cloud-init.js +1 -1
  34. package/src/cli/cluster.js +73 -68
  35. package/src/cli/db.js +9 -2
  36. package/src/cli/deploy.js +21 -5
  37. package/src/cli/docker-compose.js +1 -1
  38. package/src/cli/env.js +1 -1
  39. package/src/cli/image.js +0 -1
  40. package/src/cli/index.js +121 -9
  41. package/src/cli/lxd.js +1 -1
  42. package/src/cli/monitor.js +1 -1
  43. package/src/cli/release.js +57 -22
  44. package/src/cli/repository.js +11 -9
  45. package/src/cli/run.js +36 -9
  46. package/src/cli/ssh.js +198 -77
  47. package/src/cli/system.js +26 -13
  48. package/src/cli/test.js +1 -1
  49. package/src/cli/vultr.js +583 -0
  50. package/src/cli/wireguard.js +2125 -0
  51. package/src/client-builder/client-build.js +20 -14
  52. package/src/db/mongo/MongooseDB.js +4 -0
  53. package/src/index.js +25 -1
  54. package/src/projects/underpost/catalog-underpost.js +4 -1
  55. package/src/server/backup.js +1 -1
  56. package/src/server/conf.js +18 -108
  57. package/src/server/cron.js +249 -51
  58. package/src/server/dns.js +100 -6
  59. package/src/server/environment.js +98 -0
  60. package/src/server/forward-proxy.js +549 -0
  61. package/src/server/middlewares.js +56 -1
  62. package/src/server/process.js +0 -1
  63. package/src/server/selinux.js +185 -0
  64. package/src/server/systemd.js +205 -0
  65. package/src/server/underpost-compression.js +186 -0
  66. package/src/server/underpost-gateway.js +20 -10
  67. package/src/server/underpost-ingress.js +18 -2
  68. package/test/selinux.test.js +71 -0
  69. package/test/underpost-gateway.test.js +41 -0
  70. package/test/underpost-ingress.test.js +52 -0
  71. package/test/wireguard-edge.test.js +1177 -0
@@ -525,6 +525,7 @@ const defaultSitemapXsl = `<?xml version="1.0" encoding="UTF-8"?>
525
525
  * @param {string|number} options.split - Optional zip split size in MB.
526
526
  * @param {boolean} options.fullBuild - Whether to perform a full build.
527
527
  * @param {boolean} options.iconsBuild - Whether to build icons.
528
+ * @param {boolean} options.ssrOnly - Whether to rebuild only views defined in `conf.ssr.json`.
528
529
  * @returns {Promise<void>} - Promise that resolves when the build is complete.
529
530
  * @throws {Error} - If the build fails.
530
531
  * @memberof clientBuild
@@ -548,6 +549,7 @@ const buildClient = async (
548
549
  const packageData = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
549
550
  const acmeChallengePath = `/.well-known/acme-challenge`;
550
551
  const publicPath = `./public`;
552
+ const ssrOnly = options.ssrOnly === true;
551
553
 
552
554
  /**
553
555
  * @async
@@ -691,11 +693,11 @@ const buildClient = async (
691
693
  ? `${directory}${acmeChallengePath}`
692
694
  : `${publicPath}/${host}${acmeChallengePath}`;
693
695
 
694
- if (!enableLiveRebuild) buildAcmeChallengePath(acmeChallengeFullPath);
696
+ if (!enableLiveRebuild && !ssrOnly) buildAcmeChallengePath(acmeChallengeFullPath);
695
697
 
696
698
  if (redirect || disabledRebuild) continue;
697
699
 
698
- if (fullBuildEnabled)
700
+ if (fullBuildEnabled && !ssrOnly)
699
701
  await fullBuild({
700
702
  path,
701
703
  logger,
@@ -710,7 +712,7 @@ const buildClient = async (
710
712
  publicCopyNonExistingFiles,
711
713
  });
712
714
 
713
- if (components)
715
+ if (!ssrOnly && components)
714
716
  for (const module of Object.keys(components)) {
715
717
  if (!fs.existsSync(`${rootClientPath}/components/${module}`))
716
718
  fs.mkdirSync(`${rootClientPath}/components/${module}`, { recursive: true });
@@ -733,7 +735,7 @@ const buildClient = async (
733
735
  }
734
736
  }
735
737
 
736
- if (services) {
738
+ if (!ssrOnly && services) {
737
739
  for (const module of services) {
738
740
  if (!fs.existsSync(`${rootClientPath}/services/${module}`))
739
741
  fs.mkdirSync(`${rootClientPath}/services/${module}`, { recursive: true });
@@ -796,7 +798,9 @@ const buildClient = async (
796
798
  const swSrcPath = `./src/client/sw/core.sw.js`;
797
799
  const swPublicPath = `${rootClientPath}/sw.js`;
798
800
  const swShouldRebuild =
799
- views && !(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === swSrcPath));
801
+ !ssrOnly &&
802
+ views &&
803
+ !(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === swSrcPath));
800
804
  // Transformed SW JS is held in memory; it gets prepended with renderPayload
801
805
  // and written once below, after PRE_CACHED_RESOURCES are known.
802
806
  let swTransformedJs = '';
@@ -810,13 +814,15 @@ const buildClient = async (
810
814
  });
811
815
  }
812
816
 
813
- if (views) {
814
- if (!(
815
- enableLiveRebuild &&
816
- !options.liveClientBuildPaths.find(
817
- (p) => p.srcBuildPath.startsWith(`./src/client/ssr`) || p.srcBuildPath.slice(-9) === '.index.js',
817
+ if (!ssrOnly && views) {
818
+ if (
819
+ !(
820
+ enableLiveRebuild &&
821
+ !options.liveClientBuildPaths.find(
822
+ (p) => p.srcBuildPath.startsWith(`./src/client/ssr`) || p.srcBuildPath.slice(-9) === '.index.js',
823
+ )
818
824
  )
819
- ))
825
+ )
820
826
  for (const view of views) {
821
827
  const buildPath = `${
822
828
  rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
@@ -983,7 +989,7 @@ const buildClient = async (
983
989
  );
984
990
  }
985
991
  }
986
- if (!enableLiveRebuild && siteMapLinks.length > 0) {
992
+ if (!ssrOnly && !enableLiveRebuild && siteMapLinks.length > 0) {
987
993
  const hasSitemapTemplate = fs.existsSync(`${rootClientPath}/sitemap`);
988
994
  const sitemapBaseUrl = `https://${host}${path === '/' ? '' : path}`;
989
995
  // Create a stream to write to — omit xslUrl so we can inject a relative href below
@@ -1020,7 +1026,7 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
1020
1026
  );
1021
1027
  }
1022
1028
 
1023
- if (fullBuildEnabled && docs) {
1029
+ if (!ssrOnly && fullBuildEnabled && docs) {
1024
1030
  await buildDocs({
1025
1031
  host,
1026
1032
  path,
@@ -1128,7 +1134,7 @@ ${swTransformedJs}`,
1128
1134
  );
1129
1135
  }
1130
1136
  }
1131
- if (!enableLiveRebuild && options.buildZip) {
1137
+ if (!ssrOnly && !enableLiveRebuild && options.buildZip) {
1132
1138
  logger.warn('build zip', rootClientPath);
1133
1139
 
1134
1140
  if (!fs.existsSync('./build')) fs.mkdirSync('./build');
@@ -186,6 +186,10 @@ class MongooseDBService {
186
186
  const { ProviderSchema } = await import(`../../api/${api}/${api}.model.js`);
187
187
  const keyModel = getCapVariableName(api); // Assuming this returns a capitalized model name
188
188
  models[keyModel] = conn.model(keyModel, ProviderSchema);
189
+ // Mongoose emits 'error' on the model when an autoIndex build fails; with no
190
+ // listener attached EventEmitter rethrows and takes the whole process down.
191
+ // A stale or conflicting index must degrade to a log, not kill startup.
192
+ models[keyModel].on('error', (error) => logger.error(`${keyModel} index build failed: ${error.message}`));
189
193
  }
190
194
 
191
195
  return models;
package/src/index.js CHANGED
@@ -26,6 +26,8 @@ import UnderpostStatic from './cli/static.js';
26
26
  import UnderpostTest from './cli/test.js';
27
27
  import UnderpostRelease from './cli/release.js';
28
28
  import UnderpostSystemProvisionig from './cli/system.js';
29
+ import UnderpostVultr from './cli/vultr.js';
30
+ import UnderpostWireguard from './cli/wireguard.js';
29
31
 
30
32
  import UnderpostDns from './server/dns.js';
31
33
  import UnderpostBackup from './server/backup.js';
@@ -45,7 +47,7 @@ class Underpost {
45
47
  * @type {String}
46
48
  * @memberof Underpost
47
49
  */
48
- static version = 'v3.2.90';
50
+ static version = 'v3.3.0';
49
51
 
50
52
  /**
51
53
  * Required Node.js major version
@@ -252,6 +254,26 @@ class Underpost {
252
254
  return UnderpostSystemProvisionig.API;
253
255
  }
254
256
 
257
+ /**
258
+ * Edge hub WireGuard/HAProxy cli API
259
+ * @static
260
+ * @type {UnderpostWireguard.API}
261
+ * @memberof Underpost
262
+ */
263
+ static get wireguard() {
264
+ return UnderpostWireguard.API;
265
+ }
266
+
267
+ /**
268
+ * Vultr bandwidth guard cli API
269
+ * @static
270
+ * @type {UnderpostVultr.API}
271
+ * @memberof Underpost
272
+ */
273
+ static get vultr() {
274
+ return UnderpostVultr.API;
275
+ }
276
+
255
277
  /**
256
278
  * Dns server API
257
279
  * @static
@@ -352,6 +374,8 @@ export {
352
374
  UnderpostStartUp,
353
375
  UnderpostRelease,
354
376
  UnderpostTLS,
377
+ UnderpostVultr,
378
+ UnderpostWireguard,
355
379
  };
356
380
 
357
381
  export default Underpost;
@@ -18,7 +18,10 @@ const TEMPLATE_RESTORE_PATHS = [
18
18
  `./.github/workflows/gitlab.ci.yml`,
19
19
  `./.github/workflows/publish.ci.yml`,
20
20
  `./.github/workflows/release.cd.yml`,
21
- `./src/client/services/user/guest.service.js`,
21
+ `./deploy/lib`,
22
+ './deploy/release',
23
+ './deploy/pwa-microservices-template',
24
+ './src/client/services/user/guest.service.js',
22
25
  './src/api/user/guest.service.js',
23
26
  './src/ws/IoInterface.js',
24
27
  './src/ws/IoServer.js',
@@ -7,7 +7,7 @@
7
7
  import fs from 'fs-extra';
8
8
  import { loggerFactory } from './logger.js';
9
9
  import Underpost from '../index.js';
10
- import { loadCronDeployEnv } from './conf.js';
10
+ import { loadCronDeployEnv } from './cron.js';
11
11
 
12
12
  const logger = loggerFactory(import.meta);
13
13
 
@@ -22,6 +22,7 @@ import net from 'net';
22
22
  import crypto from 'crypto';
23
23
  import colors from 'colors';
24
24
  import { loggerFactory } from './logger.js';
25
+ import { writeEnv } from './environment.js';
25
26
  import { shellExec } from './process.js';
26
27
  import { UNDERPOST_GATEWAY, statusPageAssetPathFactory } from './underpost-gateway.js';
27
28
  import { DefaultConf } from '../../conf.js';
@@ -45,6 +46,13 @@ const logger = loggerFactory(import.meta);
45
46
  */
46
47
  const ENV_REF_PREFIX = 'env:';
47
48
 
49
+ /**
50
+ * Default deploy ID used when no deploy ID is specified.
51
+ * @constant {string}
52
+ * @memberof ServerConfBuilder
53
+ */
54
+ const DEFAULT_DEPLOY_ID = 'dd-default';
55
+
48
56
  /**
49
57
  * Resolves a standardized context key from host/path descriptors.
50
58
  * The key is used across DB, WS, mailer, and cache registries.
@@ -166,63 +174,6 @@ const getConfFolder = (deployId) => {
166
174
  : `./engine-private/conf/${deployId}`;
167
175
  };
168
176
 
169
- /**
170
- * Reads `engine-private/deploy/dd.cron` and returns the deploy-id string,
171
- * or `null` if the file does not exist or is empty.
172
- *
173
- * @method cronDeployIdResolve
174
- * @returns {string|null} The deploy-id from dd.cron, or null.
175
- * @memberof ServerConfBuilder
176
- */
177
- const cronDeployIdResolve = () => {
178
- const cronDeployFile = './engine-private/deploy/dd.cron';
179
- if (fs.existsSync(cronDeployFile)) {
180
- const id = fs.readFileSync(cronDeployFile, 'utf8').trim();
181
- return id || null;
182
- }
183
- return null;
184
- };
185
-
186
- /**
187
- * Loads the deployment-specific `.env` file referenced by `engine-private/deploy/dd.cron`
188
- * into `process.env`. Uses `NODE_ENV` to select the environment variant
189
- * (defaults to `production`).
190
- *
191
- * Safe to call multiple times; subsequent calls are no-ops once the env is loaded.
192
- *
193
- * @method loadCronDeployEnv
194
- * @memberof ServerConfBuilder
195
- */
196
- function loadCronDeployEnv() {
197
- const envName = process.env.NODE_ENV || 'production';
198
-
199
- // 1) Load dd.cron env (takes full precedence)
200
- const cronDeployId = cronDeployIdResolve();
201
- if (cronDeployId) {
202
- const cronEnvPath = `./engine-private/conf/${cronDeployId}/.env.${envName}`;
203
- if (fs.existsSync(cronEnvPath)) {
204
- const cronEnv = dotenv.parse(fs.readFileSync(cronEnvPath, 'utf8'));
205
- process.env = { ...process.env, ...cronEnv };
206
- }
207
- }
208
-
209
- // 2) Load dd.router envs — only keys not already present
210
- const routerDeployFile = './engine-private/deploy/dd.router';
211
- if (fs.existsSync(routerDeployFile)) {
212
- const routerIds = fs.readFileSync(routerDeployFile, 'utf8').trim().split(',');
213
- for (const deployId of routerIds) {
214
- const id = deployId.trim();
215
- if (!id) continue;
216
- const envPath = `./engine-private/conf/${id}/.env.${envName}`;
217
- if (!fs.existsSync(envPath)) continue;
218
- const env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
219
- for (const key of Object.keys(env)) {
220
- if (!(key in process.env)) process.env[key] = env[key];
221
- }
222
- }
223
- }
224
- }
225
-
226
177
  /**
227
178
  * Resolves the full path to a specific configuration JSON file for a deploy ID.
228
179
  * For `server` configs in development mode with a subConf, it will prefer the
@@ -280,13 +231,6 @@ const readConfJson = (deployId, confType, options = {}) => {
280
231
  return parsed;
281
232
  };
282
233
 
283
- /**
284
- * Default deploy ID used when no deploy ID is specified.
285
- * @constant {string}
286
- * @memberof ServerConfBuilder
287
- */
288
- const DEFAULT_DEPLOY_ID = 'dd-default';
289
-
290
234
  /**
291
235
  * @class Config
292
236
  * @description Manages the configuration of the server.
@@ -1431,43 +1375,6 @@ const generateSecurePassword = (length = 16) => {
1431
1375
  return chars.join('');
1432
1376
  };
1433
1377
 
1434
- /**
1435
- * @method getNpmRootPath
1436
- * @description Gets the npm root path.
1437
- * @returns {string} - The npm root path.
1438
- * @memberof ServerConfBuilder
1439
- */
1440
- const getNpmRootPath = () =>
1441
- shellExec(`npm root -g`, {
1442
- stdout: true,
1443
- disableLog: true,
1444
- silent: true,
1445
- }).trim();
1446
-
1447
- /**
1448
- * @method getUnderpostRootPath
1449
- * @description Gets the underpost root path.
1450
- * @returns {string} - The underpost root path.
1451
- * @memberof ServerConfBuilder
1452
- */
1453
- const getUnderpostRootPath = () => `${getNpmRootPath()}/underpost`;
1454
-
1455
- /**
1456
- * @method writeEnv
1457
- * @description Writes the environment variables.
1458
- * @param {string} envPath - The environment path.
1459
- * @param {object} envObj - The environment object.
1460
- * @memberof ServerConfBuilder
1461
- */
1462
- const writeEnv = (envPath, envObj) =>
1463
- fs.writeFileSync(
1464
- envPath,
1465
- Object.keys(envObj)
1466
- .map((key) => `${key}=${envObj[key]}`)
1467
- .join(`\n`),
1468
- 'utf8',
1469
- );
1470
-
1471
1378
  /**
1472
1379
  * @method buildCliDoc
1473
1380
  * @description Scrapes `node bin help` (and `node bin help <command>` for every
@@ -3028,7 +2935,9 @@ ${renderHosts}`,
3028
2935
  * Resolves the concrete deploy ids a build or conf-sync run should iterate over.
3029
2936
  *
3030
2937
  * The meta deploy id `dd` fans out to the comma separated ids declared in
3031
- * `engine-private/deploy/dd.router`; any other value is parsed as a comma separated list.
2938
+ * `engine-private/deploy/dd.router`; when that file is absent (e.g. the private
2939
+ * repository is not checked out) it falls back to {@link ServerConfBuilder.DEFAULT_DEPLOY_ID}.
2940
+ * Any other value is parsed as a comma separated list.
3032
2941
  * Entries are trimmed and empties dropped.
3033
2942
  *
3034
2943
  * @method resolveDeployList
@@ -3037,7 +2946,12 @@ ${renderHosts}`,
3037
2946
  * @memberof ServerConfBuilder
3038
2947
  */
3039
2948
  const resolveDeployList = (deployId) =>
3040
- (deployId === 'dd' ? fs.readFileSync('./engine-private/deploy/dd.router', 'utf8') : deployId)
2949
+ (deployId === 'dd'
2950
+ ? fs.existsSync('./engine-private/deploy/dd.router')
2951
+ ? fs.readFileSync('./engine-private/deploy/dd.router', 'utf8')
2952
+ : DEFAULT_DEPLOY_ID
2953
+ : deployId
2954
+ )
3041
2955
  .split(',')
3042
2956
  .map((id) => id.trim())
3043
2957
  .filter(Boolean);
@@ -3186,6 +3100,7 @@ const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-te
3186
3100
  }
3187
3101
  shellExec(`rm -rf ${toPath}/.github`);
3188
3102
  shellExec(`rm -rf ${toPath}/manifests/deployment/dd-*`);
3103
+ shellExec(`rm -rf ${toPath}/deploy`);
3189
3104
 
3190
3105
  fs.mkdirSync(`${toPath}/.github/workflows`, { recursive: true });
3191
3106
  for (const restorePath of TEMPLATE_RESTORE_PATHS) {
@@ -3388,9 +3303,6 @@ export {
3388
3303
  splitFileFactory,
3389
3304
  generateSecurePassword,
3390
3305
  resolveReplicaCount,
3391
- getNpmRootPath,
3392
- getUnderpostRootPath,
3393
- writeEnv,
3394
3306
  pathPortAssignmentFactory,
3395
3307
  deployRangePortFactory,
3396
3308
  awaitDeployMonitor,
@@ -3410,8 +3322,6 @@ export {
3410
3322
  getConfFilePath,
3411
3323
  readConfJson,
3412
3324
  DEFAULT_DEPLOY_ID,
3413
- loadCronDeployEnv,
3414
- cronDeployIdResolve,
3415
3325
  clusterContextFactory,
3416
3326
  clusterTypeFactory,
3417
3327
  exposeTcpPortsFactory,