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.
Files changed (3) hide show
  1. package/README.md +48 -12
  2. package/package.json +1 -1
  3. 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
- npm install
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.mjs
38
+ node portokd.js
32
39
  ```
33
40
 
34
41
  ### Use the CLI
35
42
 
36
43
  ```bash
37
44
  # Check status
38
- ./portok.mjs status --token your-secret-token
45
+ portok status --token your-secret-token
39
46
 
40
47
  # Switch to new port
41
- ./portok.mjs switch 8081 --token your-secret-token
48
+ portok switch 8081 --token your-secret-token
42
49
 
43
50
  # Check metrics
44
- ./portok.mjs metrics --token your-secret-token
51
+ portok metrics --token your-secret-token
45
52
 
46
53
  # Check health
47
- ./portok.mjs health --token your-secret-token
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 systemctl start portok@api
244
+ sudo portok start api
245
+
246
+ # 4. Enable at boot
247
+ sudo portok enable api
224
248
 
225
- # 4. Check status
249
+ # 5. Check status
226
250
  portok status --instance api
227
251
 
228
- # 5. List all services
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.mjs
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.mjs --quick
587
+ FAST_PATH=1 node bench/validate.js --quick
552
588
 
553
589
  # Full validation (10s)
554
- FAST_PATH=1 node bench/validate.mjs
590
+ FAST_PATH=1 node bench/validate.js
555
591
 
556
592
  # Manual autocannon test
557
593
  # Direct:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portok",
3
- "version": "1.0.1",
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
5
  "main": "portokd.js",
6
6
  "bin": {
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 add web --port 3002 --target 8002
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
  }