portok 1.0.0 → 1.0.2
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/Dockerfile +1 -1
- package/README.md +48 -12
- package/bench/{baseline.bench.mjs → baseline.bench.js} +5 -3
- package/bench/{connections.bench.mjs → connections.bench.js} +5 -3
- package/bench/{keepalive.bench.mjs → keepalive.bench.js} +15 -16
- package/bench/{latency.bench.mjs → latency.bench.js} +5 -3
- package/bench/{run.mjs → run.js} +11 -11
- package/bench/{switching.bench.mjs → switching.bench.js} +5 -3
- package/bench/{throughput.bench.mjs → throughput.bench.js} +5 -3
- package/bench/{validate.mjs → validate.js} +7 -7
- package/package.json +10 -12
- package/{portok.mjs → portok.js} +123 -10
- package/{portokd.mjs → portokd.js} +6 -5
- package/test/{cli.test.mjs → cli.test.js} +7 -6
- package/test/{drain.test.mjs → drain.test.js} +7 -7
- package/test/helpers/{mock-server.mjs → mock-server.js} +19 -11
- package/test/{metrics.test.mjs → metrics.test.js} +6 -6
- package/test/{proxy.test.mjs → proxy.test.js} +8 -7
- package/test/{rollback.test.mjs → rollback.test.js} +8 -8
- package/test/{security.test.mjs → security.test.js} +6 -6
- package/test/{switching.test.mjs → switching.test.js} +5 -5
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -15,8 +15,15 @@ A lightweight Node.js "switchboard" proxy that enables zero-downtime deployments
|
|
|
15
15
|
|
|
16
16
|
### Installation
|
|
17
17
|
|
|
18
|
+
**Global Installation (Recommended):**
|
|
19
|
+
|
|
18
20
|
```bash
|
|
19
|
-
|
|
21
|
+
# Install globally
|
|
22
|
+
npm install -g portok
|
|
23
|
+
|
|
24
|
+
# Now you can use portok and portokd commands from anywhere
|
|
25
|
+
portok --help
|
|
26
|
+
portokd --help
|
|
20
27
|
```
|
|
21
28
|
|
|
22
29
|
### Start the Daemon
|
|
@@ -28,25 +35,27 @@ export INITIAL_TARGET_PORT=8080
|
|
|
28
35
|
export ADMIN_TOKEN=your-secret-token
|
|
29
36
|
|
|
30
37
|
# Start portokd
|
|
31
|
-
node portokd.
|
|
38
|
+
node portokd.js
|
|
32
39
|
```
|
|
33
40
|
|
|
34
41
|
### Use the CLI
|
|
35
42
|
|
|
36
43
|
```bash
|
|
37
44
|
# Check status
|
|
38
|
-
|
|
45
|
+
portok status --token your-secret-token
|
|
39
46
|
|
|
40
47
|
# Switch to new port
|
|
41
|
-
|
|
48
|
+
portok switch 8081 --token your-secret-token
|
|
42
49
|
|
|
43
50
|
# Check metrics
|
|
44
|
-
|
|
51
|
+
portok metrics --token your-secret-token
|
|
45
52
|
|
|
46
53
|
# Check health
|
|
47
|
-
|
|
54
|
+
portok health --token your-secret-token
|
|
48
55
|
```
|
|
49
56
|
|
|
57
|
+
> **Note:** If not installed globally, use `./portok.js` instead of `portok`.
|
|
58
|
+
|
|
50
59
|
## Configuration
|
|
51
60
|
|
|
52
61
|
All configuration is via environment variables:
|
|
@@ -190,6 +199,14 @@ Management Commands:
|
|
|
190
199
|
add <name> Create a new service instance
|
|
191
200
|
list List all configured instances and their status
|
|
192
201
|
|
|
202
|
+
Service Control Commands:
|
|
203
|
+
start <name> Start a portok service (systemctl start portok@<name>)
|
|
204
|
+
stop <name> Stop a portok service
|
|
205
|
+
restart <name> Restart a portok service
|
|
206
|
+
enable <name> Enable service at boot
|
|
207
|
+
disable <name> Disable service at boot
|
|
208
|
+
logs <name> Show service logs (journalctl)
|
|
209
|
+
|
|
193
210
|
Operational Commands:
|
|
194
211
|
status Show current proxy status
|
|
195
212
|
metrics Show proxy metrics
|
|
@@ -208,6 +225,10 @@ Options for 'add' command:
|
|
|
208
225
|
--target <port> Target port (default: random 8000-8999)
|
|
209
226
|
--health <path> Health check path (default: /health)
|
|
210
227
|
--force Overwrite existing config
|
|
228
|
+
|
|
229
|
+
Options for 'logs' command:
|
|
230
|
+
--follow, -f Follow log output
|
|
231
|
+
--lines, -n Number of lines to show (default: 50)
|
|
211
232
|
```
|
|
212
233
|
|
|
213
234
|
### Quick Start with CLI
|
|
@@ -220,12 +241,15 @@ sudo portok init
|
|
|
220
241
|
sudo portok add api --port 3001 --target 8001
|
|
221
242
|
|
|
222
243
|
# 3. Start the service
|
|
223
|
-
sudo
|
|
244
|
+
sudo portok start api
|
|
245
|
+
|
|
246
|
+
# 4. Enable at boot
|
|
247
|
+
sudo portok enable api
|
|
224
248
|
|
|
225
|
-
#
|
|
249
|
+
# 5. Check status
|
|
226
250
|
portok status --instance api
|
|
227
251
|
|
|
228
|
-
#
|
|
252
|
+
# 6. List all services
|
|
229
253
|
portok list
|
|
230
254
|
```
|
|
231
255
|
|
|
@@ -244,6 +268,18 @@ sudo portok init
|
|
|
244
268
|
sudo portok add api --port 3001 --target 8001
|
|
245
269
|
sudo portok add web --port 3002 --target 8002
|
|
246
270
|
|
|
271
|
+
# Service management
|
|
272
|
+
sudo portok start api
|
|
273
|
+
sudo portok stop api
|
|
274
|
+
sudo portok restart api
|
|
275
|
+
sudo portok enable api # Enable at boot
|
|
276
|
+
sudo portok disable api # Disable at boot
|
|
277
|
+
|
|
278
|
+
# View logs
|
|
279
|
+
portok logs api
|
|
280
|
+
portok logs api --follow # Follow log output
|
|
281
|
+
portok logs api -n 100 # Show last 100 lines
|
|
282
|
+
|
|
247
283
|
# List all instances with status
|
|
248
284
|
portok list
|
|
249
285
|
|
|
@@ -276,7 +312,7 @@ After=network.target
|
|
|
276
312
|
Type=simple
|
|
277
313
|
User=www-data
|
|
278
314
|
WorkingDirectory=/opt/portok
|
|
279
|
-
ExecStart=/usr/bin/node /opt/portok/portokd.
|
|
315
|
+
ExecStart=/usr/bin/node /opt/portok/portokd.js
|
|
280
316
|
Restart=always
|
|
281
317
|
RestartSec=5
|
|
282
318
|
|
|
@@ -548,10 +584,10 @@ Run the validation benchmark to verify performance:
|
|
|
548
584
|
|
|
549
585
|
```bash
|
|
550
586
|
# Quick validation (3s)
|
|
551
|
-
FAST_PATH=1 node bench/validate.
|
|
587
|
+
FAST_PATH=1 node bench/validate.js --quick
|
|
552
588
|
|
|
553
589
|
# Full validation (10s)
|
|
554
|
-
FAST_PATH=1 node bench/validate.
|
|
590
|
+
FAST_PATH=1 node bench/validate.js
|
|
555
591
|
|
|
556
592
|
# Manual autocannon test
|
|
557
593
|
# Direct:
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Compares direct server performance vs proxied performance
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort, formatNumber } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
const shortDuration = Math.max(2, Math.floor(duration / 2));
|
|
11
11
|
|
|
12
12
|
// Setup mock server for direct test
|
|
@@ -71,3 +71,5 @@ export async function run({ duration, adminToken }) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
module.exports = { run };
|
|
75
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests scaling with increasing concurrent connections
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort, formatNumber } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
// Setup
|
|
11
11
|
const mockServer = await createMockServer();
|
|
12
12
|
const proxyPort = await getFreePort();
|
|
@@ -68,3 +68,5 @@ export async function run({ duration, adminToken }) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
module.exports = { run };
|
|
72
|
+
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* - Keep-alive should be at least 50% faster than no-keepalive
|
|
7
7
|
* - Proxied RPS should be at least 20% of direct RPS
|
|
8
8
|
*
|
|
9
|
-
* Can be run standalone or via bench/run.
|
|
10
|
-
* node bench/keepalive.bench.
|
|
9
|
+
* Can be run standalone or via bench/run.js:
|
|
10
|
+
* node bench/keepalive.bench.js --quick
|
|
11
11
|
* npm run bench
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const autocannon = require('autocannon');
|
|
15
|
+
const { spawn } = require('node:child_process');
|
|
16
|
+
const http = require('node:http');
|
|
17
17
|
|
|
18
18
|
// =============================================================================
|
|
19
19
|
// Utilities (used when running standalone)
|
|
@@ -62,7 +62,7 @@ async function createMockServer(port = 0) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
async function startDaemonWithKA(listenPort, targetPort, adminToken, keepAlive = true) {
|
|
65
|
-
const proc = spawn('node', ['portokd.
|
|
65
|
+
const proc = spawn('node', ['portokd.js'], {
|
|
66
66
|
env: {
|
|
67
67
|
...process.env,
|
|
68
68
|
LISTEN_PORT: String(listenPort),
|
|
@@ -99,10 +99,10 @@ async function startDaemonWithKA(listenPort, targetPort, adminToken, keepAlive =
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// =============================================================================
|
|
102
|
-
// Main run function (called by bench/run.
|
|
102
|
+
// Main run function (called by bench/run.js)
|
|
103
103
|
// =============================================================================
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
async function run({ duration, adminToken }) {
|
|
106
106
|
const shortDuration = Math.max(2, Math.floor(duration / 2));
|
|
107
107
|
const CONNECTIONS = 100;
|
|
108
108
|
const PIPELINING = 10;
|
|
@@ -187,9 +187,7 @@ export async function run({ duration, adminToken }) {
|
|
|
187
187
|
// Standalone execution
|
|
188
188
|
// =============================================================================
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (isMain) {
|
|
190
|
+
if (require.main === module) {
|
|
193
191
|
const args = process.argv.slice(2);
|
|
194
192
|
const isQuick = args.includes('--quick');
|
|
195
193
|
const isJson = args.includes('--json');
|
|
@@ -209,9 +207,7 @@ if (isMain) {
|
|
|
209
207
|
log('╚══════════════════════════════════════════════════════════════╝');
|
|
210
208
|
log('');
|
|
211
209
|
|
|
212
|
-
|
|
213
|
-
const results = await run({ duration, adminToken });
|
|
214
|
-
|
|
210
|
+
run({ duration, adminToken }).then(results => {
|
|
215
211
|
// Validation
|
|
216
212
|
const rpsPass = results.proxiedKeepAlive.rps >= results.direct.rps * 0.20;
|
|
217
213
|
const kaEffective = results.proxiedKeepAlive.rps >= results.proxiedNoKeepAlive.rps * 1.5;
|
|
@@ -241,8 +237,11 @@ if (isMain) {
|
|
|
241
237
|
}
|
|
242
238
|
|
|
243
239
|
process.exit(0);
|
|
244
|
-
}
|
|
240
|
+
}).catch(err => {
|
|
245
241
|
console.error('Benchmark failed:', err);
|
|
246
242
|
process.exit(1);
|
|
247
|
-
}
|
|
243
|
+
});
|
|
248
244
|
}
|
|
245
|
+
|
|
246
|
+
module.exports = { run };
|
|
247
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Measures latency distribution under moderate load
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
// Setup
|
|
11
11
|
const mockServer = await createMockServer();
|
|
12
12
|
const proxyPort = await getFreePort();
|
|
@@ -45,3 +45,5 @@ export async function run({ duration, adminToken }) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
module.exports = { run };
|
|
49
|
+
|
package/bench/{run.mjs → run.js}
RENAMED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Orchestrates all benchmarks and outputs formatted results
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const http = require('node:http');
|
|
10
10
|
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// Configuration
|
|
@@ -82,7 +82,7 @@ async function createMockServer(port = 0) {
|
|
|
82
82
|
// =============================================================================
|
|
83
83
|
|
|
84
84
|
async function startDaemon(listenPort, targetPort) {
|
|
85
|
-
const proc = spawn('node', ['portokd.
|
|
85
|
+
const proc = spawn('node', ['portokd.js'], {
|
|
86
86
|
env: {
|
|
87
87
|
...process.env,
|
|
88
88
|
LISTEN_PORT: String(listenPort),
|
|
@@ -116,7 +116,7 @@ async function startDaemon(listenPort, targetPort) {
|
|
|
116
116
|
// =============================================================================
|
|
117
117
|
|
|
118
118
|
async function runBenchmark(file) {
|
|
119
|
-
const module =
|
|
119
|
+
const module = require(file);
|
|
120
120
|
return module.run({ duration, adminToken: ADMIN_TOKEN });
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -165,12 +165,12 @@ async function main() {
|
|
|
165
165
|
|
|
166
166
|
// Run each benchmark
|
|
167
167
|
const benchmarks = [
|
|
168
|
-
{ name: 'Throughput', file: './throughput.bench.
|
|
169
|
-
{ name: 'Latency', file: './latency.bench.
|
|
170
|
-
{ name: 'Connections', file: './connections.bench.
|
|
171
|
-
{ name: 'Switching', file: './switching.bench.
|
|
172
|
-
{ name: 'Baseline', file: './baseline.bench.
|
|
173
|
-
{ name: 'KeepAlive', file: './keepalive.bench.
|
|
168
|
+
{ name: 'Throughput', file: './throughput.bench.js' },
|
|
169
|
+
{ name: 'Latency', file: './latency.bench.js' },
|
|
170
|
+
{ name: 'Connections', file: './connections.bench.js' },
|
|
171
|
+
{ name: 'Switching', file: './switching.bench.js' },
|
|
172
|
+
{ name: 'Baseline', file: './baseline.bench.js' },
|
|
173
|
+
{ name: 'KeepAlive', file: './keepalive.bench.js' },
|
|
174
174
|
];
|
|
175
175
|
|
|
176
176
|
for (const bench of benchmarks) {
|
|
@@ -207,5 +207,5 @@ main().catch(err => {
|
|
|
207
207
|
process.exit(1);
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
module.exports = { createMockServer, startDaemon, getFreePort, waitFor, formatNumber, ADMIN_TOKEN };
|
|
211
211
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Measures switch latency and request loss during switch
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort, formatNumber, ADMIN_TOKEN } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
// Setup two servers
|
|
11
11
|
const mockServer1 = await createMockServer();
|
|
12
12
|
const mockServer2 = await createMockServer();
|
|
@@ -94,3 +94,5 @@ export async function run({ duration, adminToken }) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
module.exports = { run };
|
|
98
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Measures maximum requests per second with high concurrency
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort, formatNumber } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
// Setup
|
|
11
11
|
const mockServer = await createMockServer();
|
|
12
12
|
const proxyPort = await getFreePort();
|
|
@@ -42,3 +42,5 @@ export async function run({ duration, adminToken }) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
module.exports = { run };
|
|
46
|
+
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* - p99 <= 50ms
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
|
-
* node bench/validate.
|
|
13
|
-
* node bench/validate.
|
|
14
|
-
* FAST_PATH=1 node bench/validate.
|
|
12
|
+
* node bench/validate.js
|
|
13
|
+
* node bench/validate.js --quick
|
|
14
|
+
* FAST_PATH=1 node bench/validate.js
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const autocannon = require('autocannon');
|
|
18
|
+
const { spawn } = require('node:child_process');
|
|
19
|
+
const http = require('node:http');
|
|
20
20
|
|
|
21
21
|
const DURATION = parseInt(process.env.BENCH_DURATION || '10', 10);
|
|
22
22
|
const CONNECTIONS = parseInt(process.env.BENCH_CONNECTIONS || '50', 10);
|
|
@@ -72,7 +72,7 @@ async function createMockServer() {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
async function startProxy(listenPort, targetPort) {
|
|
75
|
-
const proc = spawn('node', ['portokd.
|
|
75
|
+
const proc = spawn('node', ['portokd.js'], {
|
|
76
76
|
env: {
|
|
77
77
|
...process.env,
|
|
78
78
|
LISTEN_PORT: String(listenPort),
|
package/package.json
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portok",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Zero-downtime deployment proxy - routes traffic through a stable port to internal app instances with health-gated switching",
|
|
5
|
-
"
|
|
6
|
-
"main": "portokd.mjs",
|
|
5
|
+
"main": "portokd.js",
|
|
7
6
|
"bin": {
|
|
8
|
-
"portok": "./portok.
|
|
9
|
-
"portokd": "./portokd.
|
|
7
|
+
"portok": "./portok.js",
|
|
8
|
+
"portokd": "./portokd.js"
|
|
10
9
|
},
|
|
11
10
|
"scripts": {
|
|
12
|
-
"start": "node portokd.
|
|
13
|
-
"test": "node --test --test-timeout=120000 test/*.test.
|
|
14
|
-
"test:watch": "node --test --watch test/*.test.
|
|
15
|
-
"bench": "node bench/run.
|
|
16
|
-
"bench:quick": "node bench/run.
|
|
17
|
-
"bench:json": "node bench/run.
|
|
11
|
+
"start": "node portokd.js",
|
|
12
|
+
"test": "node --test --test-timeout=120000 test/*.test.js",
|
|
13
|
+
"test:watch": "node --test --watch test/*.test.js",
|
|
14
|
+
"bench": "node bench/run.js",
|
|
15
|
+
"bench:quick": "node bench/run.js --quick",
|
|
16
|
+
"bench:json": "node bench/run.js --json"
|
|
18
17
|
},
|
|
19
18
|
"keywords": [
|
|
20
19
|
"proxy",
|
|
@@ -36,4 +35,3 @@
|
|
|
36
35
|
"node": ">=20.0.0"
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
|
-
|
package/{portok.mjs → portok.js}
RENAMED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* Communicates with the daemon via HTTP to query status, metrics, and trigger switches.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
|
|
8
12
|
// =============================================================================
|
|
9
13
|
// ANSI Colors (only if TTY)
|
|
10
14
|
// =============================================================================
|
|
@@ -61,9 +65,6 @@ function parseArgs(args) {
|
|
|
61
65
|
// Env File Parser (for --instance support)
|
|
62
66
|
// =============================================================================
|
|
63
67
|
|
|
64
|
-
import fs from 'node:fs';
|
|
65
|
-
import path from 'node:path';
|
|
66
|
-
|
|
67
68
|
const ENV_FILE_DIR = '/etc/portok';
|
|
68
69
|
|
|
69
70
|
/**
|
|
@@ -384,7 +385,7 @@ async function cmdInit(options) {
|
|
|
384
385
|
function generateToken(length = 32) {
|
|
385
386
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
386
387
|
let token = '';
|
|
387
|
-
const randomBytes =
|
|
388
|
+
const randomBytes = crypto.randomBytes(length);
|
|
388
389
|
for (let i = 0; i < length; i++) {
|
|
389
390
|
token += chars[randomBytes[i] % chars.length];
|
|
390
391
|
}
|
|
@@ -630,6 +631,92 @@ async function cmdHealth(baseUrl, token, options) {
|
|
|
630
631
|
}
|
|
631
632
|
}
|
|
632
633
|
|
|
634
|
+
// =============================================================================
|
|
635
|
+
// Systemctl Commands (start, stop, restart, enable, disable)
|
|
636
|
+
// =============================================================================
|
|
637
|
+
|
|
638
|
+
const { execSync } = require('node:child_process');
|
|
639
|
+
|
|
640
|
+
async function cmdSystemctl(action, name, options) {
|
|
641
|
+
if (!name) {
|
|
642
|
+
console.error(`${colors.red}Error:${colors.reset} Instance name is required`);
|
|
643
|
+
console.error(`Usage: portok ${action} <name>`);
|
|
644
|
+
return 1;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Validate name exists
|
|
648
|
+
const envFilePath = path.join(ENV_FILE_DIR, `${name}.env`);
|
|
649
|
+
if (!fs.existsSync(envFilePath)) {
|
|
650
|
+
console.error(`${colors.red}Error:${colors.reset} Instance '${name}' not found`);
|
|
651
|
+
console.error(`Make sure ${envFilePath} exists. Run ${colors.cyan}portok add ${name}${colors.reset} to create it.`);
|
|
652
|
+
return 1;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const serviceName = `portok@${name}`;
|
|
656
|
+
const actionVerb = {
|
|
657
|
+
start: 'Starting',
|
|
658
|
+
stop: 'Stopping',
|
|
659
|
+
restart: 'Restarting',
|
|
660
|
+
enable: 'Enabling',
|
|
661
|
+
disable: 'Disabling',
|
|
662
|
+
}[action];
|
|
663
|
+
|
|
664
|
+
console.log(`${colors.cyan}${actionVerb} ${serviceName}...${colors.reset}`);
|
|
665
|
+
|
|
666
|
+
try {
|
|
667
|
+
execSync(`systemctl ${action} ${serviceName}`, { stdio: 'inherit' });
|
|
668
|
+
|
|
669
|
+
const pastVerb = {
|
|
670
|
+
start: 'started',
|
|
671
|
+
stop: 'stopped',
|
|
672
|
+
restart: 'restarted',
|
|
673
|
+
enable: 'enabled',
|
|
674
|
+
disable: 'disabled',
|
|
675
|
+
}[action];
|
|
676
|
+
|
|
677
|
+
console.log(`${colors.green}✓ Service ${serviceName} ${pastVerb}${colors.reset}`);
|
|
678
|
+
|
|
679
|
+
// For start/restart, show status after
|
|
680
|
+
if (action === 'start' || action === 'restart') {
|
|
681
|
+
console.log('');
|
|
682
|
+
try {
|
|
683
|
+
execSync(`systemctl status ${serviceName} --no-pager -l`, { stdio: 'inherit' });
|
|
684
|
+
} catch {
|
|
685
|
+
// Status might return non-zero even when running, ignore
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return 0;
|
|
690
|
+
} catch (err) {
|
|
691
|
+
console.error(`${colors.red}Failed to ${action} ${serviceName}${colors.reset}`);
|
|
692
|
+
if (err.status === 1) {
|
|
693
|
+
console.error(`${colors.yellow}Hint:${colors.reset} This command may require sudo privileges.`);
|
|
694
|
+
console.error(`Try: ${colors.cyan}sudo portok ${action} ${name}${colors.reset}`);
|
|
695
|
+
}
|
|
696
|
+
return 1;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async function cmdLogs(name, options) {
|
|
701
|
+
if (!name) {
|
|
702
|
+
console.error(`${colors.red}Error:${colors.reset} Instance name is required`);
|
|
703
|
+
console.error('Usage: portok logs <name> [--follow]');
|
|
704
|
+
return 1;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const serviceName = `portok@${name}`;
|
|
708
|
+
const followFlag = options.follow || options.f ? '-f' : '';
|
|
709
|
+
const lines = options.lines || options.n || '50';
|
|
710
|
+
|
|
711
|
+
try {
|
|
712
|
+
execSync(`journalctl -u ${serviceName} -n ${lines} ${followFlag} --no-pager`, { stdio: 'inherit' });
|
|
713
|
+
return 0;
|
|
714
|
+
} catch (err) {
|
|
715
|
+
console.error(`${colors.red}Failed to get logs for ${serviceName}${colors.reset}`);
|
|
716
|
+
return 1;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
633
720
|
function showHelp() {
|
|
634
721
|
console.log(`
|
|
635
722
|
${colors.bold}portok${colors.reset} - CLI for portokd zero-downtime proxy daemon
|
|
@@ -643,6 +730,14 @@ ${colors.bold}COMMANDS${colors.reset}
|
|
|
643
730
|
add <name> Create a new service instance
|
|
644
731
|
list List all configured instances and their status
|
|
645
732
|
|
|
733
|
+
${colors.cyan}Service Control:${colors.reset}
|
|
734
|
+
start <name> Start a portok service (systemctl start portok@<name>)
|
|
735
|
+
stop <name> Stop a portok service
|
|
736
|
+
restart <name> Restart a portok service
|
|
737
|
+
enable <name> Enable service at boot
|
|
738
|
+
disable <name> Disable service at boot
|
|
739
|
+
logs <name> Show service logs (journalctl)
|
|
740
|
+
|
|
646
741
|
${colors.cyan}Operations:${colors.reset}
|
|
647
742
|
status Show current proxy status (activePort, drainUntil, lastSwitch)
|
|
648
743
|
metrics Show proxy metrics (requests, errors, health, RPS)
|
|
@@ -662,17 +757,28 @@ ${colors.bold}OPTIONS${colors.reset}
|
|
|
662
757
|
--health <path> Health check path (default: /health)
|
|
663
758
|
--force Overwrite existing config
|
|
664
759
|
|
|
760
|
+
${colors.dim}For 'logs' command:${colors.reset}
|
|
761
|
+
--follow, -f Follow log output
|
|
762
|
+
--lines, -n Number of lines to show (default: 50)
|
|
763
|
+
|
|
665
764
|
${colors.bold}EXAMPLES${colors.reset}
|
|
666
765
|
${colors.dim}# Initialize portok (run once, requires sudo)${colors.reset}
|
|
667
766
|
sudo portok init
|
|
668
767
|
|
|
669
|
-
${colors.dim}# Create a new service${colors.reset}
|
|
768
|
+
${colors.dim}# Create and start a new service${colors.reset}
|
|
670
769
|
sudo portok add api --port 3001 --target 8001
|
|
671
|
-
sudo portok
|
|
770
|
+
sudo portok start api
|
|
771
|
+
sudo portok enable api
|
|
672
772
|
|
|
673
773
|
${colors.dim}# List all instances${colors.reset}
|
|
674
774
|
portok list
|
|
675
775
|
|
|
776
|
+
${colors.dim}# Service management${colors.reset}
|
|
777
|
+
sudo portok start api
|
|
778
|
+
sudo portok stop api
|
|
779
|
+
sudo portok restart api
|
|
780
|
+
portok logs api --follow
|
|
781
|
+
|
|
676
782
|
${colors.dim}# Check status of an instance${colors.reset}
|
|
677
783
|
portok status --instance api
|
|
678
784
|
|
|
@@ -682,9 +788,6 @@ ${colors.bold}EXAMPLES${colors.reset}
|
|
|
682
788
|
${colors.dim}# Get metrics as JSON${colors.reset}
|
|
683
789
|
portok metrics --instance api --json
|
|
684
790
|
|
|
685
|
-
${colors.dim}# Direct URL mode (without instance)${colors.reset}
|
|
686
|
-
portok status --url http://127.0.0.1:3000 --token mysecret
|
|
687
|
-
|
|
688
791
|
${colors.bold}MULTI-INSTANCE${colors.reset}
|
|
689
792
|
When using --instance, the CLI reads /etc/portok/<name>.env
|
|
690
793
|
to resolve LISTEN_PORT and ADMIN_TOKEN for that instance.
|
|
@@ -710,7 +813,7 @@ async function main() {
|
|
|
710
813
|
}
|
|
711
814
|
|
|
712
815
|
// Management commands (don't require daemon connection)
|
|
713
|
-
const managementCommands = ['init', 'add', 'list'];
|
|
816
|
+
const managementCommands = ['init', 'add', 'list', 'start', 'stop', 'restart', 'enable', 'disable', 'logs'];
|
|
714
817
|
|
|
715
818
|
if (managementCommands.includes(args.command)) {
|
|
716
819
|
let exitCode = 1;
|
|
@@ -724,6 +827,16 @@ async function main() {
|
|
|
724
827
|
case 'list':
|
|
725
828
|
exitCode = await cmdList(args.options);
|
|
726
829
|
break;
|
|
830
|
+
case 'start':
|
|
831
|
+
case 'stop':
|
|
832
|
+
case 'restart':
|
|
833
|
+
case 'enable':
|
|
834
|
+
case 'disable':
|
|
835
|
+
exitCode = await cmdSystemctl(args.command, args.positional[0], args.options);
|
|
836
|
+
break;
|
|
837
|
+
case 'logs':
|
|
838
|
+
exitCode = await cmdLogs(args.positional[0], args.options);
|
|
839
|
+
break;
|
|
727
840
|
}
|
|
728
841
|
process.exit(exitCode);
|
|
729
842
|
}
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* - Connection header stripping for proper keep-alive upstream
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const http = require('http');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const crypto = require('crypto');
|
|
19
|
+
const httpProxy = require('http-proxy');
|
|
20
20
|
|
|
21
21
|
// =============================================================================
|
|
22
22
|
// Configuration
|
|
@@ -779,7 +779,7 @@ process.on('SIGINT', () => {
|
|
|
779
779
|
});
|
|
780
780
|
|
|
781
781
|
// Export for testing
|
|
782
|
-
|
|
782
|
+
module.exports = {
|
|
783
783
|
config,
|
|
784
784
|
state,
|
|
785
785
|
metrics,
|
|
@@ -791,3 +791,4 @@ export {
|
|
|
791
791
|
server,
|
|
792
792
|
upstreamAgent,
|
|
793
793
|
};
|
|
794
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests the portok CLI commands
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn, execSync } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('CLI', () => {
|
|
12
12
|
let mockServer;
|
|
@@ -18,7 +18,7 @@ describe('CLI', () => {
|
|
|
18
18
|
mockServer = await createMockServer({ port: 0 });
|
|
19
19
|
proxyPort = await getFreePort();
|
|
20
20
|
|
|
21
|
-
daemonProcess = spawn('node', ['portokd.
|
|
21
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
22
22
|
env: {
|
|
23
23
|
...process.env,
|
|
24
24
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -64,7 +64,7 @@ describe('CLI', () => {
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
try {
|
|
67
|
-
const output = execSync(`node portok.
|
|
67
|
+
const output = execSync(`node portok.js ${args}`, {
|
|
68
68
|
encoding: 'utf-8',
|
|
69
69
|
env,
|
|
70
70
|
timeout: 15000,
|
|
@@ -218,3 +218,4 @@ describe('CLI', () => {
|
|
|
218
218
|
});
|
|
219
219
|
});
|
|
220
220
|
});
|
|
221
|
+
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Tests that existing connections drain to old port while new connections go to new port
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const http = require('node:http');
|
|
9
|
+
const { spawn } = require('node:child_process');
|
|
10
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
11
11
|
|
|
12
12
|
describe('Connection Draining', () => {
|
|
13
13
|
let mockServer1;
|
|
@@ -24,7 +24,7 @@ describe('Connection Draining', () => {
|
|
|
24
24
|
proxyPort = await getFreePort();
|
|
25
25
|
|
|
26
26
|
// Start daemon with longer drain time for testing
|
|
27
|
-
daemonProcess = spawn('node', ['portokd.
|
|
27
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
28
28
|
env: {
|
|
29
29
|
...process.env,
|
|
30
30
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -148,7 +148,7 @@ describe('Connection Draining', () => {
|
|
|
148
148
|
it('should clear drainUntil after drain period', async () => {
|
|
149
149
|
// Use a fresh daemon with shorter drain for this test
|
|
150
150
|
const shortDrainPort = await getFreePort();
|
|
151
|
-
const shortDaemon = spawn('node', ['portokd.
|
|
151
|
+
const shortDaemon = spawn('node', ['portokd.js'], {
|
|
152
152
|
env: {
|
|
153
153
|
...process.env,
|
|
154
154
|
LISTEN_PORT: String(shortDrainPort),
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
* Provides configurable responses, delays, and health status
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const http = require('http');
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
let WebSocketServer;
|
|
8
|
+
// Try to load ws, handle when not available
|
|
9
|
+
let WebSocketServer = null;
|
|
10
10
|
try {
|
|
11
|
-
const ws =
|
|
11
|
+
const ws = require('ws');
|
|
12
12
|
WebSocketServer = ws.WebSocketServer;
|
|
13
13
|
} catch {
|
|
14
|
-
|
|
14
|
+
// ws not available
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -24,7 +24,7 @@ try {
|
|
|
24
24
|
* @param {boolean} options.enableWebSocket - Enable WebSocket support (default: true)
|
|
25
25
|
* @returns {Promise<MockServer>}
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
async function createMockServer(options = {}) {
|
|
28
28
|
const config = {
|
|
29
29
|
port: options.port || 0,
|
|
30
30
|
healthStatus: options.healthStatus ?? 200,
|
|
@@ -202,7 +202,7 @@ export async function createMockServer(options = {}) {
|
|
|
202
202
|
* Create the portokd server for testing
|
|
203
203
|
* Sets up environment and spawns the daemon
|
|
204
204
|
*/
|
|
205
|
-
|
|
205
|
+
async function createTestDaemon(options = {}) {
|
|
206
206
|
const {
|
|
207
207
|
listenPort = 0,
|
|
208
208
|
targetPort,
|
|
@@ -226,7 +226,7 @@ export async function createTestDaemon(options = {}) {
|
|
|
226
226
|
process.env.STATE_FILE = `/tmp/portok-test-${Date.now()}.json`;
|
|
227
227
|
|
|
228
228
|
// Import the daemon (will start based on env vars)
|
|
229
|
-
const daemon =
|
|
229
|
+
const daemon = require('../../portokd.js');
|
|
230
230
|
|
|
231
231
|
// Wait for server to be ready
|
|
232
232
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -248,7 +248,7 @@ export async function createTestDaemon(options = {}) {
|
|
|
248
248
|
/**
|
|
249
249
|
* Helper to make admin requests
|
|
250
250
|
*/
|
|
251
|
-
|
|
251
|
+
async function adminRequest(baseUrl, endpoint, options = {}) {
|
|
252
252
|
const { method = 'GET', token = 'test-token-12345', body } = options;
|
|
253
253
|
|
|
254
254
|
const fetchOptions = {
|
|
@@ -276,7 +276,7 @@ export async function adminRequest(baseUrl, endpoint, options = {}) {
|
|
|
276
276
|
/**
|
|
277
277
|
* Helper to wait for a condition
|
|
278
278
|
*/
|
|
279
|
-
|
|
279
|
+
async function waitFor(condition, timeout = 5000, interval = 100) {
|
|
280
280
|
const startTime = Date.now();
|
|
281
281
|
|
|
282
282
|
while (Date.now() - startTime < timeout) {
|
|
@@ -292,7 +292,7 @@ export async function waitFor(condition, timeout = 5000, interval = 100) {
|
|
|
292
292
|
/**
|
|
293
293
|
* Helper to get a free port
|
|
294
294
|
*/
|
|
295
|
-
|
|
295
|
+
async function getFreePort() {
|
|
296
296
|
return new Promise((resolve, reject) => {
|
|
297
297
|
const server = http.createServer();
|
|
298
298
|
server.listen(0, '127.0.0.1', () => {
|
|
@@ -303,3 +303,11 @@ export async function getFreePort() {
|
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
module.exports = {
|
|
307
|
+
createMockServer,
|
|
308
|
+
createTestDaemon,
|
|
309
|
+
adminRequest,
|
|
310
|
+
waitFor,
|
|
311
|
+
getFreePort,
|
|
312
|
+
};
|
|
313
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests metrics collection: counters, RPS, inflight tracking
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('Metrics', () => {
|
|
12
12
|
let mockServer;
|
|
@@ -18,7 +18,7 @@ describe('Metrics', () => {
|
|
|
18
18
|
mockServer = await createMockServer({ port: 0 });
|
|
19
19
|
proxyPort = await getFreePort();
|
|
20
20
|
|
|
21
|
-
daemonProcess = spawn('node', ['portokd.
|
|
21
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
22
22
|
env: {
|
|
23
23
|
...process.env,
|
|
24
24
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -279,7 +279,7 @@ describe('Metrics', () => {
|
|
|
279
279
|
it('should record last proxy error details', async () => {
|
|
280
280
|
// Create a daemon with invalid target port
|
|
281
281
|
const badPort = await getFreePort();
|
|
282
|
-
const badDaemon = spawn('node', ['portokd.
|
|
282
|
+
const badDaemon = spawn('node', ['portokd.js'], {
|
|
283
283
|
env: {
|
|
284
284
|
...process.env,
|
|
285
285
|
LISTEN_PORT: String(badPort),
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* Tests HTTP proxying, WebSocket upgrades, and streaming
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const { describe, it, before, after, beforeEach } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const http = require('node:http');
|
|
9
|
+
const { spawn } = require('node:child_process');
|
|
10
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
11
|
+
const WebSocket = require('ws');
|
|
12
12
|
|
|
13
13
|
describe('Proxy Core', () => {
|
|
14
14
|
let mockServer;
|
|
@@ -24,7 +24,7 @@ describe('Proxy Core', () => {
|
|
|
24
24
|
proxyPort = await getFreePort();
|
|
25
25
|
|
|
26
26
|
// Start the daemon as a subprocess
|
|
27
|
-
daemonProcess = spawn('node', ['portokd.
|
|
27
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
28
28
|
env: {
|
|
29
29
|
...process.env,
|
|
30
30
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -36,6 +36,7 @@ describe('Proxy Core', () => {
|
|
|
36
36
|
ROLLBACK_CHECK_EVERY_MS: '500',
|
|
37
37
|
ROLLBACK_FAIL_THRESHOLD: '3',
|
|
38
38
|
ADMIN_RATE_LIMIT: '1000',
|
|
39
|
+
ENABLE_XFWD: '1',
|
|
39
40
|
},
|
|
40
41
|
stdio: 'pipe',
|
|
41
42
|
});
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests automatic rollback when health checks fail consecutively
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('Auto Rollback', () => {
|
|
12
12
|
const adminToken = 'test-token-rollback';
|
|
@@ -24,7 +24,7 @@ describe('Auto Rollback', () => {
|
|
|
24
24
|
proxyPort = await getFreePort();
|
|
25
25
|
|
|
26
26
|
// Start daemon with aggressive rollback settings for testing
|
|
27
|
-
daemonProcess = spawn('node', ['portokd.
|
|
27
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
28
28
|
env: {
|
|
29
29
|
...process.env,
|
|
30
30
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -126,7 +126,7 @@ describe('Auto Rollback', () => {
|
|
|
126
126
|
|
|
127
127
|
proxyPort = await getFreePort();
|
|
128
128
|
|
|
129
|
-
daemonProcess = spawn('node', ['portokd.
|
|
129
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
130
130
|
env: {
|
|
131
131
|
...process.env,
|
|
132
132
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -208,7 +208,7 @@ describe('Auto Rollback', () => {
|
|
|
208
208
|
|
|
209
209
|
proxyPort = await getFreePort();
|
|
210
210
|
|
|
211
|
-
daemonProcess = spawn('node', ['portokd.
|
|
211
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
212
212
|
env: {
|
|
213
213
|
...process.env,
|
|
214
214
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -280,7 +280,7 @@ describe('Auto Rollback', () => {
|
|
|
280
280
|
|
|
281
281
|
proxyPort = await getFreePort();
|
|
282
282
|
|
|
283
|
-
daemonProcess = spawn('node', ['portokd.
|
|
283
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
284
284
|
env: {
|
|
285
285
|
...process.env,
|
|
286
286
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests token validation, IP allowlist, and rate limiting
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('Security', () => {
|
|
12
12
|
let mockServer;
|
|
@@ -18,7 +18,7 @@ describe('Security', () => {
|
|
|
18
18
|
mockServer = await createMockServer({ port: 0 });
|
|
19
19
|
proxyPort = await getFreePort();
|
|
20
20
|
|
|
21
|
-
daemonProcess = spawn('node', ['portokd.
|
|
21
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
22
22
|
env: {
|
|
23
23
|
...process.env,
|
|
24
24
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -187,7 +187,7 @@ describe('Security - Rate Limit Test', () => {
|
|
|
187
187
|
proxyPort = await getFreePort();
|
|
188
188
|
|
|
189
189
|
// Use rate limit of 15 - waitFor uses ~5 requests, then we make 15 more
|
|
190
|
-
daemonProcess = spawn('node', ['portokd.
|
|
190
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
191
191
|
env: {
|
|
192
192
|
...process.env,
|
|
193
193
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests the /__switch endpoint with health checks
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after, beforeEach } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor, adminRequest } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('Health-Gated Switching', () => {
|
|
12
12
|
let mockServer1;
|
|
@@ -24,7 +24,7 @@ describe('Health-Gated Switching', () => {
|
|
|
24
24
|
proxyPort = await getFreePort();
|
|
25
25
|
|
|
26
26
|
// Start the daemon
|
|
27
|
-
daemonProcess = spawn('node', ['portokd.
|
|
27
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
28
28
|
env: {
|
|
29
29
|
...process.env,
|
|
30
30
|
LISTEN_PORT: String(proxyPort),
|