port-nuker 1.0.3 → 1.0.5
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 -0
- package/index.js +272 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,9 @@ nuke <PORT>
|
|
|
32
32
|
# Kill and wait until port is actually free
|
|
33
33
|
nuke <PORT> --wait
|
|
34
34
|
|
|
35
|
+
# Force kill even on protected ports
|
|
36
|
+
nuke <PORT> --force
|
|
37
|
+
|
|
35
38
|
# List all active ports and select one to kill interactively
|
|
36
39
|
nuke list
|
|
37
40
|
```
|
|
@@ -87,6 +90,8 @@ nuke 5000 --wait && echo "Port is free!"
|
|
|
87
90
|
## Features
|
|
88
91
|
|
|
89
92
|
- **Cross-Platform**: Works on Windows (using `netstat` + `taskkill`) and Unix-like systems (Linux/macOS using `lsof` + `kill`)
|
|
93
|
+
- **Docker Awareness**: Detects when a port is held by Docker and offers to stop the specific container instead of killing the entire Docker daemon
|
|
94
|
+
- **Safe Mode Protection**: Protected ports (22, 80, 443, 3306, 5432, 6379, 27017, 5000, 8080) require `--force` flag to prevent accidental termination of critical services
|
|
90
95
|
- **Interactive Mode**: Browse all active ports with `nuke list` and select which one to kill
|
|
91
96
|
- **Wait Mode**: Block until port is actually released with `--wait` flag for safe command chaining
|
|
92
97
|
- **Detailed Process Info**: See PID, command name, user, memory usage, and protocol for each port
|
|
@@ -113,6 +118,49 @@ nuke 8080
|
|
|
113
118
|
node server.js
|
|
114
119
|
```
|
|
115
120
|
|
|
121
|
+
### Protected Ports (Safe Mode)
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Attempting to kill a protected port (e.g., SSH on 22)
|
|
125
|
+
nuke 22
|
|
126
|
+
# Output:
|
|
127
|
+
# ⚠️ Port 22 is protected (SSH).
|
|
128
|
+
# This port is typically used for critical services.
|
|
129
|
+
# Use --force to override: nuke 22 --force
|
|
130
|
+
|
|
131
|
+
# Force kill a protected port
|
|
132
|
+
nuke 443 --force
|
|
133
|
+
# Successfully nukes the process on port 443 (HTTPS)
|
|
134
|
+
|
|
135
|
+
# Protected ports: 22 (SSH), 80 (HTTP), 443 (HTTPS), 3306 (MySQL),
|
|
136
|
+
# 5432 (PostgreSQL), 6379 (Redis), 27017 (MongoDB),
|
|
137
|
+
# 5000 (Flask/Docker), 8080 (Alt HTTP)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Docker Containers
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# When a port is held by Docker, port-nuker detects it automatically
|
|
144
|
+
nuke 3000
|
|
145
|
+
|
|
146
|
+
# Output:
|
|
147
|
+
# 🐳 Detected Docker process (PID: 12345)
|
|
148
|
+
# Searching for container using this port...
|
|
149
|
+
#
|
|
150
|
+
# 📦 Found container using port 3000:
|
|
151
|
+
# Name: my-app
|
|
152
|
+
# ID: abc123def456
|
|
153
|
+
# Ports: 0.0.0.0:3000->3000/tcp
|
|
154
|
+
#
|
|
155
|
+
# What would you like to do?
|
|
156
|
+
# 1. Stop container 'my-app' (recommended)
|
|
157
|
+
# 2. ⚠️ Kill Docker daemon (stops ALL containers)
|
|
158
|
+
# 3. Cancel
|
|
159
|
+
|
|
160
|
+
# Selecting option 1 stops only the specific container
|
|
161
|
+
# ✅ Successfully stopped container abc123def456
|
|
162
|
+
```
|
|
163
|
+
|
|
116
164
|
## Requirements
|
|
117
165
|
|
|
118
166
|
- Node.js >= 12.0.0
|
package/index.js
CHANGED
|
@@ -5,16 +5,34 @@ const os = require('os');
|
|
|
5
5
|
const inquirer = require('inquirer');
|
|
6
6
|
const Table = require('cli-table3');
|
|
7
7
|
|
|
8
|
+
// Protected ports that require --force flag
|
|
9
|
+
const SAFE_PORTS = [22, 80, 443, 3306, 5432, 6379, 27017, 5000, 8080];
|
|
10
|
+
|
|
11
|
+
const SAFE_PORT_DESCRIPTIONS = {
|
|
12
|
+
22: 'SSH',
|
|
13
|
+
80: 'HTTP',
|
|
14
|
+
443: 'HTTPS',
|
|
15
|
+
3306: 'MySQL',
|
|
16
|
+
5432: 'PostgreSQL',
|
|
17
|
+
6379: 'Redis',
|
|
18
|
+
27017: 'MongoDB',
|
|
19
|
+
5000: 'Flask/Docker Registry',
|
|
20
|
+
8080: 'Alternative HTTP'
|
|
21
|
+
};
|
|
22
|
+
|
|
8
23
|
const args = process.argv.slice(2);
|
|
9
24
|
|
|
10
25
|
// Parse arguments
|
|
11
26
|
let port = null;
|
|
12
27
|
let shouldWait = false;
|
|
28
|
+
let shouldForce = false;
|
|
13
29
|
let command = null;
|
|
14
30
|
|
|
15
31
|
for (let i = 0; i < args.length; i++) {
|
|
16
32
|
if (args[i] === '--wait') {
|
|
17
33
|
shouldWait = true;
|
|
34
|
+
} else if (args[i] === '--force') {
|
|
35
|
+
shouldForce = true;
|
|
18
36
|
} else if (args[i] === 'list') {
|
|
19
37
|
command = 'list';
|
|
20
38
|
} else if (!isNaN(args[i])) {
|
|
@@ -26,16 +44,179 @@ for (let i = 0; i < args.length; i++) {
|
|
|
26
44
|
if (command === 'list') {
|
|
27
45
|
listPorts();
|
|
28
46
|
} else if (port) {
|
|
29
|
-
// nuke <PORT> [--wait]
|
|
30
|
-
nukePort(port, shouldWait);
|
|
47
|
+
// nuke <PORT> [--wait] [--force]
|
|
48
|
+
nukePort(port, shouldWait, shouldForce);
|
|
31
49
|
} else {
|
|
32
50
|
console.error('Usage:');
|
|
33
|
-
console.error(' nuke <PORT>
|
|
34
|
-
console.error(' nuke <PORT> --wait
|
|
35
|
-
console.error(' nuke
|
|
51
|
+
console.error(' nuke <PORT> Kill process on specific port');
|
|
52
|
+
console.error(' nuke <PORT> --wait Kill process and wait until port is free');
|
|
53
|
+
console.error(' nuke <PORT> --force Force kill even on protected ports');
|
|
54
|
+
console.error(' nuke list List all active ports and select one to kill');
|
|
55
|
+
console.error('');
|
|
56
|
+
console.error('Protected ports (require --force): 22, 80, 443, 3306, 5432, 6379, 27017, 5000, 8080');
|
|
36
57
|
process.exit(1);
|
|
37
58
|
}
|
|
38
59
|
|
|
60
|
+
// Check if a port is protected and requires --force flag
|
|
61
|
+
function checkSafePort(port, force) {
|
|
62
|
+
const portNum = parseInt(port);
|
|
63
|
+
|
|
64
|
+
if (SAFE_PORTS.includes(portNum) && !force) {
|
|
65
|
+
const description = SAFE_PORT_DESCRIPTIONS[portNum] || 'System Service';
|
|
66
|
+
console.error(`\n⚠️ Port ${port} is protected (${description}).`);
|
|
67
|
+
console.error(` This port is typically used for critical services.`);
|
|
68
|
+
console.error(` Use --force to override: nuke ${port} --force\n`);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if a process is Docker-related
|
|
76
|
+
function isDockerProcess(commandName) {
|
|
77
|
+
if (!commandName) return false;
|
|
78
|
+
|
|
79
|
+
const dockerProcessNames = [
|
|
80
|
+
'docker',
|
|
81
|
+
'dockerd',
|
|
82
|
+
'com.docker.backend',
|
|
83
|
+
'docker desktop',
|
|
84
|
+
'containerd',
|
|
85
|
+
'docker.exe'
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const lowerCommand = commandName.toLowerCase();
|
|
89
|
+
return dockerProcessNames.some(name => lowerCommand.includes(name));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Find Docker container using a specific port
|
|
93
|
+
async function findDockerContainerByPort(port) {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
exec('docker ps --format "{{.ID}}|{{.Names}}|{{.Ports}}"', (error, stdout) => {
|
|
96
|
+
if (error) {
|
|
97
|
+
resolve(null);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const lines = stdout.trim().split('\n');
|
|
102
|
+
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
if (!line) continue;
|
|
105
|
+
|
|
106
|
+
const [id, name, ports] = line.split('|');
|
|
107
|
+
|
|
108
|
+
// Check if this container has the target port
|
|
109
|
+
// Formats: 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp, etc.
|
|
110
|
+
const portRegex = new RegExp(`[:\\s]${port}->|[:\\s]${port}/`);
|
|
111
|
+
|
|
112
|
+
if (ports && portRegex.test(ports)) {
|
|
113
|
+
resolve({ id, name, ports });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
resolve(null);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Handle Docker process - offer to stop container instead of killing daemon
|
|
124
|
+
async function handleDockerProcess(port, pid, processCommand) {
|
|
125
|
+
console.log(`\n🐳 Detected Docker process (PID: ${pid})`);
|
|
126
|
+
console.log(' Searching for container using this port...\n');
|
|
127
|
+
|
|
128
|
+
const container = await findDockerContainerByPort(port);
|
|
129
|
+
|
|
130
|
+
if (!container) {
|
|
131
|
+
console.log('⚠️ Could not find a specific container using this port.');
|
|
132
|
+
console.log(' The port might be used by Docker\'s internal networking.\n');
|
|
133
|
+
|
|
134
|
+
const answer = await inquirer.prompt([
|
|
135
|
+
{
|
|
136
|
+
type: 'list',
|
|
137
|
+
name: 'action',
|
|
138
|
+
message: 'What would you like to do?',
|
|
139
|
+
choices: [
|
|
140
|
+
{ name: 'Cancel (recommended)', value: 'cancel' },
|
|
141
|
+
{ name: '⚠️ Kill Docker daemon (stops ALL containers)', value: 'kill' }
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
if (answer.action === 'kill') {
|
|
147
|
+
const confirm = await inquirer.prompt([
|
|
148
|
+
{
|
|
149
|
+
type: 'confirm',
|
|
150
|
+
name: 'confirmed',
|
|
151
|
+
message: '⚠️ This will stop ALL Docker containers. Are you sure?',
|
|
152
|
+
default: false
|
|
153
|
+
}
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
if (confirm.confirmed) {
|
|
157
|
+
killPid(pid);
|
|
158
|
+
} else {
|
|
159
|
+
console.log('Cancelled.');
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
console.log('Cancelled.');
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Found a container
|
|
168
|
+
console.log(`📦 Found container using port ${port}:`);
|
|
169
|
+
console.log(` Name: ${container.name}`);
|
|
170
|
+
console.log(` ID: ${container.id}`);
|
|
171
|
+
console.log(` Ports: ${container.ports}\n`);
|
|
172
|
+
|
|
173
|
+
const answer = await inquirer.prompt([
|
|
174
|
+
{
|
|
175
|
+
type: 'list',
|
|
176
|
+
name: 'action',
|
|
177
|
+
message: 'What would you like to do?',
|
|
178
|
+
choices: [
|
|
179
|
+
{ name: `Stop container '${container.name}' (recommended)`, value: 'stop' },
|
|
180
|
+
{ name: '⚠️ Kill Docker daemon (stops ALL containers)', value: 'kill' },
|
|
181
|
+
{ name: 'Cancel', value: 'cancel' }
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
if (answer.action === 'stop') {
|
|
187
|
+
console.log(`\nStopping container '${container.name}'...`);
|
|
188
|
+
stopDockerContainer(container.id);
|
|
189
|
+
} else if (answer.action === 'kill') {
|
|
190
|
+
const confirm = await inquirer.prompt([
|
|
191
|
+
{
|
|
192
|
+
type: 'confirm',
|
|
193
|
+
name: 'confirmed',
|
|
194
|
+
message: '⚠️ This will stop ALL Docker containers. Are you sure?',
|
|
195
|
+
default: false
|
|
196
|
+
}
|
|
197
|
+
]);
|
|
198
|
+
|
|
199
|
+
if (confirm.confirmed) {
|
|
200
|
+
killPid(pid);
|
|
201
|
+
} else {
|
|
202
|
+
console.log('Cancelled.');
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
console.log('Cancelled.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Stop a Docker container
|
|
210
|
+
function stopDockerContainer(containerId) {
|
|
211
|
+
exec(`docker stop ${containerId}`, (error, stdout, stderr) => {
|
|
212
|
+
if (error) {
|
|
213
|
+
console.error(`Failed to stop container: ${error.message}`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
console.log(`✅ Successfully stopped container ${containerId}`);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
39
220
|
// List all active ports with process details
|
|
40
221
|
function listPorts() {
|
|
41
222
|
const platform = os.platform();
|
|
@@ -235,11 +416,22 @@ async function promptKillProcess(processes) {
|
|
|
235
416
|
if (answer.pid) {
|
|
236
417
|
const selectedProcess = processes.find(p => p.pid === answer.pid);
|
|
237
418
|
|
|
419
|
+
// Check if port is protected
|
|
420
|
+
const portNum = parseInt(selectedProcess.port);
|
|
421
|
+
const isProtected = SAFE_PORTS.includes(portNum);
|
|
422
|
+
|
|
423
|
+
let confirmMessage = `Kill process ${selectedProcess.command} (PID: ${answer.pid}) on port ${selectedProcess.port}?`;
|
|
424
|
+
|
|
425
|
+
if (isProtected) {
|
|
426
|
+
const description = SAFE_PORT_DESCRIPTIONS[portNum] || 'System Service';
|
|
427
|
+
confirmMessage = `⚠️ Port ${selectedProcess.port} is protected (${description}).\n Kill process ${selectedProcess.command} (PID: ${answer.pid})?`;
|
|
428
|
+
}
|
|
429
|
+
|
|
238
430
|
const confirm = await inquirer.prompt([
|
|
239
431
|
{
|
|
240
432
|
type: 'confirm',
|
|
241
433
|
name: 'confirmed',
|
|
242
|
-
message:
|
|
434
|
+
message: confirmMessage,
|
|
243
435
|
default: false
|
|
244
436
|
}
|
|
245
437
|
]);
|
|
@@ -335,7 +527,12 @@ function pollPortUntilFree(port, maxWaitSeconds = 30) {
|
|
|
335
527
|
}
|
|
336
528
|
|
|
337
529
|
// Original nuke port function
|
|
338
|
-
function nukePort(port, shouldWait = false) {
|
|
530
|
+
function nukePort(port, shouldWait = false, shouldForce = false) {
|
|
531
|
+
// Check if port is protected
|
|
532
|
+
if (!checkSafePort(port, shouldForce)) {
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
|
|
339
536
|
const platform = os.platform();
|
|
340
537
|
let findCommand;
|
|
341
538
|
|
|
@@ -402,17 +599,76 @@ function nukePort(port, shouldWait = false) {
|
|
|
402
599
|
return;
|
|
403
600
|
}
|
|
404
601
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
602
|
+
// Get process details to check for Docker
|
|
603
|
+
const pidArray = Array.from(pids);
|
|
604
|
+
|
|
605
|
+
if (platform === 'win32') {
|
|
606
|
+
// Get process details on Windows
|
|
607
|
+
const tasklistCmd = `tasklist /FI "PID eq ${pidArray.join('" /FI "PID eq ')}" /FO CSV /NH`;
|
|
608
|
+
|
|
609
|
+
exec(tasklistCmd, async (error, tasklistOutput) => {
|
|
610
|
+
let processCommand = null;
|
|
611
|
+
|
|
612
|
+
if (!error && tasklistOutput) {
|
|
613
|
+
const lines = tasklistOutput.trim().split('\n');
|
|
614
|
+
if (lines.length > 0) {
|
|
615
|
+
const matches = lines[0].match(/"([^"]+)"/g);
|
|
616
|
+
if (matches && matches.length > 0) {
|
|
617
|
+
processCommand = matches[0].replace(/"/g, '');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Check if it's a Docker process
|
|
623
|
+
if (processCommand && isDockerProcess(processCommand)) {
|
|
624
|
+
await handleDockerProcess(port, pidArray[0], processCommand);
|
|
625
|
+
} else {
|
|
626
|
+
// Normal kill
|
|
627
|
+
pidArray.forEach(pid => {
|
|
628
|
+
console.log(`Found process with PID: ${pid}. Nuking it...`);
|
|
629
|
+
killPid(pid, () => {
|
|
630
|
+
if (shouldWait) {
|
|
631
|
+
setTimeout(() => {
|
|
632
|
+
pollPortUntilFree(port);
|
|
633
|
+
}, 1000);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
});
|
|
413
637
|
}
|
|
414
638
|
});
|
|
415
|
-
}
|
|
639
|
+
} else {
|
|
640
|
+
// Unix-like: use lsof to get process command
|
|
641
|
+
exec(`lsof -i :${port} -n -P`, async (error, lsofOutput) => {
|
|
642
|
+
let processCommand = null;
|
|
643
|
+
|
|
644
|
+
if (!error && lsofOutput) {
|
|
645
|
+
const lines = lsofOutput.trim().split('\n');
|
|
646
|
+
if (lines.length > 1) {
|
|
647
|
+
const parts = lines[1].trim().split(/\s+/);
|
|
648
|
+
if (parts.length > 0) {
|
|
649
|
+
processCommand = parts[0];
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Check if it's a Docker process
|
|
655
|
+
if (processCommand && isDockerProcess(processCommand)) {
|
|
656
|
+
await handleDockerProcess(port, pidArray[0], processCommand);
|
|
657
|
+
} else {
|
|
658
|
+
// Normal kill
|
|
659
|
+
pidArray.forEach(pid => {
|
|
660
|
+
console.log(`Found process with PID: ${pid}. Nuking it...`);
|
|
661
|
+
killPid(pid, () => {
|
|
662
|
+
if (shouldWait) {
|
|
663
|
+
setTimeout(() => {
|
|
664
|
+
pollPortUntilFree(port);
|
|
665
|
+
}, 1000);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
416
672
|
});
|
|
417
673
|
}
|
|
418
674
|
|