underpost 2.8.882 → 2.8.884

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.
@@ -1,32 +1,37 @@
1
+ /**
2
+ * @namespace Runtime
3
+ * @description The main runtime orchestrator responsible for reading configuration,
4
+ * initializing services (Prometheus, Ports, DB, Mailer), and building the
5
+ * specific server runtime for each host/path (e.g., nodejs, lampp).
6
+ */
7
+
1
8
  import fs from 'fs-extra';
2
- import express from 'express';
3
9
  import dotenv from 'dotenv';
4
- import fileUpload from 'express-fileupload';
5
- import swaggerUi from 'swagger-ui-express';
6
10
  import * as promClient from 'prom-client';
7
- import compression from 'compression';
8
11
 
9
12
  import UnderpostStartUp from './start.js';
10
- import { createServer } from 'http';
11
- import { loggerFactory, loggerMiddleware } from './logger.js';
12
- import { getCapVariableName, newInstance } from '../client/components/core/CommonJs.js';
13
- import { MailerProvider } from '../mailer/MailerProvider.js';
14
- import { DataBaseProvider } from '../db/DataBaseProvider.js';
15
- import { createPeerServer } from './peer.js';
13
+ import { loggerFactory } from './logger.js';
14
+ import { newInstance } from '../client/components/core/CommonJs.js';
16
15
  import { Lampp } from '../runtime/lampp/Lampp.js';
17
- import { Xampp } from '../runtime/xampp/Xampp.js';
18
- import { createValkeyConnection } from './valkey.js';
19
- import { applySecurity, authMiddlewareFactory } from './auth.js';
20
16
  import { getInstanceContext } from './conf.js';
21
- import { ssrMiddlewareFactory } from './ssr.js';
17
+
18
+ import ExpressService from '../runtime/express/Express.js';
22
19
 
23
20
  dotenv.config();
24
21
 
25
22
  const logger = loggerFactory(import.meta);
26
23
 
