underpost 3.2.70 → 3.2.90

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 (60) hide show
  1. package/.github/workflows/publish.ci.yml +3 -3
  2. package/.github/workflows/release.cd.yml +1 -1
  3. package/CHANGELOG.md +1358 -1038
  4. package/CLI-HELP.md +39 -16
  5. package/README.md +3 -3
  6. package/bin/build.js +10 -4
  7. package/bin/deploy.js +18 -16
  8. package/docker-compose.yml +1 -1
  9. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  10. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  11. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  12. package/manifests/deployment/playwright/deployment.yaml +1 -1
  13. package/manifests/mongodb/kustomization.yaml +4 -1
  14. package/manifests/mongodb/statefulset.yaml +4 -0
  15. package/manifests/mongodb/storage-class.yaml +9 -2
  16. package/package.json +20 -20
  17. package/scripts/nat-iptables.sh +10 -4
  18. package/scripts/test-monitor.sh +4 -3
  19. package/src/api/core/core.controller.js +4 -65
  20. package/src/api/core/core.router.js +8 -14
  21. package/src/api/default/default.controller.js +2 -70
  22. package/src/api/default/default.router.js +7 -17
  23. package/src/api/document/document.controller.js +5 -77
  24. package/src/api/document/document.router.js +9 -13
  25. package/src/api/file/file.controller.js +9 -53
  26. package/src/api/file/file.router.js +14 -6
  27. package/src/api/test/test.controller.js +8 -53
  28. package/src/api/test/test.router.js +1 -4
  29. package/src/cli/cluster.js +771 -66
  30. package/src/cli/db.js +6 -4
  31. package/src/cli/deploy.js +1715 -168
  32. package/src/cli/docker-compose.js +19 -24
  33. package/src/cli/fs.js +0 -1
  34. package/src/cli/image.js +40 -13
  35. package/src/cli/index.js +129 -35
  36. package/src/cli/ipfs.js +82 -11
  37. package/src/cli/monitor.js +1 -1
  38. package/src/cli/release.js +4 -0
  39. package/src/cli/repository.js +14 -3
  40. package/src/cli/run.js +2253 -439
  41. package/src/cli/secrets.js +969 -0
  42. package/src/cli/ssh.js +38 -39
  43. package/src/client/components/core/Modal.js +38 -4
  44. package/src/client-builder/client-build.js +94 -11
  45. package/src/client-builder/ssr.js +27 -73
  46. package/src/db/mongo/MongoBootstrap.js +295 -54
  47. package/src/db/mongo/MongooseDB.js +47 -32
  48. package/src/index.js +1 -1
  49. package/src/server/conf.js +1307 -6
  50. package/src/server/cri.js +70 -0
  51. package/src/server/downloader.js +3 -3
  52. package/src/server/middlewares.js +152 -0
  53. package/src/server/underpost-gateway.js +1073 -0
  54. package/src/server/underpost-ingress.js +364 -0
  55. package/test/cluster-instances.test.js +435 -0
  56. package/test/deploy-node-placement.test.js +45 -0
  57. package/test/instance-traffic-plan.test.js +710 -0
  58. package/test/sops-secret-store.test.js +612 -0
  59. package/test/underpost-gateway.test.js +469 -0
  60. package/test/underpost-ingress.test.js +253 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * CRI (Container Runtime Interface) endpoint resolution shared by every layer
