roster-server 2.0.4 β†’ 2.1.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.
package/README.md CHANGED
@@ -11,6 +11,7 @@ Welcome to **RosterServer**, the ultimate domain host router with automatic HTTP
11
11
  - **Virtual Hosting**: Serve multiple domains from a single server.
12
12
  - **Automatic Redirects**: Redirect `www` subdomains to the root domain.
13
13
  - **Zero Configuration**: Well, almost zero. Just a tiny bit of setup.
14
+ - **Bun compatible**: Works with both Node.js and [Bun](https://bun.sh).
14
15
 
15
16
  ## πŸ“¦ Installation
16
17
 
@@ -18,6 +19,12 @@ Welcome to **RosterServer**, the ultimate domain host router with automatic HTTP
18
19
  npm install roster-server
19
20
  ```
20
21
 
22
+ Or with [Bun](https://bun.sh):
23
+
24
+ ```bash
25
+ bun add roster-server
26
+ ```
27
+
21
28
  ## πŸ€– AI Skill
22
29
 
23
30
  You can also add RosterServer as a skill for AI agentic development:
@@ -39,17 +46,42 @@ Your project should look something like this:
39
46
  └── www/
40
47
  β”œβ”€β”€ example.com/
41
48
  β”‚ └── index.js
42
- └── subdomain.example.com/
49
+ β”œβ”€β”€ subdomain.example.com/
50
+ β”‚ └── index.js
51
+ β”œβ”€β”€ other-domain.com/
43
52
  β”‚ └── index.js
44
- └── other-domain.com/
45
- └── index.js
53
+ └── *.example.com/ # Wildcard: one handler for all subdomains (api.example.com, app.example.com, etc.)
54
+ └── index.js
46
55
  ```
47
56
 
57
+ ### Wildcard DNS (*.example.com)
58
+
59
+ You can serve all subdomains of a domain with a single handler in three ways:
60
+
61
+ 1. **Folder**: Create a directory named literally `*.example.com` under `www` (e.g. `www/*.example.com/index.js`). Any request to `api.example.com`, `app.example.com`, etc. will use that handler.
62
+ 2. **Register (default port)**: `roster.register('*.example.com', handler)` for the default HTTPS port.
63
+ 3. **Register (custom port)**: `roster.register('*.example.com:8080', handler)` for a specific port.
64
+
65
+ Wildcard SSL certificates require **DNS-01** validation (Let's Encrypt does not support HTTP-01 for wildcards). By default Roster uses `acme-dns-01-cli` through an internal wrapper (adds `propagationDelay` and modern plugin signatures). Override with a custom plugin:
66
+
67
+ ```javascript
68
+ import Roster from 'roster-server';
69
+
70
+ const roster = new Roster({
71
+ email: 'admin@example.com',
72
+ wwwPath: '/srv/www',
73
+ greenlockStorePath: '/srv/greenlock.d',
74
+ dnsChallenge: { module: 'acme-dns-01-route53', /* provider options */ } // optional override
75
+ });
76
+ ```
77
+
78
+ Set `dnsChallenge: false` to disable. For other DNS providers install the plugin in your app and pass it. See [Greenlock DNS plugins](https://git.rootprojects.org/root/greenlock-express.js#dns-01-challenge-plugins).
79
+
48
80
  ### Setting Up Your Server
49
81
 
50
82
  ```javascript
51
83
  // /srv/roster/server.js
52
- const Roster = require('roster-server');
84
+ import Roster from 'roster-server';
53
85
 
54
86
  const options = {
55
87
  email: 'admin@example.com',
@@ -71,7 +103,7 @@ I'll help analyze the example files shown. You have 3 different implementations
71
103
 
72
104
  1. **Basic HTTP Handler**:
73
105
  ```javascript:demo/www/example.com/index.js
74
- module.exports = (httpsServer) => {
106
+ export default (httpsServer) => {
75
107
  return (req, res) => {
76
108
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
77
109
  res.end('"Loco de pensar, queriendo entrar en razΓ³n, y el corazΓ³n tiene razones que la propia razΓ³n nunca entenderΓ‘."');
@@ -81,9 +113,9 @@ module.exports = (httpsServer) => {
81
113
 
82
114
  2. **Express App**:
83
115
  ```javascript:demo/www/express.example.com/index.js
84
- const express = require('express');
116
+ import express from 'express';
85
117
 
86
- module.exports = (httpsServer) => {
118
+ export default (httpsServer) => {
87
119
  const app = express();
88
120
  app.get('/', (req, res) => {
89
121
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
@@ -96,9 +128,9 @@ module.exports = (httpsServer) => {
96
128
 
97
129
  3. **Socket.IO Server**:
98
130
  ```javascript:demo/www/sio.example.com/index.js
99
- const { Server } = require('socket.io');
131
+ import { Server } from 'socket.io';
100
132
 
101
- module.exports = (httpsServer) => {
133
+ export default (httpsServer) => {
102
134
  const io = new Server(httpsServer);
103
135
 
104
136
  io.on('connection', (socket) => {
@@ -145,10 +177,16 @@ roster.register('example.com:8080', (httpsServer) => {
145
177
  ### Running the Server
146
178
 
147
179
  ```bash
148
- # /srv/roster/server.js
180
+ # With Node.js
149
181
  node server.js
150
182
  ```
151
183
 
184
+ Or with Bun:
185
+
186
+ ```bash
187
+ bun server.js
188
+ ```
189
+
152
190
  And that's it! Your server is now hosting multiple HTTPS-enabled sites. πŸŽ‰
153
191
 
154
192
  ## 🀯 But Wait, There's More!
@@ -172,122 +210,11 @@ When creating a new `RosterServer` instance, you can pass the following options:
172
210
  - `email` (string): Your email for Let's Encrypt notifications.
173
211
  - `wwwPath` (string): Path to your `www` directory containing your sites.
174
212
  - `greenlockStorePath` (string): Directory for Greenlock configuration.
213
+ - `dnsChallenge` (object|false): Optional override for wildcard DNS-01 challenge config. Default is local/manual `acme-dns-01-cli` wrapper with `propagationDelay: 120000`, `autoContinue: false`, and `dryRunDelay: 120000`. This is safer for manual DNS providers (Linode/Cloudflare UI) because Roster waits longer and does not auto-advance in interactive terminals. Set `false` to disable. You can pass `{ module: '...', propagationDelay: 180000 }` to tune DNS wait time (ms). Set `autoContinue: true` (or env `ROSTER_DNS_AUTO_CONTINUE=1`) to continue automatically after delay. For Greenlock dry-runs (`_greenlock-dryrun-*`), delay defaults to `dryRunDelay` (same as `propagationDelay` unless overridden with `dnsChallenge.dryRunDelay` or env `ROSTER_DNS_DRYRUN_DELAY_MS`).
175
214
  - `staging` (boolean): Set to `true` to use Let's Encrypt's staging environment (for testing).
176
215
  - `local` (boolean): Set to `true` to run in local development mode.
177
216
  - `minLocalPort` (number): Minimum port for local mode (default: 4000).
178
217
  - `maxLocalPort` (number): Maximum port for local mode (default: 9999).
179
- - `tlsMode` (string): TLS backend to use β€” `'auto'` (default), `'greenlock'`, or `'static'`. See [TLS Configuration](#-tls-configuration) below.
180
- - `tlsDomain` (string): Domain whose cert files are pre-loaded as the server default in static mode (optional).
181
- - `tls` (object): Additional TLS options passed to `https.createServer` (e.g. `minVersion`, `maxVersion`, `ciphers`).
182
-
183
- ## πŸ”’ TLS Configuration
184
-
185
- RosterServer supports three TLS backends, selectable with the `tlsMode` option.
186
-
187
- ### Behavior matrix
188
-
189
- | `tlsMode` | Runtime | HTTPS server |
190
- |-----------|---------|-------------|
191
- | `'auto'` (default) | Node.js | Greenlock SNI β€” certs managed and auto-renewed automatically |
192
- | `'auto'` (default) | Bun | Static file certs from `greenlockStorePath/live/<domain>/` |
193
- | `'greenlock'` | any | Always Greenlock SNI |
194
- | `'static'` | any | Always static file certs |
195
-
196
- Bun's TLS stack does not support Greenlock's async SNICallback, causing a `tlsv1 alert protocol version` error. `'auto'` mode detects the runtime and picks the correct backend transparently.
197
-
198
- ### Static mode cert layout
199
-
200
- In `'auto'` (Bun) or `'static'` mode, certs are read per-domain from:
201
-
202
- ```
203
- greenlockStorePath/
204
- live/
205
- example.com/
206
- privkey.pem
207
- cert.pem
208
- chain.pem
209
- api.example.com/
210
- privkey.pem
211
- cert.pem
212
- chain.pem
213
- ```
214
-
215
- Greenlock populates this layout automatically when it renews certificates, so no extra tooling is required.
216
-
217
- ### Default TLS options
218
-
219
- The static-cert path enforces secure defaults that can be overridden with the `tls` option:
220
-
221
- ```javascript
222
- { minVersion: 'TLSv1.2', maxVersion: 'TLSv1.3' }
223
- ```
224
-
225
- ### Examples
226
-
227
- **Default β€” works on both Node and Bun without changes:**
228
-
229
- ```javascript
230
- const roster = new Roster({
231
- email: 'admin@example.com',
232
- wwwPath: '/srv/www'
233
- // tlsMode defaults to 'auto'
234
- });
235
- roster.start();
236
- ```
237
-
238
- **Force Greenlock on all runtimes:**
239
-
240
- ```javascript
241
- const roster = new Roster({
242
- email: 'admin@example.com',
243
- wwwPath: '/srv/www',
244
- tlsMode: 'greenlock'
245
- });
246
- roster.start();
247
- ```
248
-
249
- **Force static certs with a pre-loaded default cert (avoids SNI-less connection failures):**
250
-
251
- ```javascript
252
- const roster = new Roster({
253
- email: 'admin@example.com',
254
- wwwPath: '/srv/www',
255
- tlsMode: 'static',
256
- tlsDomain: 'example.com' // loaded as the server's fallback cert
257
- });
258
- roster.start();
259
- ```
260
-
261
- **Custom TLS options (e.g. restrict ciphers):**
262
-
263
- ```javascript
264
- const roster = new Roster({
265
- email: 'admin@example.com',
266
- wwwPath: '/srv/www',
267
- tls: {
268
- minVersion: 'TLSv1.2',
269
- ciphers: 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256'
270
- }
271
- });
272
- roster.start();
273
- ```
274
-
275
- ### Smoke test
276
-
277
- After deploying, verify TLS is working:
278
-
279
- ```bash
280
- curl -v https://example.com
281
- # Should show TLSv1.2 or TLSv1.3 in the handshake β€” no "alert protocol version" errors
282
- ```
283
-
284
- Server logs will display the active mode on startup:
285
-
286
- ```
287
- Runtime: bun | TLS mode: static
288
- HTTPS port 443: using static certs from /srv/greenlock.d/live [static]
289
- HTTPS server listening on port 443
290
- ```
291
218
 
292
219
  ## 🏠 Local Development Mode
293
220
 
@@ -298,6 +225,8 @@ When `{ local: true }` is enabled, RosterServer **Skips SSL/HTTPS**: Runs pure H
298
225
  ### Setting Up Local Mode
299
226
 
300
227
  ```javascript
228
+ import Roster from 'roster-server';
229
+
301
230
  const server = new Roster({
302
231
  wwwPath: '/srv/www',
303
232
  local: true, // Enable local development mode
@@ -318,6 +247,8 @@ In local mode, domains are automatically assigned ports based on a CRC32 hash of
318
247
  You can customize the port range:
319
248
 
320
249
  ```javascript
250
+ import Roster from 'roster-server';
251
+
321
252
  const roster = new Roster({
322
253
  local: true,
323
254
  minLocalPort: 5000, // Start from port 5000
@@ -332,6 +263,8 @@ RosterServer provides a method to get the URL for a domain that adapts automatic
332
263
  **Instance Method: `roster.getUrl(domain)`**
333
264
 
334
265
  ```javascript
266
+ import Roster from 'roster-server';
267
+
335
268
  const roster = new Roster({ local: true });
336
269
  roster.register('example.com', handler);
337
270
 
@@ -354,6 +287,8 @@ This method:
354
287
  **Example Usage:**
355
288
 
356
289
  ```javascript
290
+ import Roster from 'roster-server';
291
+
357
292
  // Local development
358
293
  const localRoster = new Roster({ local: true });
359
294
  localRoster.register('example.com', handler);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Wildcard DNS demo: one handler for all subdomains (*.example.com)
3
+ *
4
+ * Run from repo root: node demo/wildcard-example.js
5
+ * Then open the printed URL (or use curl with Host header) to see the wildcard response.
6
+ * Any subdomain (api.example.com, app.example.com, foo.example.com) uses the same handler.
7
+ */
8
+ const Roster = require('../index.js');
9
+ const path = require('path');
10
+ const { wildcardRoot } = require('../index.js');
11
+
12
+ const roster = new Roster({
13
+ local: true,
14
+ wwwPath: path.join(__dirname, 'www'),
15
+ });
16
+
17
+ roster.start().then(() => {
18
+ const wildcardPattern = roster.domains.find((d) => d.startsWith('*.'));
19
+ const subdomain = wildcardPattern ? 'api.' + wildcardRoot(wildcardPattern) : 'api.example.com';
20
+ const wildcardUrl = roster.getUrl(subdomain);
21
+
22
+ console.log('\n🌐 Wildcard demo');
23
+ console.log(' Loaded:', wildcardPattern ? `https://${wildcardPattern}` : '(none)');
24
+ console.log(' Any subdomain uses the same handler.\n');
25
+ console.log(' Try:', wildcardUrl || '(no wildcard site in www path)');
26
+ if (wildcardUrl) {
27
+ const host = wildcardPattern ? 'api.' + wildcardRoot(wildcardPattern) : 'api.example.com';
28
+ console.log(' Or: curl -H "Host: ' + host + '"', wildcardUrl);
29
+ }
30
+ console.log('');
31
+ });