roster-server 1.8.0 → 1.8.4

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.
@@ -3,7 +3,8 @@
3
3
  "allow": [
4
4
  "Bash(node:*)",
5
5
  "Bash(timeout:*)",
6
- "Bash(rm:*)"
6
+ "Bash(rm:*)",
7
+ "Bash(git checkout:*)"
7
8
  ],
8
9
  "deny": []
9
10
  }
package/README.md CHANGED
@@ -162,6 +162,31 @@ When creating a new `RosterServer` instance, you can pass the following options:
162
162
  - `wwwPath` (string): Path to your `www` directory containing your sites.
163
163
  - `greenlockStorePath` (string): Directory for Greenlock configuration.
164
164
  - `staging` (boolean): Set to `true` to use Let's Encrypt's staging environment (for testing).
165
+ - `local` (boolean): Set to `true` to run in local development mode.
166
+
167
+ ## 🏠 Local Development Mode
168
+
169
+ For local development and testing, you can run RosterServer in local mode by setting `local: true`. This mode is perfect for development environments where you don't need SSL certificates or production features.
170
+
171
+ When `{ local: true }` is enabled, RosterServer **Skips SSL/HTTPS**: Runs pure HTTP servers instead of HTTPS.
172
+
173
+ ### Setting Up Local Mode
174
+
175
+ ```javascript
176
+ const server = new Roster({
177
+ wwwPath: '/srv/www',
178
+ local: true // Enable local development mode
179
+ });
180
+ server.start();
181
+ ```
182
+
183
+ ### Port Assignment
184
+
185
+ In local mode, domains are automatically assigned ports starting from 3000:
186
+
187
+ - `example.com` → `http://localhost:3000`
188
+ - `api.example.com` → `http://localhost:3001`
189
+ - And so on...
165
190
 
166
191
  ## 🧂 A Touch of Magic
167
192
 
@@ -2,6 +2,7 @@ const Roster = require('../../index.js');
2
2
  const path = require('path');
3
3
 
4
4
  const roster = new Roster({
5
+ local: true,
5
6
  email: 'admin@example.com',
6
7
  wwwPath: path.join(__dirname, '..', 'www'),
7
8
  });
