roster-server 1.9.6 → 1.9.8

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
@@ -18,6 +18,14 @@ Welcome to **RosterServer**, the ultimate domain host router with automatic HTTP
18
18
  npm install roster-server
19
19
  ```
20
20
 
21
+ ## 🤖 AI Skill
22
+
23
+ You can also add RosterServer as a skill for AI agentic development:
24
+
25
+ ```bash
26
+ npx skills add https://github.com/clasen/RosterServer --skill roster-server
27
+ ```
28
+
21
29
  ## 🛠️ Usage
22
30
 
23
31
  ### Directory Structure
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "1.9.6",
3
+ "version": "1.9.8",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,260 @@
1
+ # RosterServer Skill
2
+
3
+ ## Overview
4
+
5
+ RosterServer is a virtual hosting platform for multiple HTTPS sites with automatic SSL via Let's Encrypt. Each domain gets an isolated `VirtualServer` instance to prevent configuration conflicts between different application types (Express, Socket.IO, etc).
6
+
7
+ ## Quick Setup
8
+
9
+ ### Production
10
+ ```javascript
11
+ const Roster = require('roster-server');
12
+
13
+ const roster = new Roster({
14
+ email: 'admin@example.com',
15
+ wwwPath: '/srv/www',
16
+ greenlockStorePath: '/srv/greenlock.d',
17
+ staging: false // Use true for testing
18
+ });
19
+
20
+ roster.start();
21
+ ```
22
+
23
+ ### Local Development
24
+ ```javascript
25
+ const roster = new Roster({
26
+ local: true, // HTTP mode, no SSL
27
+ wwwPath: './www',
28
+ minLocalPort: 4000, // Optional
29
+ maxLocalPort: 9999 // Optional
30
+ });
31
+
32
+ roster.start().then(() => {
33
+ console.log('example.com:', roster.getUrl('example.com'));
34
+ // → http://localhost:9465 (deterministic CRC32-based port)
35
+ });
36
+ ```
37
+
38
+ ## Directory Structure
39
+
40
+ ```
41
+ project/
42
+ ├── greenlock.d/ # SSL certificates (auto-generated)
43
+ ├── www/
44
+ │ ├── example.com/
45
+ │ │ └── index.js # Handler for example.com
46
+ │ └── api.example.com/
47
+ │ └── index.js # Handler for subdomain
48
+ └── server.js # Your setup
49
+ ```
50
+
51
+ ## Handler Patterns
52
+
53
+ Each `www/{domain}/index.js` must export a function that receives `httpsServer` and returns a request handler.
54
+
55
+ ### Pattern 1: Basic HTTP Handler
56
+ ```javascript
57
+ module.exports = (httpsServer) => {
58
+ return (req, res) => {
59
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
60
+ res.end('Hello World');
61
+ };
62
+ };
63
+ ```
64
+
65
+ ### Pattern 2: Express App
66
+ ```javascript
67
+ const express = require('express');
68
+
69
+ module.exports = (httpsServer) => {
70
+ const app = express();
71
+
72
+ app.get('/', (req, res) => res.send('Hello'));
73
+ app.post('/api/data', (req, res) => res.json({ ok: true }));
74
+
75
+ return app;
76
+ };
77
+ ```
78
+
79
+ ### Pattern 3: Socket.IO
80
+ ```javascript
81
+ const { Server } = require('socket.io');
82
+
83
+ module.exports = (httpsServer) => {
84
+ const io = new Server(httpsServer);
85
+
86
+ io.on('connection', (socket) => {
87
+ socket.on('message', (data) => io.emit('message', data));
88
+ });
89
+
90
+ return (req, res) => {
91
+ if (req.url && req.url.startsWith(io.opts.path)) return;
92
+ res.writeHead(200);
93
+ res.end('Socket.IO running');
94
+ };
95
+ };
96
+ ```
97
+
98
+ ### Pattern 4: Manual Registration
99
+ ```javascript
100
+ // In server.js, before roster.start()
101
+ roster.register('example.com', (httpsServer) => {
102
+ return (req, res) => {
103
+ res.writeHead(200);
104
+ res.end('Manual handler');
105
+ };
106
+ });
107
+
108
+ // With custom port
109
+ roster.register('api.example.com:8443', handler);
110
+ ```
111
+
112
+ ## Key Configuration Options
113
+
114
+ ```javascript
115
+ new Roster({
116
+ email: 'admin@example.com', // Required for SSL
117
+ wwwPath: '/srv/www', // Site handlers directory
118
+ greenlockStorePath: '/srv/greenlock.d', // SSL storage
119
+
120
+ // Environment
121
+ local: false, // true = HTTP, false = HTTPS
122
+ staging: false, // true = Let's Encrypt staging
123
+
124
+ // Server
125
+ hostname: '0.0.0.0',
126
+ port: 443, // Default HTTPS port (NOT 80!)
127
+
128
+ // Local mode
129
+ minLocalPort: 4000,
130
+ maxLocalPort: 9999,
131
+
132
+ // Advanced
133
+ filename: 'index', // Handler filename (no extension)
134
+ basePath: '/srv' // Base for relative paths
135
+ })
136
+ ```
137
+
138
+ ## Core API
139
+
140
+ ### `roster.start()`
141
+ Loads sites, generates SSL config, starts servers. Returns `Promise<void>`.
142
+
143
+ ### `roster.register(domain, handler)`
144
+ Manually register a domain handler. Domain can include port: `'api.com:8443'`.
145
+
146
+ ### `roster.getUrl(domain)`
147
+ Get environment-aware URL:
148
+ - Local mode: `http://localhost:{port}`
149
+ - Production: `https://{domain}` or `https://{domain}:{port}`
150
+ - Returns `null` if domain not registered
151
+
152
+ ## How It Works
153
+
154
+ ### Request Flow
155
+ 1. Request arrives → Dispatcher extracts `Host` header
156
+ 2. Strips `www.` prefix (301 redirect if present)
157
+ 3. Looks up domain → Gets `VirtualServer` instance
158
+ 4. Routes to handler via `virtualServer.processRequest(req, res)`
159
+
160
+ ### VirtualServer Architecture
161
+ Each domain gets isolated server instance that simulates `http.Server`:
162
+ - Captures `request` and `upgrade` event listeners
163
+ - Complete separation between domains
164
+ - No configuration conflicts between apps
165
+
166
+ ### Port Assignment
167
+ **Production**: Default 443, custom via `domain:port` syntax
168
+ **Local**: CRC32 hash of domain → deterministic port in range 4000-9999
169
+ **Reserved**: Port 80 for ACME challenges only
170
+
171
+ ### SSL Management
172
+ - Automatic Let's Encrypt certificate generation
173
+ - Auto-renewal 45 days before expiration
174
+ - SNI support for multiple domains
175
+ - Custom ports reuse certificates via SNI callback
176
+
177
+ ## Common Issues & Solutions
178
+
179
+ **Port 443 in use**: Use different port `{ port: 8443 }`
180
+ **Certificate failed**: Check firewall (ports 80, 443), verify DNS, try `staging: true`
181
+ **Site not found**: Verify directory name matches domain, check `index.js` exports function
182
+ **Local port conflict**: Adjust `minLocalPort`/`maxLocalPort` range
183
+ **Socket.IO not working**: Ensure handler checks `io.opts.path` and returns properly
184
+
185
+ ## Best Practices
186
+
187
+ 1. **Test with staging first**: `staging: true` to avoid Let's Encrypt rate limits
188
+ 2. **Use local mode for dev**: `local: true` for faster iteration
189
+ 3. **Environment variables**: Configure via `process.env` for portability
190
+ 4. **Error handling**: Wrap handlers with try/catch, don't expose internals
191
+ 5. **Socket.IO paths**: Always check `req.url.startsWith(io.opts.path)` in returned handler
192
+ 6. **Port 80**: Never use as HTTPS port (reserved for ACME)
193
+
194
+ ## Quick Examples
195
+
196
+ ### Full Production Setup
197
+ ```javascript
198
+ const Roster = require('roster-server');
199
+
200
+ const roster = new Roster({
201
+ email: process.env.ADMIN_EMAIL,
202
+ wwwPath: '/srv/www',
203
+ greenlockStorePath: '/srv/greenlock.d',
204
+ staging: process.env.NODE_ENV !== 'production'
205
+ });
206
+
207
+ roster.start().then(() => {
208
+ console.log('RosterServer running');
209
+ }).catch(err => {
210
+ console.error('Startup failed:', err);
211
+ process.exit(1);
212
+ });
213
+ ```
214
+
215
+ ### Local Dev with Manual Registration
216
+ ```javascript
217
+ const roster = new Roster({ local: true, wwwPath: './www' });
218
+
219
+ roster.register('test.local', (server) => {
220
+ return (req, res) => {
221
+ res.writeHead(200, { 'Content-Type': 'application/json' });
222
+ res.end(JSON.stringify({ status: 'ok', url: roster.getUrl('test.local') }));
223
+ };
224
+ });
225
+
226
+ roster.start();
227
+ ```
228
+
229
+ ### Environment-Aware Configuration
230
+ ```javascript
231
+ const isProduction = process.env.NODE_ENV === 'production';
232
+
233
+ const roster = new Roster({
234
+ email: process.env.ADMIN_EMAIL || 'admin@example.com',
235
+ wwwPath: process.env.WWW_PATH || './www',
236
+ greenlockStorePath: process.env.SSL_PATH || './greenlock.d',
237
+ local: !isProduction,
238
+ staging: !isProduction,
239
+ minLocalPort: parseInt(process.env.MIN_PORT) || 4000,
240
+ maxLocalPort: parseInt(process.env.MAX_PORT) || 9999
241
+ });
242
+
243
+ roster.start();
244
+ ```
245
+
246
+ ## Implementation Checklist
247
+
248
+ When implementing RosterServer:
249
+
250
+ - [ ] Create `www/` directory structure with domain folders
251
+ - [ ] Each domain has `index.js` exporting `(httpsServer) => handler`
252
+ - [ ] Configure email for Let's Encrypt notifications
253
+ - [ ] Test with `local: true` first
254
+ - [ ] Test with `staging: true` before production
255
+ - [ ] Ensure ports 80 and 443 are open (production)
256
+ - [ ] Verify DNS points to server
257
+ - [ ] Never use port 80 as HTTPS port
258
+ - [ ] Use `roster.getUrl(domain)` for environment-aware URLs
259
+ - [ ] Handle Socket.IO paths correctly in returned handler
260
+ - [ ] Implement error handling in handlers