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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kport
3
- Version: 3.1.0
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 :: MIT License
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
  [![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](https://github.com/farman20ali/port-killer)
48
48
  [![Python](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/)
49
- [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
49
+ [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
50
50
  [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)](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 MIT License - see the LICENSE file for details.
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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
- return True, "Killed (SIGKILL)"
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 after SIGKILL"
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 (after graceful timeout)")
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.0")
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")
@@ -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.