@@ -1,6 +1,6 @@
1
1
  module.exports = (httpsServer) => {
2
2
  return (req, res) => {
3
3
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
4
- res.end('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
4
+ res.end('"example.com: Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
5
5
  };
6
6
  };
@@ -4,7 +4,7 @@ module.exports = (httpsServer) => {
4
4
  const app = express();
5
5
  app.get('/', (req, res) => {
6
6
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
7
- res.send('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
7
+ res.send('"subdomain.example.com: Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
8
8
  });
9
9
 
10
10
  return app;
@@ -1,6 +1,6 @@
1
1
  module.exports = (httpsServer) => {
2
2
  return (req, res) => {
3
3
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
4
- res.end('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
4
+ res.end('"subdomain.example.com: Crazy from thinking, wanting to be reasonable, and the heart has reasons that reason itself will never understand."');
5
5
  };
6
6
  };
package/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const http = require('http');
3
4
  const https = require('https');
4
5
  const tls = require('tls');
5
6
  const { EventEmitter } = require('events');
6
7
  const Greenlock = require('greenlock-express');
8
+ const log = require('lemonlog')('roster');
7
9
 
8
10
  // Virtual Server that completely isolates applications
9
11
  class VirtualServer extends EventEmitter {
@@ -79,6 +81,7 @@ class Roster {
79
81
  this.greenlockStorePath = options.greenlockStorePath || path.join(basePath, 'greenlock.d');
80
82
  this.staging = options.staging || false;
81
83
  this.cluster = options.cluster || false;
84
+ this.local = options.local || false;
82
85
  this.domains = [];
83
86
  this.sites = {};
84
87
  this.domainServers = {}; // Store separate servers for each domain
@@ -87,7 +90,7 @@ class Roster {
87
90
  this.filename = options.filename || 'index';
88
91
 
89
92
  const port = options.port === undefined ? 443 : options.port;
90
- if (port === 80) {
93
+ if (port === 80 && !this.local) {
91
94
  throw new Error('⚠️ Port 80 is reserved for ACME challenge. Please use a different port.');
92
95
  }
93
96
  this.defaultPort = port;
@@ -96,7 +99,7 @@ class Roster {
96
99
  async loadSites() {
97
100
  // Check if wwwPath exists
98
101
  if (!fs.existsSync(this.wwwPath)) {
99
- console.warn(`⚠️ WWW path does not exist: ${this.wwwPath}`);
102
+ log.warn(`⚠️ WWW path does not exist: ${this.wwwPath}`);
100
103
  return;
101
104
  }
102
105
 
@@ -123,7 +126,7 @@ class Roster {
123
126
  siteApp = siteApp.default || siteApp;
124
127
  break;
125
128
  } catch (err) {
126
- console.warn(`⚠️ Error loading ${indexPath}:`, err);
129
+ log.warn(`⚠️ Error loading ${indexPath}:`, err);
127
130
  }
128
131
  }
129
132
  }
@@ -135,9 +138,9 @@ class Roster {
135
138
  this.sites[d] = siteApp;
136
139
  });
137
140
 
138
- console.log(`✅ Loaded site: ${domain}`);
141
+ log.info(`(✔) Loaded site: ${domain}`);
139
142
  } else {
140
- console.warn(`⚠️ No index file (js/mjs/cjs) found in ${domainPath}`);
143
+ log.warn(`⚠️ No index file (js/mjs/cjs) found in ${domainPath}`);
141
144
  }
142
145
  }
143
146
  }
@@ -215,16 +218,16 @@ class Roster {
215
218
  const currentConfigContentFormatted = JSON.stringify(currentConfig, null, 2);
216
219
 
217
220
  if (newConfigContent === currentConfigContentFormatted) {
218
- console.log('ℹ️ Configuration has not changed. config.json will not be overwritten.');
221
+ log.info('ℹ️ Configuration has not changed. config.json will not be overwritten.');
219
222
  return;
220
223
  }
221
- console.log('🔄 Configuration has changed. config.json will be updated.');
224
+ log.info('🔄 Configuration has changed. config.json will be updated.');
222
225
  } else {
223
- console.log('🆕 config.json does not exist. A new one will be created.');
226
+ log.info('🆕 config.json does not exist. A new one will be created.');
224
227
  }
225
228
 
226
229
  fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
227
- console.log(`📁 config.json generated at ${configPath}`);
230
+ log.info(`📁 config.json generated at ${configPath}`);
228
231
  }
229
232
 
230
233
  handleRequest(req, res) {
@@ -268,7 +271,7 @@ class Roster {
268
271
  this.sites[domainKey] = requestHandler;
269
272
  });
270
273
 
271
- console.log(`✅ Registered site: ${domain}${port !== this.defaultPort ? ':' + port : ''}`);
274
+ log.info(`(✔) Registered site: ${domain}${port !== this.defaultPort ? ':' + port : ''}`);
272
275
  return this;
273
276
  }
274
277
 
@@ -277,7 +280,7 @@ class Roster {
277
280
  if (parts.length === 2) {
278
281
  const domain = parts[0];
279
282
  const port = parseInt(parts[1]);
280
- if (port === 80) {
283
+ if (port === 80 && !this.local) {
281
284
  throw new Error('⚠️ Port 80 is reserved for ACME challenge. Please use a different port.');
282
285
  }
283
286
  return { domain, port };
@@ -307,9 +310,72 @@ class Roster {
307
310
  return null;
308
311
  }
309
312
 
313
+ // Start server in local mode with HTTP - simplified version
314
+ startLocalMode() {
315
+ const startPort = 3000;
316
+ let currentPort = startPort;
317
+
318
+ // Create a simple HTTP server for each domain with sequential ports
319
+ for (const [hostKey, siteApp] of Object.entries(this.sites)) {
320
+ const domain = hostKey.split(':')[0]; // Remove port if present
321
+
322
+ // Skip www domains in local mode
323
+ if (domain.startsWith('www.')) {
324
+ continue;
325
+ }
326
+
327
+ const port = currentPort; // Capture current port value
328
+
329
+ // Create virtual server for the domain
330
+ const virtualServer = this.createVirtualServer(domain);
331
+ this.domainServers[domain] = virtualServer;
332
+
333
+ // Initialize app with virtual server
334
+ const appHandler = siteApp(virtualServer);
335
+
336
+ // Create simple dispatcher for this domain
337
+ const dispatcher = (req, res) => {
338
+ if (virtualServer.requestListeners.length > 0) {
339
+ virtualServer.processRequest(req, res);
340
+ } else if (appHandler) {
341
+ appHandler(req, res);
342
+ } else {
343
+ res.writeHead(404);
344
+ res.end('Site not found');
345
+ }
346
+ };
347
+
348
+ // Create HTTP server for this domain
349
+ const httpServer = http.createServer(dispatcher);
350
+ this.portServers[port] = httpServer;
351
+
352
+ httpServer.listen(port, 'localhost', () => {
353
+ log.info(`🌐 ${domain} → http://localhost:${port}`);
354
+ });
355
+
356
+ httpServer.on('error', (error) => {
357
+ log.error(`❌ Error on port ${port} for ${domain}:`, error.message);
358
+ });
359
+
360
+ currentPort++;
361
+ }
362
+
363
+ log.info(`(✔) Started ${currentPort - startPort} sites in local mode`);
364
+ return Promise.resolve();
365
+ }
366
+
310
367
  async start() {
311
368
  await this.loadSites();
312
- this.generateConfigJson();
369
+
370
+ // Skip Greenlock configuration generation in local mode
371
+ if (!this.local) {
372
+ this.generateConfigJson();
373
+ }
374
+
375
+ // Handle local mode with simple HTTP server
376
+ if (this.local) {
377
+ return this.startLocalMode();
378
+ }
313
379
 
314
380
  const greenlock = Greenlock.init({
315
381
  packageRoot: __dirname,
@@ -379,7 +445,7 @@ class Roster {
379
445
  };
380
446
 
381
447
  httpServer.listen(80, this.hostname, () => {
382
- console.log('HTTP server listening on port 80');
448
+ log.info('HTTP server listening on port 80');
383
449
  });
384
450
 
385
451
  // Handle different port types
@@ -393,7 +459,7 @@ class Roster {
393
459
  this.portServers[portNum] = httpsServer;
394
460
 
395
461
  httpsServer.listen(portNum, this.hostname, () => {
396
- console.log(`HTTPS server listening on port ${portNum}`);
462
+ log.info(`HTTPS server listening on port ${portNum}`);
397
463
  });
398
464
  } else {
399
465
  // Create HTTPS server for custom ports using Greenlock certificates
@@ -427,20 +493,23 @@ class Roster {
427
493
  const httpsServer = https.createServer(httpsOptions, dispatcher);
428
494
 
429
495
  httpsServer.on('error', (error) => {
430
- console.error(`HTTPS server error on port ${portNum}:`, error.message);
496
+ log.error(`HTTPS server error on port ${portNum}:`, error.message);
431
497
  });
432
498
 
433
499
  httpsServer.on('tlsClientError', (error) => {
434
- console.error(`TLS error on port ${portNum}:`, error.message);
500
+ // Suppress HTTP request errors to avoid log spam
501
+ if (!error.message.includes('http request')) {
502
+ log.error(`TLS error on port ${portNum}:`, error.message);
503
+ }
435
504
  });
436
505
 
437
506
  this.portServers[portNum] = httpsServer;
438
507
 
439
508
  httpsServer.listen(portNum, this.hostname, (error) => {
440
509
  if (error) {
441
- console.error(`Failed to start HTTPS server on port ${portNum}:`, error.message);
510
+ log.error(`Failed to start HTTPS server on port ${portNum}:`, error.message);
442
511
  } else {
443
- console.log(`HTTPS server listening on port ${portNum}`);
512
+ log.info(`HTTPS server listening on port ${portNum}`);
444
513
  }
445
514
  });
446
515
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "1.8.0",
3
+ "version": "1.8.4",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "homepage": "https://github.com/clasen/RosterServer#readme",
36
36
  "dependencies": {
37
- "greenlock-express": "^4.0.3"
37
+ "greenlock-express": "^4.0.3",
38
+ "lemonlog": "^1.2.0"
38
39
  }
39
40
  }