3
+ * that shells out to `crictl`: the cluster CLI, image management, and the
4
+ * database bootstraps. Lives under `src/server` so the db layer can import it
5
+ * without depending on the CLI god-object.
6
+ * @module src/server/cri.js
7
+ * @namespace CriEndpoint
8
+ */
9
+
10
+ import { shellExec } from './process.js';
11
+
12
+ const CRIO_SOCKET_PATH = '/var/run/crio/crio.sock';
13
+
14
+ /**
15
+ * @constant CRI_SOCKETS
16
+ * @description The CRI endpoints this platform ever targets.
17
+ * @memberof CriEndpoint
18
+ */
19
+ const CRI_SOCKETS = {
20
+ crio: `unix://${CRIO_SOCKET_PATH}`,
21
+ containerd: 'unix:///run/containerd/containerd.sock',
22
+ k3s: 'unix:///run/k3s/containerd/containerd.sock',
23
+ };
24
+
25
+ /**
26
+ * @method resolveCriSocket
27
+ * @description Resolves the CRI endpoint a `crictl` or `kubeadm` call should
28
+ * target. K3s runs its own embedded containerd; a kubeadm host uses CRI-O only
29
+ * while that socket actually exists, otherwise the host-level containerd.
30
+ *
31
+ * Detection is by socket presence, never by configuration, so a host whose
32
+ * CRI-O install was removed or stopped still resolves to a runtime that answers.
33
+ * @param {object} [options] - Resolution inputs.
34
+ * @param {boolean} [options.k3s=false] - Whether the cluster is K3s-based.
35
+ * @param {string} [options.criSocket] - Explicit endpoint override (highest precedence).
36
+ * @returns {string} CRI endpoint URI.
37
+ * @memberof CriEndpoint
38
+ */
39
+ const resolveCriSocket = (options = {}) => {
40
+ if (options?.criSocket) return options.criSocket;
41
+ if (options?.k3s) return CRI_SOCKETS.k3s;
42
+ const runtime = shellExec(`test -S ${CRIO_SOCKET_PATH} && echo crio || echo containerd`, {
43
+ stdout: true,
44
+ silent: true,
45
+ }).trim();
46
+ return runtime === 'crio' ? CRI_SOCKETS.crio : CRI_SOCKETS.containerd;
47
+ };
48
+
49
+ /**
50
+ * @method crictlCommandFactory
51
+ * @description Builds a `crictl` invocation pinned to the live CRI endpoint.
52
+ *
53
+ * Both `--runtime-endpoint` and `--image-endpoint` are passed. crictl resolves
54
+ * the two independently, and `run install-crio` writes both into
55
+ * `/etc/crictl.yaml` pointing at CRI-O; overriding only the runtime leaves image
56
+ * operations (`pull`, `rmi`, `images`) validating a `crio.sock` that no longer
57
+ * exists, which fails with "validate CRI v1 image API for endpoint".
58
+ *
59
+ * crictl is not on sudo's secure_path, hence the explicit PATH.
60
+ * @param {string} args - crictl subcommand and arguments (e.g. `pull mongo:latest`).
61
+ * @param {object} [options] - Forwarded to {@link CriEndpoint.resolveCriSocket}.
62
+ * @returns {string} Full shell command.
63
+ * @memberof CriEndpoint
64
+ */
65
+ const crictlCommandFactory = (args, options = {}) => {
66
+ const socket = resolveCriSocket(options);
67
+ return `sudo env PATH="$PATH:/usr/local/bin:/usr/bin" crictl --runtime-endpoint ${socket} --image-endpoint ${socket} ${args}`;
68
+ };
69
+
70
+ export { CRI_SOCKETS, resolveCriSocket, crictlCommandFactory };
@@ -41,18 +41,18 @@ class Downloader {
41
41
  const writer = fs.createWriteStream(fullPath);
42
42
  response.data.pipe(writer);
43
43
  writer.on('finish', () => {
44
- logger.info('Download complete. File saved at', fullPath);
44
+ logger.info('Download completet');
45
45
  return resolve(fullPath);
46
46
  });
47
47
  writer.on('error', (error) => {
48
- logger.error(error, 'Error downloading the file');
48
+ logger.error('Error downloading the file');
49
49
  // Cleanup incomplete file if possible
50
50
  if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
51
51
  return reject(error);
52
52
  });
53
53
  })
54
54
  .catch((error) => {
55
- logger.error(error, 'Error in the request');
55
+ logger.error('Error in the request');
56
56
  return reject(error);
57
57
  }),
