kport 3.1.0__py3-none-any.whl → 3.1.1__py3-none-any.whl
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.
- {kport-3.1.0.dist-info → kport-3.1.1.dist-info}/METADATA +71 -4
- kport-3.1.1.dist-info/RECORD +7 -0
- {kport-3.1.0.dist-info → kport-3.1.1.dist-info}/WHEEL +1 -1
- kport-3.1.1.dist-info/licenses/LICENSE +30 -0
- kport.py +108 -6
- kport-3.1.0.dist-info/RECORD +0 -7
- kport-3.1.0.dist-info/licenses/LICENSE +0 -21
- {kport-3.1.0.dist-info → kport-3.1.1.dist-info}/entry_points.txt +0 -0
- {kport-3.1.0.dist-info → kport-3.1.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kport
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Summary: A cross-platform command-line tool to inspect and kill processes using specific ports
|
|
5
5
|
Home-page: https://github.com/farman20ali/port-killer
|
|
6
6
|
Author: Farman Ali
|
|
@@ -13,7 +13,7 @@ Classifier: Intended Audience :: Developers
|
|
|
13
13
|
Classifier: Intended Audience :: System Administrators
|
|
14
14
|
Classifier: Topic :: System :: Networking
|
|
15
15
|
Classifier: Topic :: System :: Systems Administration
|
|
16
|
-
Classifier: License :: OSI Approved ::
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.6
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.7
|
|
@@ -46,7 +46,7 @@ Dynamic: summary
|
|
|
46
46
|
|
|
47
47
|
[](https://github.com/farman20ali/port-killer)
|
|
48
48
|
[](https://www.python.org/downloads/)
|
|
49
|
-
[](LICENSE)
|
|
50
50
|
[](https://github.com/farman20ali/port-killer)
|
|
51
51
|
|
|
52
52
|
A simple, powerful command-line tool to inspect and kill processes using specific ports on Windows, Linux, and macOS.
|
|
@@ -472,6 +472,33 @@ kport -i 80
|
|
|
472
472
|
kport -l
|
|
473
473
|
```
|
|
474
474
|
|
|
475
|
+
## 📚 Documentation
|
|
476
|
+
|
|
477
|
+
- **[Installation Guide](INSTALL.md)** - Detailed installation instructions
|
|
478
|
+
- **[Quick Start](QUICKSTART.md)** - Get started quickly
|
|
479
|
+
- **[Publishing Guide](PUBLISH.md)** - How to publish kport
|
|
480
|
+
- **[Release Guide](RELEASE_GUIDE.md)** - Creating releases (manual & automated)
|
|
481
|
+
- **[Debian Release](DEB_RELEASE.md)** - Debian packaging and APT distribution
|
|
482
|
+
- **[Contributing](CONTRIBUTING.md)** - How to contribute
|
|
483
|
+
|
|
484
|
+
## 🚀 For Maintainers
|
|
485
|
+
|
|
486
|
+
### Creating a Release
|
|
487
|
+
|
|
488
|
+
Automated release (recommended):
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
python3 release.py
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
This script handles:
|
|
495
|
+
- Git tagging
|
|
496
|
+
- PyPI package building
|
|
497
|
+
- Debian package building
|
|
498
|
+
- GitHub release creation
|
|
499
|
+
|
|
500
|
+
See [RELEASE_GUIDE.md](RELEASE_GUIDE.md) for manual release steps and troubleshooting.
|
|
501
|
+
|
|
475
502
|
## 🤝 Contributing
|
|
476
503
|
|
|
477
504
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -484,7 +511,15 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
484
511
|
|
|
485
512
|
## 📝 License
|
|
486
513
|
|
|
487
|
-
This project is licensed under the
|
|
514
|
+
This project is licensed under the GNU Affero General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
|
515
|
+
|
|
516
|
+
**What this means:**
|
|
517
|
+
- ✅ Free to use, modify, and distribute
|
|
518
|
+
- ✅ Must share source code of any modifications
|
|
519
|
+
- ✅ **Network use = distribution**: If you run a modified version as a service, you must share the source code
|
|
520
|
+
- ❌ Cannot use in proprietary SaaS without sharing modifications
|
|
521
|
+
|
|
522
|
+
For commercial licensing or if AGPL doesn't fit your use case, contact: farman20ali@gmail.com
|
|
488
523
|
|
|
489
524
|
## ⚠️ Important Notes
|
|
490
525
|
|
|
@@ -504,6 +539,38 @@ sudo kport -k 80
|
|
|
504
539
|
|
|
505
540
|
On Windows, run your terminal as Administrator.
|
|
506
541
|
|
|
542
|
+
### Stubborn processes that won't die
|
|
543
|
+
|
|
544
|
+
Some processes (especially Java applications) may not respond to graceful termination. Use the `--force` flag which automatically uses a multi-tier kill strategy (SIGTERM → SIGKILL → fuser):
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
kport -k 8081 --force
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
On Linux, `kport` will automatically use `fuser -k` as a fallback when standard kill methods fail. This is extremely effective for stubborn Java/Node/Python processes:
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
# Install fuser for best results (Ubuntu/Debian)
|
|
554
|
+
sudo apt-get install psmisc
|
|
555
|
+
|
|
556
|
+
# Install fuser (RHEL/CentOS/Fedora)
|
|
557
|
+
sudo yum install psmisc
|
|
558
|
+
|
|
559
|
+
# Then kport will automatically use it when needed
|
|
560
|
+
kport -k 8081 --force
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
**What happens:**
|
|
564
|
+
1. First tries SIGTERM (graceful shutdown)
|
|
565
|
+
2. Then tries SIGKILL after timeout
|
|
566
|
+
3. Finally uses `fuser -k 8081/tcp` if process still lives (Linux only)
|
|
567
|
+
|
|
568
|
+
**Manual alternative:** You can also kill a port directly with fuser:
|
|
569
|
+
```bash
|
|
570
|
+
# Kill all processes using port 8081 (requires sudo)
|
|
571
|
+
sudo fuser -k 8081/tcp
|
|
572
|
+
```
|
|
573
|
+
|
|
507
574
|
### Port not found
|
|
508
575
|
|
|
509
576
|
Make sure the port number is correct and that a process is actually using it. Use `kport -l` to see all active ports.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
kport.py,sha256=eSHSppNOn2Yfd0ytBbwE2yHu51b7nS82ROGIWZL0ozA,88268
|
|
2
|
+
kport-3.1.1.dist-info/licenses/LICENSE,sha256=PCHih_8Dwem_jeC81AMDn1Vjj8BqSbIgDYouqXsNsN8,1314
|
|
3
|
+
kport-3.1.1.dist-info/METADATA,sha256=deC0pH0jnPGSEpW1dfP3SeG74MNQYmpjPpvx8zWAPAY,16847
|
|
4
|
+
kport-3.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
5
|
+
kport-3.1.1.dist-info/entry_points.txt,sha256=ppZIgJ1vrmYs2EO9NcVSFCaZj7oj7t6RnJo8ArPQiRo,37
|
|
6
|
+
kport-3.1.1.dist-info/top_level.txt,sha256=yzlMBPZ7a-pcCN4DR5MRPO-VjY3fWnILtFUQpEuA2xY,6
|
|
7
|
+
kport-3.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
GNU AFFERO GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 19 November 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2025 kport contributors
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU Affero General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU Affero General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
|
|
19
|
+
--------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
NOTE: This is the AGPL-3.0 license, which is similar to GPL-3.0 but with
|
|
22
|
+
an additional requirement: If you run a modified version of this program
|
|
23
|
+
on a server and let users interact with it remotely through a network,
|
|
24
|
+
you must make the complete source code of your modified version available
|
|
25
|
+
to those users.
|
|
26
|
+
|
|
27
|
+
This protects against the "SaaS loophole" where companies could use the
|
|
28
|
+
software as a service without sharing their modifications.
|
|
29
|
+
|
|
30
|
+
Full license text: https://www.gnu.org/licenses/agpl-3.0.txt
|
kport.py
CHANGED
|
@@ -381,6 +381,32 @@ class BaseInspector:
|
|
|
381
381
|
"""
|
|
382
382
|
raise NotImplementedError()
|
|
383
383
|
|
|
384
|
+
def kill_port(self, port: int, graceful_timeout: float = 3.0, force: bool = False, dry_run: bool = False) -> Tuple[bool, str]:
|
|
385
|
+
"""
|
|
386
|
+
Kill all processes using a specific port.
|
|
387
|
+
Default implementation finds PIDs and kills them one by one.
|
|
388
|
+
Can be overridden for more efficient port-based killing (e.g., fuser on Linux).
|
|
389
|
+
"""
|
|
390
|
+
pids = self.find_pids_on_port(port)
|
|
391
|
+
if not pids:
|
|
392
|
+
return False, "No process found on port"
|
|
393
|
+
|
|
394
|
+
killed_count = 0
|
|
395
|
+
errors = []
|
|
396
|
+
for pid in pids:
|
|
397
|
+
ok, msg = self.kill_pid(pid, graceful_timeout, force, dry_run)
|
|
398
|
+
if ok:
|
|
399
|
+
killed_count += 1
|
|
400
|
+
else:
|
|
401
|
+
errors.append(f"PID {pid}: {msg}")
|
|
402
|
+
|
|
403
|
+
if killed_count == len(pids):
|
|
404
|
+
return True, f"Killed {killed_count} process(es)"
|
|
405
|
+
elif killed_count > 0:
|
|
406
|
+
return False, f"Killed {killed_count}/{len(pids)} process(es). Errors: {'; '.join(errors)}"
|
|
407
|
+
else:
|
|
408
|
+
return False, f"Failed to kill any process. Errors: {'; '.join(errors)}"
|
|
409
|
+
|
|
384
410
|
# psutil-based inspector (best behavior cross-platform)
|
|
385
411
|
class PsutilInspector(BaseInspector):
|
|
386
412
|
def list_listening(self) -> List[PortBinding]:
|
|
@@ -551,6 +577,38 @@ class FallbackInspector(BaseInspector):
|
|
|
551
577
|
except Exception:
|
|
552
578
|
return None
|
|
553
579
|
|
|
580
|
+
def _kill_port_with_fuser(self, port: int, proto: str = "tcp", dry_run: bool = False) -> Tuple[bool, str]:
|
|
581
|
+
"""Kill all processes using a port with fuser (Linux utility).
|
|
582
|
+
|
|
583
|
+
This is often more reliable than kill -9 for stubborn processes.
|
|
584
|
+
"""
|
|
585
|
+
if dry_run:
|
|
586
|
+
return True, f"Dry-run: would fuser -k {port}/{proto}"
|
|
587
|
+
|
|
588
|
+
if not check_dependency("fuser"):
|
|
589
|
+
return False, "fuser not available"
|
|
590
|
+
|
|
591
|
+
try:
|
|
592
|
+
# fuser -k sends SIGKILL by default to all processes using the port
|
|
593
|
+
proc = subprocess.run(
|
|
594
|
+
["fuser", "-k", f"{port}/{proto}"],
|
|
595
|
+
capture_output=True,
|
|
596
|
+
text=True,
|
|
597
|
+
timeout=5
|
|
598
|
+
)
|
|
599
|
+
# fuser returns 0 on success, 1 if no process found
|
|
600
|
+
if proc.returncode == 0:
|
|
601
|
+
return True, f"Killed process(es) using port {port} with fuser"
|
|
602
|
+
elif proc.returncode == 1:
|
|
603
|
+
return False, "No process found on port"
|
|
604
|
+
else:
|
|
605
|
+
stderr = (proc.stderr or "").strip()
|
|
606
|
+
return False, f"fuser failed: {stderr}"
|
|
607
|
+
except subprocess.TimeoutExpired:
|
|
608
|
+
return False, "fuser command timed out"
|
|
609
|
+
except Exception as e:
|
|
610
|
+
return False, f"Error running fuser: {e}"
|
|
611
|
+
|
|
554
612
|
def list_listening(self) -> List[PortBinding]:
|
|
555
613
|
if self.system == "Windows":
|
|
556
614
|
return self._windows_listening()
|
|
@@ -907,7 +965,7 @@ class FallbackInspector(BaseInspector):
|
|
|
907
965
|
return False, f"Error taskkill: {e}"
|
|
908
966
|
return False, "Still running; taskkill gentle failed"
|
|
909
967
|
else:
|
|
910
|
-
# Unix: try SIGTERM then SIGKILL
|
|
968
|
+
# Unix: try SIGTERM then SIGKILL, with multiple attempts
|
|
911
969
|
try:
|
|
912
970
|
os.kill(pid, signal.SIGTERM)
|
|
913
971
|
except PermissionError:
|
|
@@ -932,19 +990,46 @@ class FallbackInspector(BaseInspector):
|
|
|
932
990
|
return False, "Permission denied"
|
|
933
991
|
if not force:
|
|
934
992
|
return False, "Still alive after graceful timeout"
|
|
935
|
-
# force kill
|
|
993
|
+
# force kill with SIGKILL
|
|
936
994
|
try:
|
|
937
995
|
os.kill(pid, signal.SIGKILL)
|
|
938
|
-
|
|
996
|
+
# Wait briefly to confirm kill
|
|
997
|
+
time.sleep(0.5)
|
|
998
|
+
try:
|
|
999
|
+
os.kill(pid, 0)
|
|
1000
|
+
# Process still exists, might need more aggressive approach
|
|
1001
|
+
except ProcessLookupError:
|
|
1002
|
+
return True, "Killed (SIGKILL)"
|
|
1003
|
+
except PermissionError:
|
|
1004
|
+
return False, "Permission denied"
|
|
939
1005
|
except PermissionError:
|
|
940
1006
|
return False, "Permission denied on SIGKILL"
|
|
941
1007
|
except ProcessLookupError:
|
|
942
|
-
return True, "Process disappeared
|
|
1008
|
+
return True, "Process disappeared"
|
|
943
1009
|
except Exception as e:
|
|
944
1010
|
return False, f"Error SIGKILL: {e}"
|
|
1011
|
+
|
|
1012
|
+
# If still running, return status indicating process is stubborn
|
|
1013
|
+
return False, "Process did not terminate after SIGKILL"
|
|
945
1014
|
except Exception as e:
|
|
946
1015
|
return False, f"Unexpected error: {e}"
|
|
947
1016
|
|
|
1017
|
+
def kill_port(self, port: int, graceful_timeout: float = 3.0, force: bool = False, dry_run: bool = False) -> Tuple[bool, str]:
|
|
1018
|
+
"""
|
|
1019
|
+
Kill all processes using a specific port.
|
|
1020
|
+
On Linux, tries fuser first as it's more reliable, then falls back to default implementation.
|
|
1021
|
+
"""
|
|
1022
|
+
if self.system != "Windows":
|
|
1023
|
+
# Try fuser first on Unix-like systems when force=True or as fallback
|
|
1024
|
+
if force and check_dependency("fuser"):
|
|
1025
|
+
ok, msg = self._kill_port_with_fuser(port, "tcp", dry_run)
|
|
1026
|
+
if ok:
|
|
1027
|
+
return ok, msg
|
|
1028
|
+
# If fuser fails, fall through to default implementation
|
|
1029
|
+
|
|
1030
|
+
# Use default implementation: find PIDs and kill them
|
|
1031
|
+
return super().kill_port(port, graceful_timeout, force, dry_run)
|
|
1032
|
+
|
|
948
1033
|
# Factory
|
|
949
1034
|
def get_inspector() -> BaseInspector:
|
|
950
1035
|
if USING_PSUTIL:
|
|
@@ -1291,6 +1376,23 @@ def handle_product_command(args: argparse.Namespace, inspector: BaseInspector) -
|
|
|
1291
1376
|
out_killed.append({"pid": pid, "msg": msg})
|
|
1292
1377
|
else:
|
|
1293
1378
|
out_failed.append({"pid": pid, "msg": msg})
|
|
1379
|
+
|
|
1380
|
+
# If any processes failed to kill and fuser is available on Linux, try as last resort
|
|
1381
|
+
if out_failed and isinstance(inspector, FallbackInspector) and inspector.system != "Windows" and check_dependency("fuser"):
|
|
1382
|
+
if not args.json:
|
|
1383
|
+
print(colorize(f"\n⚠ Some processes couldn't be killed. Trying fuser as fallback...", Colors.YELLOW))
|
|
1384
|
+
fuser_ok, fuser_msg = inspector._kill_port_with_fuser(args.port, "tcp", dry_run=args.dry_run)
|
|
1385
|
+
if fuser_ok:
|
|
1386
|
+
# Re-check if processes are gone
|
|
1387
|
+
remaining_pids = inspector.find_pids_on_port(args.port)
|
|
1388
|
+
if not remaining_pids:
|
|
1389
|
+
# Success! Mark all failed as killed
|
|
1390
|
+
for f in out_failed:
|
|
1391
|
+
out_killed.append({"pid": f["pid"], "msg": f"{f['msg']} → killed with fuser"})
|
|
1392
|
+
out_failed = []
|
|
1393
|
+
if not args.json:
|
|
1394
|
+
print(colorize(f"✓ {fuser_msg}", Colors.GREEN))
|
|
1395
|
+
|
|
1294
1396
|
if args.json:
|
|
1295
1397
|
print(json.dumps({"port": args.port, "killed": out_killed, "failed": out_failed}, indent=2))
|
|
1296
1398
|
else:
|
|
@@ -1400,9 +1502,9 @@ Examples:
|
|
|
1400
1502
|
parser.add_argument("-kr", "--kill-range", type=str, metavar="RANGE", help="Kill processes on port range (e.g., 3000-3010)")
|
|
1401
1503
|
parser.add_argument("-l", "--list", action="store_true", help="List all listening ports and their processes")
|
|
1402
1504
|
parser.add_argument("--exact", action="store_true", help="Use exact match for process name lookups")
|
|
1403
|
-
parser.add_argument("--force", action="store_true", help="Force kill immediately if needed (
|
|
1505
|
+
parser.add_argument("--force", action="store_true", help="Force kill immediately if needed (uses SIGKILL/fuser on Linux for stubborn processes)")
|
|
1404
1506
|
parser.add_argument("--graceful-timeout", type=float, default=3.0, help="Seconds to wait for graceful termination before forcing (default 3.0)")
|
|
1405
|
-
parser.add_argument("-v", "--version", action="version", version="kport 3.1.
|
|
1507
|
+
parser.add_argument("-v", "--version", action="version", version="kport 3.1.1")
|
|
1406
1508
|
|
|
1407
1509
|
# PRODUCT.md subcommands
|
|
1408
1510
|
sub = parser.add_subparsers(dest="command")
|
kport-3.1.0.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
kport.py,sha256=0swOnRrBMCpTm9Ivr8dDEjN9iJw9VLYX8ce_1LrDIFE,83354
|
|
2
|
-
kport-3.1.0.dist-info/licenses/LICENSE,sha256=jIMuFOo3loyUX7Yc92eFXDJC9Wo1ASX5j3qdIDs4ceo,1062
|
|
3
|
-
kport-3.1.0.dist-info/METADATA,sha256=1qLqNT6HH4-v-25XXQpLNQQECvAFbWjiVJxycDFgzp4,14644
|
|
4
|
-
kport-3.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
kport-3.1.0.dist-info/entry_points.txt,sha256=ppZIgJ1vrmYs2EO9NcVSFCaZj7oj7t6RnJo8ArPQiRo,37
|
|
6
|
-
kport-3.1.0.dist-info/top_level.txt,sha256=yzlMBPZ7a-pcCN4DR5MRPO-VjY3fWnILtFUQpEuA2xY,6
|
|
7
|
-
kport-3.1.0.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 kport
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
File without changes
|
|
File without changes
|