@imenam/self-register-proxy 1.0.37
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/LICENSE +15 -0
- package/README.md +510 -0
- package/config.js +31 -0
- package/logger.js +88 -0
- package/meta-config.js +336 -0
- package/package.json +71 -0
- package/proxy-client.js +203 -0
- package/proxy.js +169 -0
- package/routes/dashboard.js +152 -0
- package/routes/metaConfig.js +180 -0
- package/routes/registration.js +227 -0
- package/routes/terminal.js +68 -0
- package/services/auth.js +151 -0
- package/services/healthCheck.js +95 -0
- package/services/portManager.js +82 -0
- package/services/proxyRouter.js +99 -0
- package/services/registry.js +41 -0
- package/terminal-server.js +154 -0
- package/views/dashboard.js +688 -0
- package/views/login.js +130 -0
- package/views/metaConfigs.js +643 -0
- package/views/terminal.html +1029 -0
- package/views/terminal.js +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# Self-Register Proxy
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@imenam/self-register-proxy)
|
|
4
|
+
[](https://opensource.org/licenses/ISC)
|
|
5
|
+
|
|
6
|
+
Un reverse proxy Node.js avec API d'auto-enregistrement, permettant à vos applications de s'enregistrer dynamiquement et de recevoir un port automatiquement attribué.
|
|
7
|
+
|
|
8
|
+
## Cas d'usage
|
|
9
|
+
|
|
10
|
+
- **Conteneur Docker unique** exposant un seul port vers l'extérieur
|
|
11
|
+
- **Microservices dynamiques** qui démarrent/s'arrêtent sans configuration manuelle
|
|
12
|
+
- **Environnement de développement** avec plusieurs applications Node.js
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @imenam/self-register-proxy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Ou pour une installation globale :
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @imenam/self-register-proxy
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Démarrage rapide
|
|
27
|
+
|
|
28
|
+
### 1. Démarrer le proxy
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Via npx (sans installation globale)
|
|
32
|
+
npx @imenam/self-register-proxy
|
|
33
|
+
|
|
34
|
+
# Ou si installé globalement
|
|
35
|
+
self-register-proxy
|
|
36
|
+
# (alias : dynamic-proxy)
|
|
37
|
+
|
|
38
|
+
# Ou via npm script (développement local)
|
|
39
|
+
npm start
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Le proxy démarre par défaut sur le port `3000`.
|
|
43
|
+
|
|
44
|
+
### Configuration via fichier .env
|
|
45
|
+
|
|
46
|
+
Créez un fichier `.env` à la racine du projet (voir `.env.example`) :
|
|
47
|
+
|
|
48
|
+
```env
|
|
49
|
+
PROXY_NAME=Mon Proxy Dev
|
|
50
|
+
PROXY_PORT=3000
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Configuration via variables d'environnement
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Windows PowerShell
|
|
57
|
+
$env:PROXY_PORT="3002"; npx @imenam/self-register-proxy
|
|
58
|
+
|
|
59
|
+
# Linux/Mac
|
|
60
|
+
PROXY_PORT=3002 npx @imenam/self-register-proxy
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Enregistrer une application
|
|
64
|
+
|
|
65
|
+
Votre application doit s'enregistrer au démarrage :
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
const response = await fetch('http://localhost:3000/proxy/register', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
path: '/myapp', // Chemin d'accès via le proxy
|
|
73
|
+
name: 'My App' // Nom de l'application (optionnel)
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const { port } = await response.json();
|
|
78
|
+
// port = 4000 (attribué automatiquement)
|
|
79
|
+
|
|
80
|
+
// Démarrez votre serveur sur ce port
|
|
81
|
+
app.listen(port);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Accéder à votre application
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
http://localhost:3000/myapp/ → votre application sur le port 4000
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Page d'accueil
|
|
91
|
+
|
|
92
|
+
L'URL racine `/` affiche une page listant tous les serveurs enregistrés avec des liens pour y accéder.
|
|
93
|
+
|
|
94
|
+
Le nom du proxy (configurable via `PROXY_NAME`) est affiché dans le titre et l'en-tête de la page.
|
|
95
|
+
|
|
96
|
+
- **Navigateurs** (`Accept: text/html`) → Page HTML avec interface moderne
|
|
97
|
+
- **Clients API** (`Accept: application/json`) → Réponse JSON
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Accès navigateur : ouvrir http://localhost:3000/
|
|
101
|
+
|
|
102
|
+
# Accès API
|
|
103
|
+
curl -H "Accept: application/json" http://localhost:3000/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Réponse JSON :**
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"name": "Proxy Server",
|
|
110
|
+
"status": "healthy",
|
|
111
|
+
"uptime": 3600,
|
|
112
|
+
"count": 2,
|
|
113
|
+
"routes": [
|
|
114
|
+
{
|
|
115
|
+
"path": "/api",
|
|
116
|
+
"port": 4000,
|
|
117
|
+
"name": "API Server",
|
|
118
|
+
"registeredAt": "2026-01-27T10:00:00.000Z",
|
|
119
|
+
"target": "http://localhost:4000"
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"availablePorts": 998
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## API du Proxy
|
|
127
|
+
|
|
128
|
+
| Endpoint | Méthode | Description |
|
|
129
|
+
|----------|---------|-------------|
|
|
130
|
+
| `/` | GET | Page d'accueil (HTML ou JSON selon Accept header) |
|
|
131
|
+
| `/proxy/register` | POST | Enregistre une nouvelle route |
|
|
132
|
+
| `/proxy/unregister` | DELETE | Supprime une route |
|
|
133
|
+
| `/proxy/routes` | GET | Liste toutes les routes |
|
|
134
|
+
| `/proxy/health` | GET | Health check du proxy |
|
|
135
|
+
|
|
136
|
+
### POST /proxy/register
|
|
137
|
+
|
|
138
|
+
Enregistre une nouvelle route et attribue un port.
|
|
139
|
+
|
|
140
|
+
**Request:**
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"path": "/myapp",
|
|
144
|
+
"name": "My Application",
|
|
145
|
+
"port": 4005
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Champ | Type | Requis | Description |
|
|
150
|
+
|-------|------|--------|-------------|
|
|
151
|
+
| `path` | string | ✅ | Chemin URL pour accéder à l'app |
|
|
152
|
+
| `name` | string | ❌ | Nom descriptif |
|
|
153
|
+
| `port` | number | ❌ | Port spécifique (sinon auto-attribué) |
|
|
154
|
+
|
|
155
|
+
**Response (201):**
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"success": true,
|
|
159
|
+
"path": "/myapp",
|
|
160
|
+
"port": 4000,
|
|
161
|
+
"name": "My Application",
|
|
162
|
+
"message": "Route registered. Start your server on port 4000"
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Erreurs:**
|
|
167
|
+
- `400` - Path manquant
|
|
168
|
+
- `409` - Path ou port déjà utilisé
|
|
169
|
+
|
|
170
|
+
### DELETE /proxy/unregister
|
|
171
|
+
|
|
172
|
+
Supprime une route enregistrée.
|
|
173
|
+
|
|
174
|
+
**Request:**
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"path": "/myapp"
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Response (200):**
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"success": true,
|
|
185
|
+
"path": "/myapp",
|
|
186
|
+
"freedPort": 4000
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### GET /proxy/routes
|
|
191
|
+
|
|
192
|
+
Liste toutes les routes enregistrées.
|
|
193
|
+
|
|
194
|
+
**Response:**
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"count": 2,
|
|
198
|
+
"routes": [
|
|
199
|
+
{
|
|
200
|
+
"path": "/api",
|
|
201
|
+
"port": 4000,
|
|
202
|
+
"name": "API Server",
|
|
203
|
+
"registeredAt": "2026-01-27T10:00:00.000Z",
|
|
204
|
+
"target": "http://localhost:4000"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"path": "/web",
|
|
208
|
+
"port": 4001,
|
|
209
|
+
"name": "Web App",
|
|
210
|
+
"registeredAt": "2026-01-27T10:01:00.000Z",
|
|
211
|
+
"target": "http://localhost:4001"
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"availablePorts": 998
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### GET /proxy/health
|
|
219
|
+
|
|
220
|
+
Vérifie l'état du proxy.
|
|
221
|
+
|
|
222
|
+
**Response:**
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"name": "Proxy Server",
|
|
226
|
+
"status": "healthy",
|
|
227
|
+
"uptime": 3600,
|
|
228
|
+
"registeredRoutes": 2,
|
|
229
|
+
"usedPorts": 2
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Intégration dans votre application
|
|
234
|
+
|
|
235
|
+
### Exemple complet avec Express
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
const express = require('express');
|
|
239
|
+
|
|
240
|
+
const PROXY_URL = process.env.PROXY_URL || 'http://localhost:3000';
|
|
241
|
+
const APP_PATH = process.env.APP_PATH || '/myapp';
|
|
242
|
+
const APP_NAME = process.env.APP_NAME || 'My App';
|
|
243
|
+
|
|
244
|
+
async function start() {
|
|
245
|
+
// 1. S'enregistrer auprès du proxy
|
|
246
|
+
const res = await fetch(`${PROXY_URL}/proxy/register`, {
|
|
247
|
+
method: 'POST',
|
|
248
|
+
headers: { 'Content-Type': 'application/json' },
|
|
249
|
+
body: JSON.stringify({ path: APP_PATH, name: APP_NAME })
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const { port } = await res.json();
|
|
253
|
+
|
|
254
|
+
// 2. Créer l'application
|
|
255
|
+
const app = express();
|
|
256
|
+
|
|
257
|
+
app.get('/', (req, res) => {
|
|
258
|
+
res.json({ message: 'Hello!' });
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// 3. Démarrer sur le port attribué
|
|
262
|
+
const server = app.listen(port, () => {
|
|
263
|
+
console.log(`App accessible via ${PROXY_URL}${APP_PATH}`);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// 4. Se désenregistrer à l'arrêt
|
|
267
|
+
process.on('SIGTERM', async () => {
|
|
268
|
+
await fetch(`${PROXY_URL}/proxy/unregister`, {
|
|
269
|
+
method: 'DELETE',
|
|
270
|
+
headers: { 'Content-Type': 'application/json' },
|
|
271
|
+
body: JSON.stringify({ path: APP_PATH })
|
|
272
|
+
});
|
|
273
|
+
server.close();
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
start();
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Utilisation du ProxyClient
|
|
281
|
+
|
|
282
|
+
Le package inclut un client helper pour faciliter l'enregistrement :
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
const ProxyClient = require('@imenam/self-register-proxy');
|
|
286
|
+
|
|
287
|
+
const proxy = new ProxyClient('http://localhost:3000');
|
|
288
|
+
const { port } = await proxy.register('/myapp', 'My App');
|
|
289
|
+
|
|
290
|
+
app.listen(port);
|
|
291
|
+
|
|
292
|
+
// Arrêt propre automatique
|
|
293
|
+
proxy.setupGracefulShutdown(server);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Méthodes disponibles :
|
|
297
|
+
|
|
298
|
+
- `register(path, name, port?)` - Enregistre une route
|
|
299
|
+
- `unregister()` - Supprime l'enregistrement
|
|
300
|
+
- `listRoutes()` - Liste toutes les routes
|
|
301
|
+
- `health()` - Vérifie l'état du proxy
|
|
302
|
+
- `setupGracefulShutdown(server)` - Configure l'arrêt propre
|
|
303
|
+
- `setupHealthRoute(app, options?)` - Ajoute la route de health check
|
|
304
|
+
|
|
305
|
+
## Docker
|
|
306
|
+
|
|
307
|
+
### Build et lancement
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# Build
|
|
311
|
+
npm run docker:build
|
|
312
|
+
|
|
313
|
+
# Démarrer (proxy + 2 apps exemples)
|
|
314
|
+
npm run docker:up
|
|
315
|
+
|
|
316
|
+
# Arrêter
|
|
317
|
+
npm run docker:down
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Accès
|
|
321
|
+
|
|
322
|
+
- Proxy : `http://localhost:8081`
|
|
323
|
+
- App 1 : `http://localhost:8081/app1`
|
|
324
|
+
- App 2 : `http://localhost:8081/app2`
|
|
325
|
+
- Routes : `http://localhost:8081/proxy/routes`
|
|
326
|
+
|
|
327
|
+
### Ajouter votre propre service
|
|
328
|
+
|
|
329
|
+
Dans `docker-compose.yml` :
|
|
330
|
+
|
|
331
|
+
```yaml
|
|
332
|
+
services:
|
|
333
|
+
my-service:
|
|
334
|
+
build: ./my-service
|
|
335
|
+
environment:
|
|
336
|
+
- PROXY_URL=http://proxy:3000
|
|
337
|
+
- APP_PATH=/my-service
|
|
338
|
+
- APP_NAME=My Service
|
|
339
|
+
depends_on:
|
|
340
|
+
- proxy
|
|
341
|
+
networks:
|
|
342
|
+
- proxy-network
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Health Check Polling
|
|
346
|
+
|
|
347
|
+
Le proxy vérifie périodiquement que les serveurs enregistrés sont toujours actifs. Si un serveur ne répond pas correctement, il est automatiquement désenregistré.
|
|
348
|
+
|
|
349
|
+
### Implémentation requise
|
|
350
|
+
|
|
351
|
+
Chaque application enregistrée **doit** implémenter la route `GET /proxy/health` qui retourne un code **200** :
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
// Avec Express
|
|
355
|
+
app.get('/proxy/health', (req, res) => {
|
|
356
|
+
res.status(200).json({ status: 'healthy' });
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Avec ProxyClient
|
|
361
|
+
|
|
362
|
+
Le helper `ProxyClient` fournit une méthode pour ajouter automatiquement cette route :
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
const ProxyClient = require('@imenam/self-register-proxy');
|
|
366
|
+
|
|
367
|
+
const proxy = new ProxyClient('http://localhost:3000');
|
|
368
|
+
const app = express();
|
|
369
|
+
|
|
370
|
+
// Ajoute automatiquement GET /proxy/health
|
|
371
|
+
proxy.setupHealthRoute(app);
|
|
372
|
+
|
|
373
|
+
// Ou avec un health check personnalisé
|
|
374
|
+
proxy.setupHealthRoute(app, {
|
|
375
|
+
healthCheck: async () => {
|
|
376
|
+
// Vérifier la connexion à la DB, etc.
|
|
377
|
+
const dbOk = await checkDatabase();
|
|
378
|
+
return dbOk;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Comportement
|
|
384
|
+
|
|
385
|
+
- **Période de grâce** : les nouveaux serveurs ont **60 secondes** après l'enregistrement avant d'être vérifiés (laisse le temps au serveur de démarrer)
|
|
386
|
+
- Le proxy vérifie tous les serveurs toutes les **30 secondes** (par défaut)
|
|
387
|
+
- Timeout de **5 secondes** par requête de health check
|
|
388
|
+
- Si la réponse n'est pas un code **200**, le serveur est automatiquement désenregistré
|
|
389
|
+
- Les erreurs de connexion (serveur arrêté, timeout) entraînent aussi le désenregistrement
|
|
390
|
+
|
|
391
|
+
## Configuration MCP (Cursor / Claude Desktop)
|
|
392
|
+
|
|
393
|
+
Pour utiliser ce proxy comme serveur MCP, ajoutez la configuration suivante :
|
|
394
|
+
|
|
395
|
+
### Cursor
|
|
396
|
+
|
|
397
|
+
Dans votre fichier `.cursor/mcp.json` :
|
|
398
|
+
|
|
399
|
+
```json
|
|
400
|
+
{
|
|
401
|
+
"mcpServers": {
|
|
402
|
+
"dynamic-proxy": {
|
|
403
|
+
"command": "npx",
|
|
404
|
+
"args": ["@imenam/self-register-proxy"],
|
|
405
|
+
"env": {
|
|
406
|
+
"PROXY_PORT": "3000"
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Claude Desktop
|
|
414
|
+
|
|
415
|
+
Dans votre fichier de configuration Claude Desktop (`claude_desktop_config.json`) :
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{
|
|
419
|
+
"mcpServers": {
|
|
420
|
+
"dynamic-proxy": {
|
|
421
|
+
"command": "npx",
|
|
422
|
+
"args": ["@imenam/self-register-proxy"],
|
|
423
|
+
"env": {
|
|
424
|
+
"PROXY_PORT": "3000"
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Avec variables d'environnement personnalisées
|
|
432
|
+
|
|
433
|
+
```json
|
|
434
|
+
{
|
|
435
|
+
"mcpServers": {
|
|
436
|
+
"dynamic-proxy": {
|
|
437
|
+
"command": "npx",
|
|
438
|
+
"args": ["@imenam/self-register-proxy"],
|
|
439
|
+
"env": {
|
|
440
|
+
"PROXY_NAME": "MCP Gateway",
|
|
441
|
+
"PROXY_PORT": "8080",
|
|
442
|
+
"HEALTH_CHECK_INTERVAL": "60000",
|
|
443
|
+
"HEALTH_CHECK_TIMEOUT": "10000",
|
|
444
|
+
"HEALTH_CHECK_GRACE_PERIOD": "120000"
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Configuration
|
|
452
|
+
|
|
453
|
+
Le proxy charge automatiquement le fichier `.env` s'il existe (voir `.env.example` pour un template).
|
|
454
|
+
|
|
455
|
+
### Variables du proxy
|
|
456
|
+
|
|
457
|
+
| Variable | Défaut | Description |
|
|
458
|
+
|----------|--------|-------------|
|
|
459
|
+
| `PROXY_NAME` | `Proxy Server` | Nom affiché sur la page d'accueil |
|
|
460
|
+
| `PROXY_PORT` | `3000` | Port d'écoute du proxy |
|
|
461
|
+
| `HEALTH_CHECK_INTERVAL` | `30000` | Intervalle du health check polling (ms) |
|
|
462
|
+
| `HEALTH_CHECK_TIMEOUT` | `5000` | Timeout pour chaque health check (ms) |
|
|
463
|
+
| `HEALTH_CHECK_GRACE_PERIOD` | `60000` | Période de grâce pour les nouveaux serveurs (ms) |
|
|
464
|
+
| `PROXY_CONFIG_DIR` | `.` | Dossier de stockage des configurations (meta-configs.json) |
|
|
465
|
+
|
|
466
|
+
### Variables pour les applications clientes
|
|
467
|
+
|
|
468
|
+
| Variable | Défaut | Description |
|
|
469
|
+
|----------|--------|-------------|
|
|
470
|
+
| `PROXY_URL` | `http://localhost:3000` | URL du proxy (pour les apps) |
|
|
471
|
+
| `APP_PATH` | `/example` | Chemin de l'application |
|
|
472
|
+
| `APP_NAME` | `Example App` | Nom de l'application |
|
|
473
|
+
|
|
474
|
+
## Plage de ports
|
|
475
|
+
|
|
476
|
+
Par défaut, le proxy attribue des ports entre **4000** et **5000** (1000 ports disponibles).
|
|
477
|
+
|
|
478
|
+
Pour modifier cette plage, éditez `proxy.js` :
|
|
479
|
+
|
|
480
|
+
```javascript
|
|
481
|
+
const INTERNAL_PORT_START = 4000;
|
|
482
|
+
const INTERNAL_PORT_END = 5000;
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## Fonctionnalités
|
|
486
|
+
|
|
487
|
+
- **Attribution sécurisée des ports** : utilisation d'un mutex pour éviter les conflits lors d'enregistrements simultanés
|
|
488
|
+
- **Période de grâce** : les nouveaux serveurs ne sont pas vérifiés immédiatement, leur laissant le temps de démarrer
|
|
489
|
+
- **Négociation de contenu** : l'URL racine `/` retourne HTML pour les navigateurs, JSON pour les API
|
|
490
|
+
- **Health check automatique** : désenregistrement automatique des serveurs non répondants
|
|
491
|
+
|
|
492
|
+
## 📝 Logs et Débogage
|
|
493
|
+
|
|
494
|
+
Le serveur enregistre ses activités (requêtes, erreurs, enregistrements) dans un fichier de log persistant :
|
|
495
|
+
|
|
496
|
+
- **Windows** : `C:\var\log\proxy-server\server.log`
|
|
497
|
+
- **Linux/Mac** : `/var/log/proxy-server/server.log`
|
|
498
|
+
|
|
499
|
+
Le format des logs inclut la date, l'heure, le PID et le tag `[PROXY]`.
|
|
500
|
+
|
|
501
|
+
## Limitations
|
|
502
|
+
|
|
503
|
+
- Les routes sont stockées en mémoire (perdues au redémarrage du proxy)
|
|
504
|
+
- Pas de persistance des enregistrements
|
|
505
|
+
- Pas d'authentification sur l'API de registration
|
|
506
|
+
- Les applications doivent implémenter `GET /proxy/health` pour ne pas être désenregistrées automatiquement
|
|
507
|
+
|
|
508
|
+
## Licence
|
|
509
|
+
|
|
510
|
+
ISC
|
package/config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
const PROXY_PORT = process.env.PROXY_PORT || 3000;
|
|
4
|
+
const PROXY_NAME = process.env.PROXY_NAME || 'Proxy Server';
|
|
5
|
+
const INTERNAL_PORT_START = 4000;
|
|
6
|
+
const INTERNAL_PORT_END = 5000;
|
|
7
|
+
const HEALTH_CHECK_INTERVAL = process.env.HEALTH_CHECK_INTERVAL || 30000;
|
|
8
|
+
const HEALTH_CHECK_TIMEOUT = process.env.HEALTH_CHECK_TIMEOUT || 5000;
|
|
9
|
+
const HEALTH_CHECK_GRACE_PERIOD = process.env.HEALTH_CHECK_GRACE_PERIOD || 60000;
|
|
10
|
+
|
|
11
|
+
const ADMIN_PASSWORD = process.env.PROXY_ADMIN_PASSWORD;
|
|
12
|
+
const SESSION_SECRET = process.env.PROXY_SESSION_SECRET || crypto.randomBytes(32).toString('hex');
|
|
13
|
+
|
|
14
|
+
const TERMINAL_CWD = process.env.PROXY_TERMINAL_CWD || process.env.HOME || process.env.USERPROFILE;
|
|
15
|
+
const TERMINAL_COMMAND = process.env.PROXY_TERMINAL_COMMAND || '';
|
|
16
|
+
const OPENCODE_CLEAN_UP = process.env.PROXY_OPENCODE_CLEAN_UP || '';
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
PROXY_PORT,
|
|
20
|
+
PROXY_NAME,
|
|
21
|
+
INTERNAL_PORT_START,
|
|
22
|
+
INTERNAL_PORT_END,
|
|
23
|
+
HEALTH_CHECK_INTERVAL,
|
|
24
|
+
HEALTH_CHECK_TIMEOUT,
|
|
25
|
+
HEALTH_CHECK_GRACE_PERIOD,
|
|
26
|
+
ADMIN_PASSWORD,
|
|
27
|
+
SESSION_SECRET,
|
|
28
|
+
TERMINAL_CWD,
|
|
29
|
+
TERMINAL_COMMAND,
|
|
30
|
+
OPENCODE_CLEAN_UP,
|
|
31
|
+
};
|
package/logger.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// FIXME: le prefix global n'est pas thread-safe si plusieurs workers partagent ce module
|
|
5
|
+
let currentPrefix = 'PROXY';
|
|
6
|
+
|
|
7
|
+
const SENSITIVE_KEYS = /password|secret|token|authorization|cookie|api[_-]?key/i;
|
|
8
|
+
|
|
9
|
+
function redactObject(obj, depth = 0) {
|
|
10
|
+
if (depth > 5 || obj === null || typeof obj !== 'object') return obj;
|
|
11
|
+
if (Array.isArray(obj)) return obj.map(item => redactObject(item, depth + 1));
|
|
12
|
+
const redacted = {};
|
|
13
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
14
|
+
if (SENSITIVE_KEYS.test(key)) {
|
|
15
|
+
redacted[key] = '[REDACTED]';
|
|
16
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
17
|
+
redacted[key] = redactObject(value, depth + 1);
|
|
18
|
+
} else {
|
|
19
|
+
redacted[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return redacted;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Log a message to a file at the root of the disk.
|
|
27
|
+
* @param {string} message The message to log
|
|
28
|
+
* @param {string} [prefix] Optional prefix to override
|
|
29
|
+
*/
|
|
30
|
+
function logToFile(message, prefix = '') {
|
|
31
|
+
const logDir = process.platform === 'win32'
|
|
32
|
+
? 'C:\\var\\log\\proxy-server'
|
|
33
|
+
: '/var/log/proxy-server';
|
|
34
|
+
const logFile = path.join(logDir, 'server.log');
|
|
35
|
+
const now = new Date().toLocaleString('fr-FR', {
|
|
36
|
+
year: 'numeric',
|
|
37
|
+
month: '2-digit',
|
|
38
|
+
day: '2-digit',
|
|
39
|
+
hour: '2-digit',
|
|
40
|
+
minute: '2-digit',
|
|
41
|
+
second: '2-digit',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const effectivePrefix = prefix || currentPrefix;
|
|
45
|
+
const label = effectivePrefix ? ` [${effectivePrefix}]` : "";
|
|
46
|
+
const logEntry = `[${now}] [PID ${process.pid}]${label} ${message}\n`;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
if (!fs.existsSync(logDir)) {
|
|
50
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
fs.appendFileSync(logFile, logEntry);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// On ne log pas l'erreur de log ici pour éviter une boucle infinie
|
|
55
|
+
// mais on l'affiche sur la console
|
|
56
|
+
process.stderr.write(`Critical: Failed to write to log file: ${error.message}\n`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Override console methods to also log to file
|
|
62
|
+
* @param {string} [prefix] Prefix for this process
|
|
63
|
+
*/
|
|
64
|
+
function setupLogging(prefix = 'PROXY') {
|
|
65
|
+
currentPrefix = prefix;
|
|
66
|
+
const originalLog = console.log;
|
|
67
|
+
const originalError = console.error;
|
|
68
|
+
|
|
69
|
+
console.log = (...args) => {
|
|
70
|
+
const message = args.map(arg =>
|
|
71
|
+
typeof arg === 'object' ? JSON.stringify(redactObject(arg), null, 2) : arg
|
|
72
|
+
).join(' ');
|
|
73
|
+
logToFile(message);
|
|
74
|
+
originalLog.apply(console, args);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
console.error = (...args) => {
|
|
78
|
+
const message = args.map(arg =>
|
|
79
|
+
typeof arg === 'object' ? JSON.stringify(redactObject(arg), null, 2) : arg
|
|
80
|
+
).join(' ');
|
|
81
|
+
logToFile(`[ERROR] ${message}`);
|
|
82
|
+
originalError.apply(console, args);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
logToFile('--- Logging system initialized ---');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { logToFile, setupLogging };
|