nord-config-generator 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.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: nord-config-generator
3
+ Version: 1.0.0
4
+ Summary: A command-line tool for generating optimized NordVPN WireGuard configurations.
5
+ Author-email: Ahmed Touhami <mustafachyi272@gmail.com>
6
+ Project-URL: Homepage, https://github.com/mustafachyi/NordVPN-WireGuard-Config-Generator
7
+ Project-URL: Bug Tracker, https://github.com/mustafachyi/NordVPN-WireGuard-Config-Generator/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: System :: Networking
12
+ Classifier: Environment :: Console
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: aiohttp>=3.8.0
16
+ Requires-Dist: aiofiles>=0.8.0
17
+ Requires-Dist: rich>=12.0.0
18
+
19
+ # NordVPN WireGuard Configuration Generator
20
+
21
+ A command-line tool for generating optimized NordVPN WireGuard configurations.
22
+
23
+ ## Project Philosophy: A Focus on Quality
24
+
25
+ This project has been fundamentally refocused. Previously, multiple versions existed across several programming languages. This approach divided development effort and resulted in inconsistent quality.
26
+
27
+ The new directive is singular: to provide one exceptionally engineered tool that is robust, maintainable, and correct.
28
+
29
+ To this end, all previous language implementations have been archived. Development is now concentrated on two platforms:
30
+
31
+ 1. **This Command-Line Tool:** A complete rewrite in Python, packaged for professional use.
32
+ 2. **A Web Interface:** For users who require a graphical frontend.
33
+
34
+ This consolidated effort ensures a higher standard of quality and a more reliable end-product.
35
+
36
+ ## Core Capabilities
37
+
38
+ * **Package Distribution:** The tool is a proper command-line application, installable via PyPI. This eliminates manual dependency management.
39
+ * **Performance:** Asynchronous architecture processes the entire NordVPN server list in seconds.
40
+ * **Optimization:** Intelligently sorts servers by current load and geographic proximity to the user, generating configurations for the most performant connections.
41
+ * **Structured Output:** Automatically creates a clean directory structure containing standard configurations, a `best_configs` folder for optimal servers per location, and a `servers.json` file with detailed metadata for analysis.
42
+ * **Interactive and Non-Interactive:** A guided TUI for interactive use. The core logic is structured to be scriptable.
43
+
44
+ ## Installation
45
+
46
+ Prerequisites: Python 3.8+
47
+
48
+ Install the package using `pip`:
49
+
50
+ ```bash
51
+ pip install nord-config-generator
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ Execute the installed application:
57
+
58
+ ```bash
59
+ nord-generator
60
+ ```
61
+
62
+ The application will prompt for the required access token and configuration preferences.
63
+
64
+ ## Web Version
65
+
66
+ A graphical alternative is available for direct use in a web browser.
67
+
68
+ * **Current Version:** [https://nord-configs.selfhoster.nl/](https://nord-configs.selfhoster.nl/)
69
+ * **Legacy Version:** [https://wg-nord.pages.dev/](https://wg-nord.pages.dev/)
70
+
71
+ ## Support
72
+
73
+ Project visibility and continued development are supported by two actions:
74
+
75
+ 1. **Star the Repository:** Starring the project on GitHub increases its visibility.
76
+ 2. **NordVPN Referral:** Using the referral link for new subscriptions provides support at no additional cost. Link: [https://ref.nordvpn.com/MXIVDoJGpKT](https://ref.nordvpn.com/MXIVDoJGpKT)
77
+
78
+ ## License
79
+
80
+ This project is distributed under the GNU General Public License v3.0. See the `LICENSE` file for full details.
@@ -0,0 +1,62 @@
1
+ # NordVPN WireGuard Configuration Generator
2
+
3
+ A command-line tool for generating optimized NordVPN WireGuard configurations.
4
+
5
+ ## Project Philosophy: A Focus on Quality
6
+
7
+ This project has been fundamentally refocused. Previously, multiple versions existed across several programming languages. This approach divided development effort and resulted in inconsistent quality.
8
+
9
+ The new directive is singular: to provide one exceptionally engineered tool that is robust, maintainable, and correct.
10
+
11
+ To this end, all previous language implementations have been archived. Development is now concentrated on two platforms:
12
+
13
+ 1. **This Command-Line Tool:** A complete rewrite in Python, packaged for professional use.
14
+ 2. **A Web Interface:** For users who require a graphical frontend.
15
+
16
+ This consolidated effort ensures a higher standard of quality and a more reliable end-product.
17
+
18
+ ## Core Capabilities
19
+
20
+ * **Package Distribution:** The tool is a proper command-line application, installable via PyPI. This eliminates manual dependency management.
21
+ * **Performance:** Asynchronous architecture processes the entire NordVPN server list in seconds.
22
+ * **Optimization:** Intelligently sorts servers by current load and geographic proximity to the user, generating configurations for the most performant connections.
23
+ * **Structured Output:** Automatically creates a clean directory structure containing standard configurations, a `best_configs` folder for optimal servers per location, and a `servers.json` file with detailed metadata for analysis.
24
+ * **Interactive and Non-Interactive:** A guided TUI for interactive use. The core logic is structured to be scriptable.
25
+
26
+ ## Installation
27
+
28
+ Prerequisites: Python 3.8+
29
+
30
+ Install the package using `pip`:
31
+
32
+ ```bash
33
+ pip install nord-config-generator
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ Execute the installed application:
39
+
40
+ ```bash
41
+ nord-generator
42
+ ```
43
+
44
+ The application will prompt for the required access token and configuration preferences.
45
+
46
+ ## Web Version
47
+
48
+ A graphical alternative is available for direct use in a web browser.
49
+
50
+ * **Current Version:** [https://nord-configs.selfhoster.nl/](https://nord-configs.selfhoster.nl/)
51
+ * **Legacy Version:** [https://wg-nord.pages.dev/](https://wg-nord.pages.dev/)
52
+
53
+ ## Support
54
+
55
+ Project visibility and continued development are supported by two actions:
56
+
57
+ 1. **Star the Repository:** Starring the project on GitHub increases its visibility.
58
+ 2. **NordVPN Referral:** Using the referral link for new subscriptions provides support at no additional cost. Link: [https://ref.nordvpn.com/MXIVDoJGpKT](https://ref.nordvpn.com/MXIVDoJGpKT)
59
+
60
+ ## License
61
+
62
+ This project is distributed under the GNU General Public License v3.0. See the `LICENSE` file for full details.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nord-config-generator"
7
+ version = "1.0.0"
8
+ authors = [
9
+ { name="Ahmed Touhami", email="mustafachyi272@gmail.com" },
10
+ ]
11
+ description = "A command-line tool for generating optimized NordVPN WireGuard configurations."
12
+ readme = "README.md"
13
+ license = { file="LICENSE" }
14
+ requires-python = ">=3.8"
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
18
+ "Operating System :: OS Independent",
19
+ "Topic :: System :: Networking",
20
+ "Environment :: Console",
21
+ ]
22
+ dependencies = [
23
+ "aiohttp>=3.8.0",
24
+ "aiofiles>=0.8.0",
25
+ "rich>=12.0.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ "Homepage" = "https://github.com/mustafachyi/NordVPN-WireGuard-Config-Generator"
30
+ "Bug Tracker" = "https://github.com/mustafachyi/NordVPN-WireGuard-Config-Generator/issues"
31
+
32
+ [project.scripts]
33
+ nord-generator = "nord_config_generator.main:cli_entry_point"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,282 @@
1
+ import sys
2
+ import os
3
+ import asyncio
4
+ import json
5
+ import base64
6
+ import re
7
+ import time
8
+ from typing import List, Tuple, Optional, Dict, Any
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from math import radians, sin, cos, asin, sqrt
12
+ from functools import partial
13
+ from concurrent.futures import ThreadPoolExecutor
14
+ from datetime import datetime
15
+
16
+ import aiohttp
17
+ import aiofiles
18
+
19
+ from .ui import ConsoleManager
20
+
21
+ NORD_API_BASE_URL = "https://api.nordvpn.com/v1"
22
+ LOCATION_API_URL = "https://ipinfo.io/json"
23
+ CONCURRENT_LIMIT = 200
24
+
25
+ @dataclass
26
+ class Server:
27
+ name: str
28
+ hostname: str
29
+ station: str
30
+ load: int
31
+ country: str
32
+ city: str
33
+ latitude: float
34
+ longitude: float
35
+ public_key: str
36
+ distance: float = 0.0
37
+
38
+ @dataclass
39
+ class UserPreferences:
40
+ dns: str = "103.86.96.100"
41
+ use_ip_for_endpoint: bool = False
42
+ persistent_keepalive: int = 25
43
+
44
+ def update_from_input(self, user_input: dict):
45
+ dns_input = user_input.get("dns")
46
+ if dns_input and re.match(r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$', dns_input):
47
+ self.dns = dns_input
48
+
49
+ self.use_ip_for_endpoint = user_input.get("endpoint_type", "").lower() == 'y'
50
+
51
+ keepalive_input = user_input.get("keepalive")
52
+ if keepalive_input and keepalive_input.isdigit() and 15 <= int(keepalive_input) <= 120:
53
+ self.persistent_keepalive = int(keepalive_input)
54
+
55
+ class NordVpnApiClient:
56
+ def __init__(self, console_manager: ConsoleManager):
57
+ self._session = aiohttp.ClientSession()
58
+ self._console = console_manager
59
+
60
+ async def _get(self, url: str, **kwargs) -> Optional[Any]:
61
+ try:
62
+ async with self._session.get(url, **kwargs) as response:
63
+ response.raise_for_status()
64
+ return await response.json()
65
+ except (aiohttp.ClientError, json.JSONDecodeError) as e:
66
+ self._console.print_message("error", f"API request failed for {url}: {e}")
67
+ return None
68
+
69
+ async def get_private_key(self, token: str) -> Optional[str]:
70
+ auth_header = base64.b64encode(f'token:{token}'.encode()).decode()
71
+ url = f"{NORD_API_BASE_URL}/users/services/credentials"
72
+ data = await self._get(url, headers={'Authorization': f'Basic {auth_header}'})
73
+ if isinstance(data, dict):
74
+ return data.get('nordlynx_private_key')
75
+ return None
76
+
77
+ async def get_all_servers(self) -> List[Dict[str, Any]]:
78
+ url = f"{NORD_API_BASE_URL}/servers"
79
+ params = {'limit': 9000, 'filters[servers_technologies][identifier]': 'wireguard_udp'}
80
+ data = await self._get(url, params=params)
81
+ if isinstance(data, list):
82
+ return data
83
+ return []
84
+
85
+ async def get_user_geolocation(self) -> Optional[Tuple[float, float]]:
86
+ data = await self._get(LOCATION_API_URL)
87
+ if not isinstance(data, dict):
88
+ return None
89
+ try:
90
+ lat, lon = data.get('loc', '').split(',')
91
+ return float(lat), float(lon)
92
+ except (ValueError, IndexError):
93
+ self._console.print_message("error", "Could not parse location data.")
94
+ return None
95
+
96
+ async def close(self):
97
+ if self._session and not self._session.closed:
98
+ await self._session.close()
99
+
100
+ class ConfigurationOrchestrator:
101
+ def __init__(self, private_key: str, preferences: UserPreferences, console_manager: ConsoleManager, api_client: NordVpnApiClient):
102
+ self._api_client = api_client
103
+ self._private_key = private_key
104
+ self._preferences = preferences
105
+ self._console = console_manager
106
+ self._output_dir = Path(f'nordvpn_configs_{datetime.now().strftime("%Y%m%d_%H%M%S")}')
107
+ self._semaphore = asyncio.Semaphore(CONCURRENT_LIMIT)
108
+ self._thread_pool = ThreadPoolExecutor(max_workers=min(32, (os.cpu_count() or 1) + 4))
109
+ self.generation_succeeded = False
110
+ self.stats = {"total": 0, "best": 0}
111
+
112
+ @staticmethod
113
+ def _calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
114
+ lon1_rad, lat1_rad, lon2_rad, lat2_rad = map(radians, [lon1, lat1, lon2, lat2])
115
+ a = sin((lat2_rad - lat1_rad) / 2)**2 + cos(lat1_rad) * cos(lat2_rad) * sin((lon2_rad - lon1_rad) / 2)**2
116
+ return 2 * asin(sqrt(a)) * 6371
117
+
118
+ def _parse_server_data(self, server_data: Dict[str, Any], user_location: Tuple[float, float]) -> Optional[Server]:
119
+ try:
120
+ location = server_data['locations'][0]
121
+ public_key = next(
122
+ tech_meta['value']
123
+ for tech in server_data['technologies'] if tech['identifier'] == 'wireguard_udp'
124
+ for tech_meta in tech['metadata'] if tech_meta['name'] == 'public_key'
125
+ )
126
+ return Server(
127
+ name=server_data['name'], hostname=server_data['hostname'], station=server_data['station'],
128
+ load=int(server_data.get('load', 0)), country=location['country']['name'],
129
+ city=location['country'].get('city', {}).get('name', 'Unknown'), latitude=location['latitude'],
130
+ longitude=location['longitude'], public_key=public_key,
131
+ distance=self._calculate_distance(user_location[0], user_location[1], location['latitude'], location['longitude'])
132
+ )
133
+ except (KeyError, IndexError, StopIteration):
134
+ return None
135
+
136
+ def _generate_wireguard_config_string(self, server: Server) -> str:
137
+ endpoint = server.station if self._preferences.use_ip_for_endpoint else server.hostname
138
+ return f"[Interface]\nPrivateKey = {self._private_key}\nAddress = 10.5.0.2/16\nDNS = {self._preferences.dns}\n\n[Peer]\nPublicKey = {server.public_key}\nAllowedIPs = 0.0.0.0/0, ::/0\nEndpoint = {endpoint}:51820\nPersistentKeepalive = {self._preferences.persistent_keepalive}"
139
+
140
+ @staticmethod
141
+ def _sanitize_path_part(part: str) -> str:
142
+ return re.sub(r'[<>:"/\\|?*\0]', '', part.lower().replace(' ', '_')).replace('#', '')
143
+
144
+ async def _save_config_file(self, config_string: str, path: Path, filename: str, progress, task):
145
+ path.mkdir(parents=True, exist_ok=True)
146
+ async with self._semaphore:
147
+ async with aiofiles.open(path / filename, 'w') as f:
148
+ await f.write(config_string)
149
+ progress.update(task, advance=1)
150
+
151
+ async def generate(self) -> Optional[Path]:
152
+ progress = self._console.create_progress_bar()
153
+ with progress:
154
+ task_data = progress.add_task("Fetching remote data...", total=2)
155
+
156
+ progress.update(task_data, description="Fetching user location...")
157
+ user_location, all_servers_data = await asyncio.gather(
158
+ self._api_client.get_user_geolocation(),
159
+ self._api_client.get_all_servers()
160
+ )
161
+
162
+ if not user_location or not all_servers_data:
163
+ return None
164
+
165
+ progress.update(task_data, advance=2, description="Processing servers...")
166
+
167
+ loop = asyncio.get_running_loop()
168
+ parse_func = partial(self._parse_server_data, user_location=user_location)
169
+ parse_tasks = [loop.run_in_executor(self._thread_pool, parse_func, s) for s in all_servers_data]
170
+
171
+ processed_servers = [server for server in await asyncio.gather(*parse_tasks) if server]
172
+ self._thread_pool.shutdown(wait=False, cancel_futures=True)
173
+
174
+ sorted_servers = sorted(processed_servers, key=lambda s: (s.load, s.distance))
175
+
176
+ self._output_dir.mkdir(exist_ok=True)
177
+ servers_info, best_servers_by_location = {}, {}
178
+
179
+ config_progress = self._console.create_progress_bar(transient=False)
180
+ with config_progress:
181
+ total_configs = len(sorted_servers)
182
+ best_configs = 0
183
+
184
+ save_tasks = []
185
+ task_save_all = config_progress.add_task("Generating configs...", total=total_configs)
186
+ for server in sorted_servers:
187
+ country_sanitized = self._sanitize_path_part(server.country)
188
+ city_sanitized = self._sanitize_path_part(server.city)
189
+ config_str = self._generate_wireguard_config_string(server)
190
+ path = self._output_dir / 'configs' / country_sanitized / city_sanitized
191
+ filename = f"{self._sanitize_path_part(server.name)}.conf"
192
+ save_tasks.append(self._save_config_file(config_str, path, filename, config_progress, task_save_all))
193
+
194
+ location_key = (server.country, server.city)
195
+ if location_key not in best_servers_by_location or server.load < best_servers_by_location[location_key].load:
196
+ best_servers_by_location[location_key] = server
197
+
198
+ country_info = servers_info.setdefault(server.country, {})
199
+ city_info = country_info.setdefault(server.city, {"distance": int(server.distance), "servers": []})
200
+ city_info["servers"].append((server.name, server.load))
201
+
202
+ self.stats["total"] = total_configs
203
+ await asyncio.gather(*save_tasks)
204
+
205
+ best_save_tasks = []
206
+ best_configs = len(best_servers_by_location)
207
+ task_save_best = config_progress.add_task("Generating optimized configs...", total=best_configs)
208
+ for server in best_servers_by_location.values():
209
+ country_sanitized = self._sanitize_path_part(server.country)
210
+ city_sanitized = self._sanitize_path_part(server.city)
211
+ config_str = self._generate_wireguard_config_string(server)
212
+ path = self._output_dir / 'best_configs' / country_sanitized / city_sanitized
213
+ filename = f"{self._sanitize_path_part(server.name)}.conf"
214
+ best_save_tasks.append(self._save_config_file(config_str, path, filename, config_progress, task_save_best))
215
+
216
+ self.stats["best"] = best_configs
217
+ await asyncio.gather(*best_save_tasks)
218
+
219
+ async with aiofiles.open(self._output_dir / 'servers.json', 'w') as f:
220
+ await f.write(json.dumps(servers_info, indent=2, separators=(',', ':'), ensure_ascii=False))
221
+
222
+ self.generation_succeeded = True
223
+ return self._output_dir
224
+
225
+ def is_valid_token_format(token: str) -> bool:
226
+ return bool(re.match(r'^[a-fA-F0-9]{64}$', token))
227
+
228
+ async def main_async():
229
+ console = ConsoleManager()
230
+ api_client = NordVpnApiClient(console)
231
+
232
+ try:
233
+ console.clear()
234
+ console.print_title()
235
+
236
+ token = console.get_user_input("Please enter your NordVPN access token: ", is_secret=True)
237
+ if not is_valid_token_format(token):
238
+ console.print_message("error", "Invalid token format.")
239
+ return
240
+
241
+ private_key = None
242
+ with console.create_progress_bar() as progress:
243
+ task = progress.add_task("Validating token...", total=1)
244
+ private_key = await api_client.get_private_key(token)
245
+ progress.update(task, advance=1)
246
+
247
+ if not private_key:
248
+ console.print_message("error", "Token is invalid or could not be verified. Please check the token and try again.")
249
+ return
250
+
251
+ console.print_message("success", "Token validated successfully.")
252
+
253
+ preferences = UserPreferences()
254
+ user_input = console.get_preferences(preferences)
255
+ preferences.update_from_input(user_input)
256
+
257
+ console.clear()
258
+
259
+ start_time = time.time()
260
+ orchestrator = ConfigurationOrchestrator(private_key, preferences, console, api_client)
261
+
262
+ output_directory = await orchestrator.generate()
263
+ elapsed_time = time.time() - start_time
264
+
265
+ if orchestrator.generation_succeeded and output_directory:
266
+ console.print_summary(output_directory, orchestrator.stats["total"], orchestrator.stats["best"], elapsed_time)
267
+ else:
268
+ console.print_message("error", "Process failed. Check the logs for details.")
269
+
270
+ except Exception as e:
271
+ console.print_message("error", f"An unrecoverable error occurred: {e}")
272
+ finally:
273
+ await api_client.close()
274
+
275
+ def cli_entry_point():
276
+ try:
277
+ asyncio.run(main_async())
278
+ except KeyboardInterrupt:
279
+ print("\nProcess interrupted by user.")
280
+
281
+ if __name__ == "__main__":
282
+ cli_entry_point()
@@ -0,0 +1,65 @@
1
+ from rich.console import Console
2
+ from rich.panel import Panel
3
+ from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeElapsedColumn
4
+ from rich.theme import Theme
5
+ from rich.table import Table
6
+ from pathlib import Path
7
+ import os
8
+
9
+ class ConsoleManager:
10
+ def __init__(self):
11
+ custom_theme = Theme({
12
+ "info": "cyan",
13
+ "success": "bold green",
14
+ "warning": "yellow",
15
+ "error": "bold red",
16
+ "title": "bold magenta",
17
+ "path": "underline bright_blue"
18
+ })
19
+ self.console = Console(theme=custom_theme)
20
+
21
+ def clear(self):
22
+ os.system('cls' if os.name == 'nt' else 'clear')
23
+
24
+ def print_title(self):
25
+ self.console.print(Panel("[title]NordVPN Configuration Generator[/title]", expand=False, border_style="info"))
26
+
27
+ def get_user_input(self, prompt: str, is_secret: bool = False) -> str:
28
+ return self.console.input(f"[info]{prompt}[/info]", password=is_secret).strip()
29
+
30
+ def get_preferences(self, defaults) -> dict:
31
+ self.console.print("\n[info]Configuration Options (press Enter to use defaults)[/info]")
32
+ dns = self.get_user_input(f"Enter DNS server IP (default: {defaults.dns}): ")
33
+ endpoint_type = self.get_user_input("Use IP instead of hostname for endpoints? (y/N): ")
34
+ keepalive = self.get_user_input(f"Enter PersistentKeepalive value (default: {defaults.persistent_keepalive}): ")
35
+ return {"dns": dns, "endpoint_type": endpoint_type, "keepalive": keepalive}
36
+
37
+ def print_message(self, style: str, message: str):
38
+ self.console.print(f"[{style}]{message}[/{style}]")
39
+
40
+ def create_progress_bar(self, transient: bool = True) -> Progress:
41
+ return Progress(
42
+ SpinnerColumn(),
43
+ TextColumn("[progress.description]{task.description}"),
44
+ BarColumn(),
45
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
46
+ TimeElapsedColumn(),
47
+ console=self.console,
48
+ transient=transient
49
+ )
50
+
51
+ def print_summary(self, output_dir: Path, total_configs: int, best_configs: int, elapsed_time: float):
52
+ summary_table = Table.grid(padding=(0, 2))
53
+ summary_table.add_column(style="info")
54
+ summary_table.add_column()
55
+ summary_table.add_row("Output Directory:", f"[path]{output_dir}[/path]")
56
+ summary_table.add_row("Standard Configs:", f"{total_configs}")
57
+ summary_table.add_row("Optimized Configs:", f"{best_configs}")
58
+ summary_table.add_row("Time Taken:", f"{elapsed_time:.2f} seconds")
59
+
60
+ self.console.print(Panel(
61
+ summary_table,
62
+ title="[success]Generation Complete[/success]",
63
+ border_style="success",
64
+ expand=False
65
+ ))
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: nord-config-generator
3
+ Version: 1.0.0
4
+ Summary: A command-line tool for generating optimized NordVPN WireGuard configurations.
5
+ Author-email: Ahmed Touhami <mustafachyi272@gmail.com>
6
+ Project-URL: Homepage, https://github.com/mustafachyi/NordVPN-WireGuard-Config-Generator
7
+ Project-URL: Bug Tracker, https://github.com/mustafachyi/NordVPN-WireGuard-Config-Generator/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: System :: Networking
12
+ Classifier: Environment :: Console
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: aiohttp>=3.8.0
16
+ Requires-Dist: aiofiles>=0.8.0
17
+ Requires-Dist: rich>=12.0.0
18
+
19
+ # NordVPN WireGuard Configuration Generator
20
+
21
+ A command-line tool for generating optimized NordVPN WireGuard configurations.
22
+
23
+ ## Project Philosophy: A Focus on Quality
24
+
25
+ This project has been fundamentally refocused. Previously, multiple versions existed across several programming languages. This approach divided development effort and resulted in inconsistent quality.
26
+
27
+ The new directive is singular: to provide one exceptionally engineered tool that is robust, maintainable, and correct.
28
+
29
+ To this end, all previous language implementations have been archived. Development is now concentrated on two platforms:
30
+
31
+ 1. **This Command-Line Tool:** A complete rewrite in Python, packaged for professional use.
32
+ 2. **A Web Interface:** For users who require a graphical frontend.
33
+
34
+ This consolidated effort ensures a higher standard of quality and a more reliable end-product.
35
+
36
+ ## Core Capabilities
37
+
38
+ * **Package Distribution:** The tool is a proper command-line application, installable via PyPI. This eliminates manual dependency management.
39
+ * **Performance:** Asynchronous architecture processes the entire NordVPN server list in seconds.
40
+ * **Optimization:** Intelligently sorts servers by current load and geographic proximity to the user, generating configurations for the most performant connections.
41
+ * **Structured Output:** Automatically creates a clean directory structure containing standard configurations, a `best_configs` folder for optimal servers per location, and a `servers.json` file with detailed metadata for analysis.
42
+ * **Interactive and Non-Interactive:** A guided TUI for interactive use. The core logic is structured to be scriptable.
43
+
44
+ ## Installation
45
+
46
+ Prerequisites: Python 3.8+
47
+
48
+ Install the package using `pip`:
49
+
50
+ ```bash
51
+ pip install nord-config-generator
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ Execute the installed application:
57
+
58
+ ```bash
59
+ nord-generator
60
+ ```
61
+
62
+ The application will prompt for the required access token and configuration preferences.
63
+
64
+ ## Web Version
65
+
66
+ A graphical alternative is available for direct use in a web browser.
67
+
68
+ * **Current Version:** [https://nord-configs.selfhoster.nl/](https://nord-configs.selfhoster.nl/)
69
+ * **Legacy Version:** [https://wg-nord.pages.dev/](https://wg-nord.pages.dev/)
70
+
71
+ ## Support
72
+
73
+ Project visibility and continued development are supported by two actions:
74
+
75
+ 1. **Star the Repository:** Starring the project on GitHub increases its visibility.
76
+ 2. **NordVPN Referral:** Using the referral link for new subscriptions provides support at no additional cost. Link: [https://ref.nordvpn.com/MXIVDoJGpKT](https://ref.nordvpn.com/MXIVDoJGpKT)
77
+
78
+ ## License
79
+
80
+ This project is distributed under the GNU General Public License v3.0. See the `LICENSE` file for full details.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/nord_config_generator/__init__.py
4
+ src/nord_config_generator/main.py
5
+ src/nord_config_generator/ui.py
6
+ src/nord_config_generator.egg-info/PKG-INFO
7
+ src/nord_config_generator.egg-info/SOURCES.txt
8
+ src/nord_config_generator.egg-info/dependency_links.txt
9
+ src/nord_config_generator.egg-info/entry_points.txt
10
+ src/nord_config_generator.egg-info/requires.txt
11
+ src/nord_config_generator.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nord-generator = nord_config_generator.main:cli_entry_point
@@ -0,0 +1,3 @@
1
+ aiohttp>=3.8.0
2
+ aiofiles>=0.8.0
3
+ rich>=12.0.0