portok 1.0.1 → 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/README.md +48 -12
- package/package.json +1 -1
- package/portok.js +118 -6
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:
|
package/package.json
CHANGED
package/portok.js
CHANGED
|
@@ -631,6 +631,92 @@ async function cmdHealth(baseUrl, token, options) {
|
|
|
631
631
|
}
|
|
632
632
|
}
|
|
633
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
|
+
|
|
634
720
|
function showHelp() {
|
|
635
721
|
console.log(`
|
|
636
722
|
${colors.bold}portok${colors.reset} - CLI for portokd zero-downtime proxy daemon
|
|
@@ -644,6 +730,14 @@ ${colors.bold}COMMANDS${colors.reset}
|
|
|
644
730
|
add <name> Create a new service instance
|
|
645
731
|
list List all configured instances and their status
|
|
646
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
|
+
|
|
647
741
|
${colors.cyan}Operations:${colors.reset}
|
|
648
742
|
status Show current proxy status (activePort, drainUntil, lastSwitch)
|
|
649
743
|
metrics Show proxy metrics (requests, errors, health, RPS)
|
|
@@ -663,17 +757,28 @@ ${colors.bold}OPTIONS${colors.reset}
|
|
|
663
757
|
--health <path> Health check path (default: /health)
|
|
664
758
|
--force Overwrite existing config
|
|
665
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
|
+
|
|
666
764
|
${colors.bold}EXAMPLES${colors.reset}
|
|
667
765
|
${colors.dim}# Initialize portok (run once, requires sudo)${colors.reset}
|
|
668
766
|
sudo portok init
|
|
669
767
|
|
|
670
|
-
${colors.dim}# Create a new service${colors.reset}
|
|
768
|
+
${colors.dim}# Create and start a new service${colors.reset}
|
|
671
769
|
sudo portok add api --port 3001 --target 8001
|
|
672
|
-
sudo portok
|
|
770
|
+
sudo portok start api
|
|
771
|
+
sudo portok enable api
|
|
673
772
|
|
|
674
773
|
${colors.dim}# List all instances${colors.reset}
|
|
675
774
|
portok list
|
|
676
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
|
+
|
|
677
782
|
${colors.dim}# Check status of an instance${colors.reset}
|
|
678
783
|
portok status --instance api
|
|
679
784
|
|
|
@@ -683,9 +788,6 @@ ${colors.bold}EXAMPLES${colors.reset}
|
|
|
683
788
|
${colors.dim}# Get metrics as JSON${colors.reset}
|
|
684
789
|
portok metrics --instance api --json
|
|
685
790
|
|
|
686
|
-
${colors.dim}# Direct URL mode (without instance)${colors.reset}
|
|
687
|
-
portok status --url http://127.0.0.1:3000 --token mysecret
|
|
688
|
-
|
|
689
791
|
${colors.bold}MULTI-INSTANCE${colors.reset}
|
|
690
792
|
When using --instance, the CLI reads /etc/portok/<name>.env
|
|
691
793
|
to resolve LISTEN_PORT and ADMIN_TOKEN for that instance.
|
|
@@ -711,7 +813,7 @@ async function main() {
|
|
|
711
813
|
}
|
|
712
814
|
|
|
713
815
|
// Management commands (don't require daemon connection)
|
|
714
|
-
const managementCommands = ['init', 'add', 'list'];
|
|
816
|
+
const managementCommands = ['init', 'add', 'list', 'start', 'stop', 'restart', 'enable', 'disable', 'logs'];
|
|
715
817
|
|
|
716
818
|
if (managementCommands.includes(args.command)) {
|
|
717
819
|
let exitCode = 1;
|
|
@@ -725,6 +827,16 @@ async function main() {
|
|
|
725
827
|
case 'list':
|
|
726
828
|
exitCode = await cmdList(args.options);
|
|
727
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;
|
|
728
840
|
}
|
|
729
841
|
process.exit(exitCode);
|
|
730
842
|
}
|