k-vpn 1.0.0__tar.gz
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.
- k_vpn-1.0.0/PKG-INFO +66 -0
- k_vpn-1.0.0/README.md +54 -0
- k_vpn-1.0.0/pyproject.toml +24 -0
- k_vpn-1.0.0/src/k_vpn/__init__.py +0 -0
- k_vpn-1.0.0/src/k_vpn/main.py +221 -0
k_vpn-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: k-vpn
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Automated CLI tool to fetch and connect to global VPN Gate servers securely.
|
|
5
|
+
Author: KpihX
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Dist: typer>=0.9.0
|
|
8
|
+
Requires-Dist: rich>=13.0.0
|
|
9
|
+
Requires-Dist: requests>=2.0.0
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# k-vpn
|
|
14
|
+
|
|
15
|
+
**k-vpn** is a modern, automated CLI tool designed to easily fetch and connect to public VPN Gate servers directly from your terminal. Built with Python, Typer, and Rich, it offers a beautiful and interactive experience for securing your connection.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Automated Fetching**: Retrieves the latest list of public VPN servers from VPN Gate.
|
|
20
|
+
- **Rich UI**: Displays server availability by country in a formatted table.
|
|
21
|
+
- **Interactive Selection**: Easily choose your desired country.
|
|
22
|
+
- **Secure Connection**: Automates the OpenVPN connection process using temporary configuration files.
|
|
23
|
+
- **Smart Defaults**: Filters for high-speed, uptime-reliable servers.
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
- **Python 3.8+**
|
|
28
|
+
- **OpenVPN**: Must be installed and accessible via `sudo openvpn` on your system.
|
|
29
|
+
- **uv**: Recommended for project management and execution.
|
|
30
|
+
|
|
31
|
+
## Installation & Usage
|
|
32
|
+
|
|
33
|
+
It is recommended to use `uv` to run this tool directly without manual environment setup.
|
|
34
|
+
|
|
35
|
+
1. **Clone the repository:**
|
|
36
|
+
```bash
|
|
37
|
+
git clone https://github.com/KpihX/k-vpn.git
|
|
38
|
+
cd k-vpn
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
2. **Run the tool:**
|
|
42
|
+
```bash
|
|
43
|
+
uv run k-vpn.py
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The tool will verify your environment, fetch the server list, and present you with options.
|
|
47
|
+
|
|
48
|
+
## How it Works
|
|
49
|
+
|
|
50
|
+
1. **Fetch**: The tool downloads the CSV list from `http://www.vpngate.net/api/iphone/`.
|
|
51
|
+
2. **Parse & Filter**: It parses the data, filtering for valid OpenVPN configurations.
|
|
52
|
+
3. **Display**: Aggregates servers by country and displays a summary table (Country, Server Count).
|
|
53
|
+
4. **Connect**:
|
|
54
|
+
* You select a country by its index.
|
|
55
|
+
* The tool picks the best server (based on score/speed) for that country.
|
|
56
|
+
* It decodes the base64 OpenVPN configuration.
|
|
57
|
+
* It creates a temporary `.ovpn` file.
|
|
58
|
+
* It executes `sudo openvpn --config <temp_file>` to establish the tunnel.
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
63
|
+
|
|
64
|
+
## Disclaimer
|
|
65
|
+
|
|
66
|
+
This tool uses public servers from VPN Gate. The availability, speed, and security of these servers are not guaranteed by the author. Use at your own risk.
|
k_vpn-1.0.0/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# k-vpn
|
|
2
|
+
|
|
3
|
+
**k-vpn** is a modern, automated CLI tool designed to easily fetch and connect to public VPN Gate servers directly from your terminal. Built with Python, Typer, and Rich, it offers a beautiful and interactive experience for securing your connection.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automated Fetching**: Retrieves the latest list of public VPN servers from VPN Gate.
|
|
8
|
+
- **Rich UI**: Displays server availability by country in a formatted table.
|
|
9
|
+
- **Interactive Selection**: Easily choose your desired country.
|
|
10
|
+
- **Secure Connection**: Automates the OpenVPN connection process using temporary configuration files.
|
|
11
|
+
- **Smart Defaults**: Filters for high-speed, uptime-reliable servers.
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- **Python 3.8+**
|
|
16
|
+
- **OpenVPN**: Must be installed and accessible via `sudo openvpn` on your system.
|
|
17
|
+
- **uv**: Recommended for project management and execution.
|
|
18
|
+
|
|
19
|
+
## Installation & Usage
|
|
20
|
+
|
|
21
|
+
It is recommended to use `uv` to run this tool directly without manual environment setup.
|
|
22
|
+
|
|
23
|
+
1. **Clone the repository:**
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/KpihX/k-vpn.git
|
|
26
|
+
cd k-vpn
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. **Run the tool:**
|
|
30
|
+
```bash
|
|
31
|
+
uv run k-vpn.py
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The tool will verify your environment, fetch the server list, and present you with options.
|
|
35
|
+
|
|
36
|
+
## How it Works
|
|
37
|
+
|
|
38
|
+
1. **Fetch**: The tool downloads the CSV list from `http://www.vpngate.net/api/iphone/`.
|
|
39
|
+
2. **Parse & Filter**: It parses the data, filtering for valid OpenVPN configurations.
|
|
40
|
+
3. **Display**: Aggregates servers by country and displays a summary table (Country, Server Count).
|
|
41
|
+
4. **Connect**:
|
|
42
|
+
* You select a country by its index.
|
|
43
|
+
* The tool picks the best server (based on score/speed) for that country.
|
|
44
|
+
* It decodes the base64 OpenVPN configuration.
|
|
45
|
+
* It creates a temporary `.ovpn` file.
|
|
46
|
+
* It executes `sudo openvpn --config <temp_file>` to establish the tunnel.
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
51
|
+
|
|
52
|
+
## Disclaimer
|
|
53
|
+
|
|
54
|
+
This tool uses public servers from VPN Gate. The availability, speed, and security of these servers are not guaranteed by the author. Use at your own risk.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "k-vpn"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Automated CLI tool to fetch and connect to global VPN Gate servers securely."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "KpihX" }
|
|
8
|
+
]
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"typer>=0.9.0",
|
|
13
|
+
"rich>=13.0.0",
|
|
14
|
+
"requests>=2.0.0"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
k-vpn = "k_vpn.main:app"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = [
|
|
22
|
+
"uv_build>=0.9.21,<0.10.0",
|
|
23
|
+
]
|
|
24
|
+
build-backend = "uv_build"
|
|
File without changes
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import csv
|
|
4
|
+
import base64
|
|
5
|
+
import tempfile
|
|
6
|
+
import subprocess
|
|
7
|
+
import requests
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.prompt import Prompt
|
|
14
|
+
from rich import print as rprint
|
|
15
|
+
from typing import List, Dict, Any
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(help="k-vpn: Automated VPN Gate Connector")
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
VPN_GATE_API_URL = "http://www.vpngate.net/api/iphone/"
|
|
21
|
+
|
|
22
|
+
def fetch_vpn_servers() -> List[Dict[str, Any]]:
|
|
23
|
+
"""Fetches and parses VPN servers from VPN Gate API."""
|
|
24
|
+
try:
|
|
25
|
+
with Progress(
|
|
26
|
+
SpinnerColumn(),
|
|
27
|
+
TextColumn("[bold green]Fetching VPN server list..."),
|
|
28
|
+
transient=True,
|
|
29
|
+
) as progress:
|
|
30
|
+
progress.add_task("fetch", total=None)
|
|
31
|
+
response = requests.get(VPN_GATE_API_URL, timeout=30)
|
|
32
|
+
response.raise_for_status()
|
|
33
|
+
|
|
34
|
+
# The API returns a CSV file with a header and '*' lines at the end
|
|
35
|
+
content = response.text
|
|
36
|
+
lines = content.splitlines()
|
|
37
|
+
|
|
38
|
+
# Remove comments/garbage lines (lines starting with '*' or '#')
|
|
39
|
+
# Keep the header line which starts with 'HostName' usually, but in the raw response
|
|
40
|
+
# the second line is usually the header. Let's inspect typical response.
|
|
41
|
+
# Typical response starts with "*vpn_servers\n#HostName,..."
|
|
42
|
+
|
|
43
|
+
csv_lines = [line for line in lines if not line.startswith('*')]
|
|
44
|
+
|
|
45
|
+
if not csv_lines:
|
|
46
|
+
console.print("[bold red]Error:[/bold red] Empty response from VPN Gate.")
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
|
|
49
|
+
# The header usually starts with #HostName, strip the #
|
|
50
|
+
if csv_lines[0].startswith('#'):
|
|
51
|
+
csv_lines[0] = csv_lines[0][1:]
|
|
52
|
+
|
|
53
|
+
reader = csv.DictReader(csv_lines)
|
|
54
|
+
servers = []
|
|
55
|
+
for row in reader:
|
|
56
|
+
if row.get('OpenVPN_ConfigData_Base64'):
|
|
57
|
+
servers.append(row)
|
|
58
|
+
|
|
59
|
+
return servers
|
|
60
|
+
|
|
61
|
+
except requests.RequestException as e:
|
|
62
|
+
console.print(f"[bold red]Network Error:[/bold red] {e}")
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
console.print(f"[bold red]Error parsing data:[/bold red] {e}")
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
def group_by_country(servers: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
|
|
69
|
+
"""Groups servers by country."""
|
|
70
|
+
grouped = {}
|
|
71
|
+
for server in servers:
|
|
72
|
+
country = server.get('CountryLong', 'Unknown')
|
|
73
|
+
if country not in grouped:
|
|
74
|
+
grouped[country] = []
|
|
75
|
+
grouped[country].append(server)
|
|
76
|
+
return grouped
|
|
77
|
+
|
|
78
|
+
@app.command()
|
|
79
|
+
def main():
|
|
80
|
+
"""
|
|
81
|
+
Fetch VPN Gate servers and connect to a selected country.
|
|
82
|
+
"""
|
|
83
|
+
console.print(Panel.fit("[bold cyan]k-vpn[/bold cyan] - Secure World Connector", border_style="cyan"))
|
|
84
|
+
|
|
85
|
+
servers = fetch_vpn_servers()
|
|
86
|
+
|
|
87
|
+
if not servers:
|
|
88
|
+
console.print("[yellow]No servers found.[/yellow]")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
grouped_servers = group_by_country(servers)
|
|
92
|
+
sorted_countries = sorted(grouped_servers.keys())
|
|
93
|
+
|
|
94
|
+
table = Table(title="Available Countries", show_header=True, header_style="bold magenta")
|
|
95
|
+
table.add_column("#", style="dim", width=4, justify="right")
|
|
96
|
+
table.add_column("Country", style="cyan")
|
|
97
|
+
table.add_column("Servers", justify="right")
|
|
98
|
+
table.add_column("Top Speed", justify="right")
|
|
99
|
+
|
|
100
|
+
for idx, country in enumerate(sorted_countries, 1):
|
|
101
|
+
country_servers = grouped_servers[country]
|
|
102
|
+
count = len(country_servers)
|
|
103
|
+
# Find max speed (Speed is in bps, convert to Mbps)
|
|
104
|
+
max_speed = max([int(s.get('Speed', 0)) for s in country_servers]) / 1000 / 1000
|
|
105
|
+
table.add_row(str(idx), country, str(count), f"{max_speed:.2f} Mbps")
|
|
106
|
+
|
|
107
|
+
console.print(table)
|
|
108
|
+
|
|
109
|
+
choice = Prompt.ask(
|
|
110
|
+
"\nSelect a country number (or 'q' to quit)",
|
|
111
|
+
default="q"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if choice.lower() == 'q':
|
|
115
|
+
console.print("[bold red]Exiting...[/bold red]")
|
|
116
|
+
raise typer.Exit()
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
index = int(choice) - 1
|
|
120
|
+
if 0 <= index < len(sorted_countries):
|
|
121
|
+
selected_country = sorted_countries[index]
|
|
122
|
+
connect_to_country(selected_country, grouped_servers[selected_country])
|
|
123
|
+
else:
|
|
124
|
+
console.print("[bold red]Invalid selection.[/bold red]")
|
|
125
|
+
except ValueError:
|
|
126
|
+
console.print("[bold red]Invalid input.[/bold red]")
|
|
127
|
+
|
|
128
|
+
def connect_to_country(country_name: str, servers: List[Dict[str, Any]]):
|
|
129
|
+
"""Selects the best server for the country and connects."""
|
|
130
|
+
|
|
131
|
+
# Sort by Score (descending) as a proxy for quality
|
|
132
|
+
# VPN Gate score formula considers uptime, speed, etc.
|
|
133
|
+
best_server = sorted(servers, key=lambda s: int(s.get('Score', 0)), reverse=True)[0]
|
|
134
|
+
|
|
135
|
+
ip = best_server.get('IP', 'Unknown')
|
|
136
|
+
speed = int(best_server.get('Speed', 0)) / 1000 / 1000
|
|
137
|
+
|
|
138
|
+
console.print(f"\n[bold green]Connecting to {country_name}...[/bold green]")
|
|
139
|
+
console.print(f"Server IP: [cyan]{ip}[/cyan] | Speed: [yellow]{speed:.2f} Mbps[/yellow]")
|
|
140
|
+
|
|
141
|
+
ovpn_data = base64.b64decode(best_server['OpenVPN_ConfigData_Base64'])
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
# Create a directory for configs in user home to avoid /tmp permission issues with sudo/snap
|
|
145
|
+
config_dir = os.path.expanduser("~/.k-vpn/configs")
|
|
146
|
+
os.makedirs(config_dir, exist_ok=True)
|
|
147
|
+
|
|
148
|
+
# Create credentials file (VPN Gate standard: vpn/vpn)
|
|
149
|
+
creds_path = os.path.join(config_dir, "vpn_creds.txt")
|
|
150
|
+
with open(creds_path, "w") as f:
|
|
151
|
+
f.write("vpn\nvpn\n")
|
|
152
|
+
os.chmod(creds_path, 0o600) # Only readable by current user (and root via sudo)
|
|
153
|
+
|
|
154
|
+
# Create a temporary file for the config
|
|
155
|
+
with tempfile.NamedTemporaryFile(mode='wb', dir=config_dir, suffix='.ovpn', delete=False) as temp_config:
|
|
156
|
+
temp_config.write(ovpn_data)
|
|
157
|
+
config_path = temp_config.name
|
|
158
|
+
|
|
159
|
+
# Ensure the file is readable only by the user
|
|
160
|
+
os.chmod(config_path, 0o600)
|
|
161
|
+
|
|
162
|
+
console.print(f"[dim]Config saved to {config_path}[/dim]")
|
|
163
|
+
console.print("[bold yellow]Launching OpenVPN (sudo required)...[/bold yellow]")
|
|
164
|
+
|
|
165
|
+
# Run OpenVPN
|
|
166
|
+
# --data-ciphers-fallback and --data-ciphers with AES-128-CBC
|
|
167
|
+
# handles older servers on OpenVPN 2.6+
|
|
168
|
+
cmd = [
|
|
169
|
+
"sudo", "openvpn",
|
|
170
|
+
"--config", config_path,
|
|
171
|
+
"--auth-user-pass", creds_path,
|
|
172
|
+
"--data-ciphers", "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC",
|
|
173
|
+
"--data-ciphers-fallback", "AES-128-CBC"
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
# Use Popen to monitor output in real-time
|
|
177
|
+
process = subprocess.Popen(
|
|
178
|
+
cmd,
|
|
179
|
+
stdout=subprocess.PIPE,
|
|
180
|
+
stderr=subprocess.STDOUT,
|
|
181
|
+
text=True,
|
|
182
|
+
bufsize=1
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
connected = False
|
|
186
|
+
for line in iter(process.stdout.readline, ""):
|
|
187
|
+
console.print(line.strip())
|
|
188
|
+
|
|
189
|
+
if "Initialization Sequence Completed" in line and not connected:
|
|
190
|
+
connected = True
|
|
191
|
+
console.print("\n")
|
|
192
|
+
console.print(Panel(
|
|
193
|
+
f"[bold green]✓ SUCCESSFULLY CONNECTED[/bold green]\n"
|
|
194
|
+
f"[bold white]Country:[/bold white] {country_name}\n"
|
|
195
|
+
f"[bold white]Server IP:[/bold white] [cyan]{ip}[/cyan]\n"
|
|
196
|
+
f"[bold white]Speed:[/bold white] [yellow]{speed:.2f} Mbps[/yellow]",
|
|
197
|
+
title="[bold green]VPN Active[/bold green]",
|
|
198
|
+
border_style="green",
|
|
199
|
+
expand=False
|
|
200
|
+
))
|
|
201
|
+
console.print("[dim]Press CTRL+C to disconnect...[/dim]\n")
|
|
202
|
+
|
|
203
|
+
process.wait()
|
|
204
|
+
|
|
205
|
+
except KeyboardInterrupt:
|
|
206
|
+
console.print("\n[bold red]Disconnecting and cleaning up...[/bold red]")
|
|
207
|
+
if 'process' in locals():
|
|
208
|
+
# Send SIGTERM to the sudo process, which should relay it to OpenVPN
|
|
209
|
+
process.terminate()
|
|
210
|
+
except Exception as e:
|
|
211
|
+
console.print(f"[bold red]Connection failed:[/bold red] {e}")
|
|
212
|
+
finally:
|
|
213
|
+
# Cleanup
|
|
214
|
+
if 'config_path' in locals() and os.path.exists(config_path):
|
|
215
|
+
os.remove(config_path)
|
|
216
|
+
if 'creds_path' in locals() and os.path.exists(creds_path):
|
|
217
|
+
os.remove(creds_path)
|
|
218
|
+
console.print("[dim]Temporary files cleaned up.[/dim]")
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
app()
|