24
+ /**
25
+ * Reads server configurations, sets up Prometheus metrics, and iterates through
26
+ * all defined hosts and paths to build and start the corresponding runtime instances.
27
+ *
28
+ * @memberof Runtime
29
+ * @returns {Promise<void>}
30
+ */
27
31
  const buildRuntime = async () => {
28
32
  const deployId = process.env.DEPLOY_ID;
29
33
 
34
+ // 1. Initialize Prometheus Metrics
30
35
  const collectDefaultMetrics = promClient.collectDefaultMetrics;
31
36
  collectDefaultMetrics();
32
37
 
@@ -39,12 +44,17 @@ const buildRuntime = async () => {
39
44
  const requestCounter = new promClient.Counter(promCounterOption);
40
45
  const initPort = parseInt(process.env.PORT) + 1;
41
46
  let currentPort = initPort;
47
+
48
+ // 2. Load Configuration
42
49
  const confServer = JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'));
43
50
  const confSSR = JSON.parse(fs.readFileSync(`./conf/conf.ssr.json`, 'utf8'));
44
51
  const singleReplicaHosts = [];
52
+
53
+ // 3. Iterate through hosts and paths
45
54
  for (const host of Object.keys(confServer)) {
46
55
  if (singleReplicaHosts.length > 0)
47
56
  currentPort += singleReplicaHosts.reduce((accumulator, currentValue) => accumulator + currentValue.replicas, 0);
57
+
48
58
  const rootHostPath = `/public/${host}`;
49
59
  for (const path of Object.keys(confServer[host])) {
50
60
  confServer[host][path].port = newInstance(currentPort);
@@ -66,6 +76,7 @@ const buildRuntime = async () => {
66
76
  apiBaseHost,
67
77
  } = confServer[host][path];
68
78
 
79
+ // Calculate context data
69
80
  const { redirectTarget, singleReplicaHost } = await getInstanceContext({
70
81
  redirect,
71
82
  singleReplicaHosts,
@@ -92,203 +103,37 @@ const buildRuntime = async () => {
92
103
 
93
104
  switch (runtime) {
94
105
  case 'nodejs':
95
- const app = express();
96
-
97
- app.use((req, res, next) => {
98
- // const info = `${req.headers.host}${req.url}`;
99
- return next();
100
- });
101
-
102
- if (process.env.NODE_ENV === 'production') app.set('trust proxy', true);
103
-
104
- app.use((req, res, next) => {
105
- requestCounter.inc({
106
- instance: `${host}:${port}${path}`,
107
- method: req.method,
108
- status_code: res.statusCode,
109
- });
110
- // decodeURIComponent(req.url)
111
- return next();
112
- });
113
-
114
- app.get(`${path === '/' ? '' : path}/metrics`, async (req, res) => {
115
- res.set('Content-Type', promClient.register.contentType);
116
- return res.end(await promClient.register.metrics());
117
- });
118
-
119
- // set logger
120
- app.use(loggerMiddleware(import.meta));
121
-
122
- // js src compression
123
- app.use(compression({ filter: shouldCompress }));
124
- function shouldCompress(req, res) {
125
- if (req.headers['x-no-compression']) {
126
- // don't compress responses with this request header
127
- return false;
128
- }
129
-
130
- // fallback to standard filter function
131
- return compression.filter(req, res);
132
- }
133
-
134
- // parse requests of content-type - application/json
135
- app.use(express.json({ limit: '100MB' }));
136
-
137
- // parse requests of content-type - application/x-www-form-urlencoded
138
- app.use(express.urlencoded({ extended: true, limit: '20MB' }));
139
-
140
- // file upload middleware
141
- app.use(fileUpload());
142
-
143
- // json formatted response
144
- if (process.env.NODE_ENV === 'development') app.set('json spaces', 2);
145
-
146
- // lang handling middleware
147
- app.use(function (req, res, next) {
148
- const lang = req.headers['accept-language'] || 'en';
149
- if (typeof lang === 'string' && lang.toLowerCase().match('es')) {
150
- req.lang = 'es';
151
- } else req.lang = 'en';
152
- return next();
106
+ // The devApiPort is used for development CORS origin calculation
107
+ // It needs to account for the current port and potential peer server increment
108
+ const devApiPort = currentPort + (peer ? 2 : 1);
109
+
110
+ logger.info('Build nodejs server runtime', `${host}${path}:${port}`);
111
+
112
+ const { portsUsed } = await ExpressService.createApp({
113
+ host,
114
+ path,
115
+ port,
116
+ client,
117
+ apis,
118
+ origins,
119
+ directory,
120
+ ws,
121
+ mailer,
122
+ db,
123
+ redirect,
124
+ peer,
125
+ valkey,
126
+ apiBaseHost,
127
+ devApiPort, // Pass the dynamically calculated dev API port
128
+ redirectTarget,
129
+ rootHostPath,
130
+ confSSR,
131
+ promRequestCounter: requestCounter,
132
+ promRegister: promClient.register,
153
133
  });
154
134
 
155
- // instance public static
156
- app.use('/', express.static(directory ? directory : `.${rootHostPath}`));
157
- if (process.argv.includes('static')) {
158
- logger.info('Build static server runtime', `${host}${path}`);
159
- currentPort += 2;
160
- const staticPort = newInstance(currentPort);
161
- await UnderpostStartUp.API.listenPortController(app, staticPort, runningData);
162
- currentPort++;
163
- continue;
164
- }
165
-
166
- // Flag swagger requests before security middleware is applied
167
- const swaggerJsonPath = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
168
- const swaggerPath = `${path === '/' ? `/api-docs` : `${path}/api-docs`}`;
169
- if (fs.existsSync(swaggerJsonPath))
170
- app.use(swaggerPath, (req, res, next) => {
171
- res.locals.isSwagger = true;
172
- next();
173
- });
174
-
175
- // security
176
- applySecurity(app, {
177
- origin: origins.concat(
178
- apis && process.env.NODE_ENV === 'development' ? [`http://localhost:${currentPort + 2}`] : [],
179
- ),
180
- });
181
-
182
- if (redirect) {
183
- app.use(function (req = express.Request, res = express.Response, next = express.NextFunction) {
184
- if (process.env.NODE_ENV === 'production' && !req.url.startsWith(`/.well-known/acme-challenge`))
185
- return res.status(302).redirect(redirectTarget + req.url);
186
- // if (!req.url.startsWith(`/.well-known/acme-challenge`)) return res.status(302).redirect(redirect);
187
- return next();
188
- });
189
- // app.use(
190
- // '*',
191
- // createProxyMiddleware({
192
- // target: redirect,
193
- // changeOrigin: true,
194
- // }),
195
- // );
196
-
197
- await UnderpostStartUp.API.listenPortController(app, port, runningData);
198
- break;
199
- }
200
- // instance server
201
- const server = createServer({}, app);
202
- if (peer) currentPort++;
203
-
204
- if (!apiBaseHost) {
205
- if (fs.existsSync(swaggerJsonPath)) {
206
- const swaggerInstance =
207
- (swaggerDoc) =>
208
- (...args) =>
209
- swaggerUi.setup(swaggerDoc)(...args);
210
- const swaggerDoc = JSON.parse(fs.readFileSync(swaggerJsonPath, 'utf8'));
211
- const swaggerPath = `${path === '/' ? `/api-docs` : `${path}/api-docs`}`;
212
- app.use(swaggerPath, swaggerUi.serve, swaggerInstance(swaggerDoc));
213
- }
214
-
215
- if (db && apis) await DataBaseProvider.load({ apis, host, path, db });
216
-
217
- // valkey server
218
- if (valkey) await createValkeyConnection({ host, path }, valkey);
219
-
220
- if (mailer) {
221
- const mailerSsrConf = confSSR[getCapVariableName(client)];
222
- await MailerProvider.load({
223
- id: `${host}${path}`,
224
- meta: `mailer-${host}${path}`,
225
- host,
226
- path,
227
- ...mailer,
228
- templates: mailerSsrConf ? mailerSsrConf.mailer : {},
229
- });
230
- }
231
- if (apis && apis.length > 0) {
232
- const authMiddleware = authMiddlewareFactory({ host, path });
233
- const apiPath = `${path === '/' ? '' : path}/${process.env.BASE_API}`;
234
- for (const api of apis)
235
- await (async () => {
236
- logger.info(`Build api server`, `${host}${apiPath}/${api}`);
237
- const { ApiRouter } = await import(`../api/${api}/${api}.router.js`);
238
- const router = ApiRouter({ host, path, apiPath, mailer, db, authMiddleware, origins });
239
- // router.use(cors({ origin: origins }));
240
- // logger.info('Load api router', { host, path: apiPath, api });
241
- app.use(`${apiPath}/${api}`, router);
242
- })();
243
- }
244
-
245
- if (ws)
246
- await (async () => {
247
- const { createIoServer } = await import(`../ws/${ws}/${ws}.ws.server.js`);
248
- // logger.info('Load socket.io ws router', { host, ws });
249
- // start socket.io
250
- const { options, meta } = await createIoServer(server, {
251
- host,
252
- path,
253
- db,
254
- port,
255
- origins,
256
- });
257
- await UnderpostStartUp.API.listenPortController(UnderpostStartUp.API.listenServerFactory(), port, {
258
- runtime: 'nodejs',
259
- client: null,
260
- host,
261
- path: options.path,
262
- meta,
263
- });
264
- })();
265
-
266
- if (peer) {
267
- const peerPort = newInstance(currentPort);
268
- const { options, meta, peerServer } = await createPeerServer({
269
- port: peerPort,
270
- devPort: port,
271
- origins,
272
- host,
273
- path,
274
- });
275
-
276
- await UnderpostStartUp.API.listenPortController(peerServer, peerPort, {
277
- runtime: 'nodejs',
278
- client: null,
279
- host,
280
- path: options.path,
281
- meta,
282
- });
283
- }
284
- }
285
-
286
- // load ssr
287
- const ssr = await ssrMiddlewareFactory({ app, directory, rootHostPath, path });
288
- for (const [_, ssrMiddleware] of Object.entries(ssr)) app.use(ssrMiddleware);
289
-
290
- await UnderpostStartUp.API.listenPortController(server, port, runningData);
291
-
135
+ // Increment currentPort by any additional ports used by the service (e.g., PeerServer port)
136
+ currentPort += portsUsed;
292
137
  break;
293
138
 
294
139
  case 'lampp':
@@ -311,26 +156,6 @@ const buildRuntime = async () => {
311
156
  );
312
157
  }
313
158
  break;
314
- case 'xampp':
315
- {
316
- const { disabled } = await Xampp.createApp({
317
- port,
318
- host,
319
- path,
320
- directory,
321
- rootHostPath,
322
- redirect,
323
- redirectTarget,
324
- resetRouter: currentPort === initPort,
325
- });
326
- if (disabled) continue;
327
- await UnderpostStartUp.API.listenPortController(
328
- UnderpostStartUp.API.listenServerFactory(),
329
- port,
330
- runningData,
331
- );
332
- }
333
- break;
334
159
  default:
335
160
  break;
336
161
  }
@@ -338,7 +163,6 @@ const buildRuntime = async () => {
338
163
  }
339
164
  }
340
165
 
341
- if (Xampp.enabled() && Xampp.router) Xampp.initService();
342
166
  if (Lampp.enabled() && Lampp.router) Lampp.initService();
343
167
 
344
168
  UnderpostStartUp.API.logRuntimeRouter();
@@ -1,3 +0,0 @@
1
- const Nginx = {};
2
-
3
- export { Nginx };
@@ -1,83 +0,0 @@
1
- import fs from 'fs-extra';
2
- import { shellExec, getRootDirectory } from '../../server/process.js';
3
-
4
- const Xampp = {
5
- ports: [],
6
- initService: async function (options = { daemon: false }) {
7
- let cmd;
8
- // windows
9
- fs.writeFileSync(
10
- `C:/xampp/apache/conf/httpd.conf`,
11
- fs.readFileSync(`C:/xampp/apache/conf/httpd.template.conf`, 'utf8').replace(`Listen 80`, ``),
12
- 'utf8',
13
- );
14
- fs.writeFileSync(`C:/xampp/apache/conf/extra/httpd-ssl.conf`, this.router || '', 'utf8');
15
- cmd = `C:/xampp/xampp_stop.exe`;
16
- shellExec(cmd);
17
- cmd = `C:/xampp/xampp_start.exe`;
18
- if (this.router) fs.writeFileSync(`./tmp/xampp-router.conf`, this.router, 'utf-8');
19
- shellExec(cmd);
20
- },
21
- enabled: () => fs.existsSync(`C:/xampp/apache/conf/httpd.conf`),
22
- appendRouter: function (render) {
23
- if (!this.router) {
24
- if (fs.existsSync(`./tmp/xampp-router.conf`))
25
- return (this.router = fs.readFileSync(`./tmp/xampp-router.conf`, 'utf-8')) + render;
26
- return (this.router = render);
27
- }
28
- return (this.router += render);
29
- },
30
- removeRouter: function () {
31
- this.router = undefined;
32
- if (fs.existsSync(`./tmp/xampp-router.conf`)) fs.rmSync(`./tmp/xampp-router.conf`);
33
- },
34
- createApp: async ({ port, host, path, directory, rootHostPath, redirect, redirectTarget, resetRouter }) => {
35
- if (!Xampp.enabled()) {
36
- return { disabled: true };
37
- }
38
- if (!Xampp.ports.includes(port)) Xampp.ports.push(port);
39
- if (resetRouter) Xampp.removeRouter();
40
- Xampp.appendRouter(`
41
- Listen ${port}
42
-
43
- <VirtualHost *:${port}>
44
- DocumentRoot "${directory ? directory : `${getRootDirectory()}${rootHostPath}`}"
45
- ServerName ${host}:${port}
46
-
47
- <Directory "${directory ? directory : `${getRootDirectory()}${rootHostPath}`}">
48
- Options Indexes FollowSymLinks MultiViews
49
- AllowOverride All
50
- Require all granted
51
- </Directory>
52
-
53
- ${
54
- redirect
55
- ? `
56
- RewriteEngine on
57
-
58
- RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge
59
- RewriteRule ^(.*)$ ${redirectTarget}%{REQUEST_URI} [R=302,L]
60
- `
61
- : ''
62
- }
63
-
64
- ErrorDocument 400 ${path === '/' ? '' : path}/400.html
65
- ErrorDocument 404 ${path === '/' ? '' : path}/400.html
66
- ErrorDocument 500 ${path === '/' ? '' : path}/500.html
67
- ErrorDocument 502 ${path === '/' ? '' : path}/500.html
68
- ErrorDocument 503 ${path === '/' ? '' : path}/500.html
69
- ErrorDocument 504 ${path === '/' ? '' : path}/500.html
70
-
71
- </VirtualHost>
72
-
73
- `);
74
- // ERR too many redirects:
75
- // Check: SELECT * FROM database.wp_options where option_name = 'siteurl' or option_name = 'home';
76
- // Check: wp-config.php
77
- // if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
78
- // $_SERVER['HTTPS'] = 'on';
79
- // }
80
- },
81
- };
82
-
83
- export { Xampp };