58
58
  );
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Express middleware and controller/router helpers for engine APIs.
3
+ *
4
+ * @module src/server/middlewares.js
5
+ */
6
+
7
+ import { loggerFactory } from './logger.js';
8
+ import { moderatorGuard, adminGuard } from './auth.js';
9
+
10
+ const logger = loggerFactory(import.meta);
11
+
12
+ /**
13
+ * The public-read CORS policy: reflect the request origin (or allow any)
14
+ * and mark the resource embeddable cross-origin.
15
+ * @param {import('express').Request} req
16
+ * @param {import('express').Response} res
17
+ */
18
+ const setCrossOriginHeaders = (req, res) => {
19
+ if (req && req.headers && req.headers.origin) res.set('Access-Control-Allow-Origin', req.headers.origin);
20
+ else res.setHeader('Access-Control-Allow-Origin', '*');
21
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
22
+ };
23
+
24
+ /** Express middleware form of {@link setCrossOriginHeaders}. */
25
+ const crossOriginMiddleware = (req, res, next) => {
26
+ setCrossOriginHeaders(req, res);
27
+ next();
28
+ };
29
+
30
+ /**
31
+ * Shallow request copy with `page`/`limit` parsed to integers.
32
+ * `path` and `params` are copied explicitly because spreading an Express
33
+ * request drops prototype getters.
34
+ * @param {import('express').Request} req
35
+ */
36
+ const withParsedPagination = (req) => {
37
+ const { page, limit } = req.query;
38
+ return {
39
+ ...req,
40
+ path: req.path,
41
+ params: req.params,
42
+ query: { ...req.query, page: parseInt(page), limit: parseInt(limit) },
43
+ };
44
+ };
45
+
46
+ const sendSuccess = (res, data) => res.status(200).json({ status: 'success', data });
47
+
48
+ const sendError = (res, error, status = 400) => res.status(status).json({ status: 'error', message: error.message });
49
+
50
+ /**
51
+ * Binary response with cross-origin and content headers.
52
+ * @param {import('express').Request} req
53
+ * @param {import('express').Response} res
54
+ * @param {{ buffer: Buffer, mimetype: string, filename: string, disposition?: 'inline'|'attachment' }} blob
55
+ */
56
+ const sendBlob = (req, res, { buffer, mimetype, filename, disposition = 'inline' }) => {
57
+ setCrossOriginHeaders(req, res);
58
+ res.setHeader('Content-Type', mimetype);
59
+ res.setHeader('Content-Length', buffer.length);
60
+ res.setHeader('Content-Disposition', `${disposition}; filename="${filename}"`);
61
+ return res.status(200).end(buffer);
62
+ };
63
+
64
+ /**
65
+ * Wraps a controller body with error logging and the error response envelope.
66
+ * @param {(req, res, options) => Promise<any>} fn
67
+ * @param {{ errorStatus?: number }} [config]
68
+ */
69
+ const controllerHandler =
70
+ (fn, { errorStatus = 400 } = {}) =>
71
+ async (req, res, options) => {
72
+ try {
73
+ return await fn(req, res, options);
74
+ } catch (error) {
75
+ logger.error(error, error.stack);
76
+ return sendError(res, error, errorStatus);
77
+ }
78
+ };
79
+
80
+ /**
81
+ * Builds a controller method that delegates to a service method and wraps the
82
+ * result in the success envelope.
83
+ * @param {(req, res, options) => Promise<any>} serviceFn
84
+ * @param {{ errorStatus?: number, crossOrigin?: boolean, pagination?: boolean }} [config]
85
+ */
86
+ const serviceHandler = (serviceFn, { errorStatus = 400, crossOrigin = false, pagination = false } = {}) =>
87
+ controllerHandler(
88
+ async (req, res, options) => {
89
+ if (crossOrigin) setCrossOriginHeaders(req, res);
90
+ const result = await serviceFn(pagination ? withParsedPagination(req) : req, res, options);
91
+ return sendSuccess(res, result);
92
+ },
93
+ { errorStatus },
94
+ );
95
+
96
+ /**
97
+ * Builds a standard CRUD controller class (static post/get/put/delete) from a
98
+ * service exposing the same methods. `get` parses pagination.
99
+ * @param {{ post, get, put, delete }} service
100
+ * @param {Object<string, Function>} [extend] - Extra or overriding static handlers.
101
+ */
102
+ const buildCrudController = (service, extend = {}) => {
103
+ class CrudController {
104
+ static post = serviceHandler(service.post);
105
+ static get = serviceHandler(service.get, { pagination: true });
106
+ static put = serviceHandler(service.put);
107
+ static delete = serviceHandler(service.delete);
108
+ }
109
+ Object.assign(CrudController, extend);
110
+ return CrudController;
111
+ };
112
+
113
+ /**
114
+ * Registers the standard CRUD routes with the standard guard policy:
115
+ * public reads, moderator-guarded writes, admin-guarded collection delete.
116
+ * Custom routes must be registered before calling this (generic `/:id` routes
117
+ * capture everything).
118
+ * @param {import('express').Router} router
119
+ * @param {{ post, get, put, delete }} Controller
120
+ * @param {import('../../api/types.js').RouterOptions} options
121
+ * @param {{ readGuards?: Function[], writeGuards?: Function[], deleteAllGuards?: Function[] }} [config]
122
+ * Pass empty arrays for unguarded endpoints (e.g. player-written progress)
123
+ * or explicit guard chains (e.g. admin-only reads).
124
+ * @returns {import('express').Router}
125
+ */
126
+ const registerCrudRoutes = (router, Controller, options, { readGuards = [], writeGuards, deleteAllGuards } = {}) => {
127
+ const write = writeGuards ?? [options.authMiddleware, moderatorGuard];
128
+ const deleteAll = deleteAllGuards ?? [options.authMiddleware, adminGuard];
129
+ const handle = (method) => async (req, res) => await Controller[method](req, res, options);
130
+ router.post(`/:id`, ...write, handle('post'));
131
+ router.post(`/`, ...write, handle('post'));
132
+ router.get(`/:id`, ...readGuards, handle('get'));
133
+ router.get(`/`, ...readGuards, handle('get'));
134
+ router.put(`/:id`, ...write, handle('put'));
135
+ router.put(`/`, ...write, handle('put'));
136
+ router.delete(`/:id`, ...write, handle('delete'));
137
+ router.delete(`/`, ...deleteAll, handle('delete'));
138
+ return router;
139
+ };
140
+
141
+ export {
142
+ setCrossOriginHeaders,
143
+ crossOriginMiddleware,
144
+ withParsedPagination,
145
+ sendSuccess,
146
+ sendError,
147
+ sendBlob,
148
+ controllerHandler,
149
+ serviceHandler,
150
+ buildCrudController,
151
+ registerCrudRoutes,
152
+ };