nord-config-generator 1.0.2__tar.gz → 1.0.4__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,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: nord-config-generator
3
+ Version: 1.0.4
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.9
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: aiohttp<4.0,>=3.12.14
16
+ Requires-Dist: aiofiles<25.0,>=24.1.0
17
+ Requires-Dist: rich<15.0,>=14.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nord-config-generator"
7
- version = "1.0.2"
7
+ version = "1.0.4"
8
8
  authors = [
9
9
  { name="Ahmed Touhami", email="mustafachyi272@gmail.com" },
10
10
  ]
@@ -3,7 +3,6 @@ import os
3
3
  import asyncio
4
4
  import json
5
5
  import base64
6
- import re
7
6
  import time
8
7
  from typing import List, Tuple, Optional, Dict, Any
9
8
  from dataclasses import dataclass
@@ -46,7 +45,6 @@ class GenerationStats:
46
45
  class NordVpnApiClient:
47
46
  NORD_API_BASE_URL = "https://api.nordvpn.com/v1"
48
47
  LOCATION_API_URL = "https://ipinfo.io/json"
49
- COUNTRIES_API_URL = f"{NORD_API_BASE_URL}/servers/countries"
50
48
 
51
49
  def __init__(self, console_manager: ConsoleManager):
52
50
  self._console = console_manager
@@ -71,14 +69,10 @@ class NordVpnApiClient:
71
69
 
72
70
  async def get_all_servers(self) -> List[Dict[str, Any]]:
73
71
  url = f"{self.NORD_API_BASE_URL}/servers"
74
- params = {'limit': 9000, 'filters[servers_technologies][identifier]': 'wireguard_udp'}
72
+ params = {'limit': 16384, 'filters[servers_technologies][identifier]': 'wireguard_udp'}
75
73
  data = await self._get(url, params=params)
76
74
  return data if isinstance(data, list) else []
77
75
 
78
- async def get_countries(self) -> List[Dict[str, Any]]:
79
- data = await self._get(self.COUNTRIES_API_URL)
80
- return data if isinstance(data, list) else []
81
-
82
76
  async def get_user_geolocation(self) -> Optional[Tuple[float, float]]:
83
77
  data = await self._get(self.LOCATION_API_URL)
84
78
  if not isinstance(data, dict):
@@ -103,6 +97,7 @@ class NordVpnApiClient:
103
97
 
104
98
  class ConfigurationOrchestrator:
105
99
  CONCURRENT_LIMIT = 200
100
+ _path_sanitizer = str.maketrans('', '', '<>:"/\\|?*\0')
106
101
 
107
102
  def __init__(self, private_key: str, preferences: UserPreferences, console_manager: ConsoleManager, api_client: NordVpnApiClient):
108
103
  self._private_key = private_key
@@ -114,12 +109,17 @@ class ConfigurationOrchestrator:
114
109
  self.stats = GenerationStats()
115
110
 
116
111
  async def generate(self) -> Optional[Path]:
117
- user_location, all_servers_data, countries_data = await self._fetch_remote_data()
118
- if not user_location or not all_servers_data or not countries_data:
112
+ user_location, all_servers_data = await self._fetch_remote_data()
113
+ if not user_location or not all_servers_data:
119
114
  return None
120
115
 
121
- country_code_map = {country['name']: country['code'].lower() for country in countries_data}
122
- processed_servers = await self._process_server_data(all_servers_data, user_location, country_code_map)
116
+ processed_servers = await self._process_server_data(all_servers_data, user_location)
117
+
118
+ unique_servers = {}
119
+ for s in processed_servers:
120
+ if s.name not in unique_servers:
121
+ unique_servers[s.name] = s
122
+ processed_servers = list(unique_servers.values())
123
123
 
124
124
  sorted_servers = sorted(processed_servers, key=lambda s: (s.load, s.distance))
125
125
  best_servers_by_location = self._get_best_servers(sorted_servers)
@@ -130,20 +130,19 @@ class ConfigurationOrchestrator:
130
130
  await self._save_all_configurations(sorted_servers, best_servers_by_location, servers_info)
131
131
  return self._output_dir
132
132
 
133
- async def _fetch_remote_data(self) -> Tuple[Optional[Tuple[float, float]], List[Dict[str, Any]], List[Dict[str, Any]]]:
133
+ async def _fetch_remote_data(self) -> Tuple[Optional[Tuple[float, float]], List[Dict[str, Any]]]:
134
134
  with self._console.create_progress_bar() as progress:
135
- task = progress.add_task("Fetching remote data...", total=3)
136
- user_location, all_servers_data, countries_data = await asyncio.gather(
135
+ task = progress.add_task("Fetching remote data...", total=2)
136
+ user_location, all_servers_data = await asyncio.gather(
137
137
  self._api_client.get_user_geolocation(),
138
- self._api_client.get_all_servers(),
139
- self._api_client.get_countries()
138
+ self._api_client.get_all_servers()
140
139
  )
141
- progress.update(task, advance=3)
142
- return user_location, all_servers_data, countries_data
140
+ progress.update(task, advance=2)
141
+ return user_location, all_servers_data
143
142
 
144
- async def _process_server_data(self, all_servers_data: List[Dict[str, Any]], user_location: Tuple[float, float], country_code_map: Dict[str, str]) -> List[Server]:
143
+ async def _process_server_data(self, all_servers_data: List[Dict[str, Any]], user_location: Tuple[float, float]) -> List[Server]:
145
144
  loop = asyncio.get_running_loop()
146
- parse_func = partial(self._parse_server_data, user_location=user_location, country_code_map=country_code_map)
145
+ parse_func = partial(self._parse_server_data, user_location=user_location)
147
146
  with ThreadPoolExecutor(max_workers=min(32, (os.cpu_count() or 1) + 4)) as executor:
148
147
  tasks = [loop.run_in_executor(executor, parse_func, s) for s in all_servers_data]
149
148
  processed_servers = await asyncio.gather(*tasks)
@@ -166,6 +165,8 @@ class ConfigurationOrchestrator:
166
165
  return info
167
166
 
168
167
  async def _save_all_configurations(self, sorted_servers: List[Server], best_servers: Dict, servers_info: Dict):
168
+ used_paths: Dict[str, int] = {}
169
+
169
170
  with self._console.create_progress_bar(transient=False) as progress:
170
171
  self.stats.total_configs = len(sorted_servers)
171
172
  self.stats.best_configs = len(best_servers)
@@ -173,18 +174,46 @@ class ConfigurationOrchestrator:
173
174
  task_all = progress.add_task("Generating standard configs...", total=self.stats.total_configs)
174
175
  task_best = progress.add_task("Generating optimized configs...", total=self.stats.best_configs)
175
176
 
176
- save_tasks = [self._create_save_task(s, 'configs', progress, task_all) for s in sorted_servers]
177
- save_tasks.extend([self._create_save_task(s, 'best_configs', progress, task_best) for s in best_servers.values()])
177
+ save_tasks = []
178
+ save_tasks.extend(self._create_batch_save_tasks(sorted_servers, 'configs', progress, task_all, used_paths))
179
+ save_tasks.extend(self._create_batch_save_tasks(list(best_servers.values()), 'best_configs', progress, task_best, used_paths))
178
180
 
179
181
  await asyncio.gather(*save_tasks)
180
182
  async with aiofiles.open(self._output_dir / 'servers.json', 'w') as f:
181
183
  await f.write(json.dumps(servers_info, indent=2, separators=(',', ':'), ensure_ascii=False))
182
184
 
183
- def _create_save_task(self, server: Server, subfolder: str, progress, task_id):
184
- config_str = self._generate_wireguard_config_string(server, self._preferences, self._private_key)
185
- path = self._output_dir / subfolder / self._sanitize_path_part(server.country) / self._sanitize_path_part(server.city)
186
- filename = self._generate_compliant_filename(server)
187
- return self._save_config_file(config_str, path, filename, progress, task_id)
185
+ def _create_batch_save_tasks(self, servers: List[Server], subfolder: str, progress, task_id, used_paths: Dict[str, int]):
186
+ tasks = []
187
+ for server in servers:
188
+ country_clean = self._sanitize_path_part(server.country)
189
+ city_clean = self._sanitize_path_part(server.city)
190
+ dir_path = self._output_dir / subfolder / country_clean / city_clean
191
+
192
+ base_filename = self._extract_base_filename(server)
193
+ rel_path = f"{subfolder}/{country_clean}/{city_clean}/{base_filename}"
194
+
195
+ if rel_path in used_paths:
196
+ idx = used_paths[rel_path]
197
+ if idx == 0: idx = 1
198
+
199
+ base_path_no_ext = rel_path[:-5]
200
+ base_name_no_ext = base_filename[:-5]
201
+
202
+ while True:
203
+ new_rel = f"{base_path_no_ext}_{idx}.conf"
204
+ if new_rel not in used_paths:
205
+ used_paths[rel_path] = idx + 1
206
+ used_paths[new_rel] = 0
207
+ filename = f"{base_name_no_ext}_{idx}.conf"
208
+ break
209
+ idx += 1
210
+ else:
211
+ filename = base_filename
212
+ used_paths[rel_path] = 0
213
+
214
+ config_str = self._generate_wireguard_config_string(server, self._preferences, self._private_key)
215
+ tasks.append(self._save_config_file(config_str, dir_path, filename, progress, task_id))
216
+ return tasks
188
217
 
189
218
  async def _save_config_file(self, config_string: str, path: Path, filename: str, progress, task_id):
190
219
  path.mkdir(parents=True, exist_ok=True)
@@ -194,15 +223,23 @@ class ConfigurationOrchestrator:
194
223
  progress.update(task_id, advance=1)
195
224
 
196
225
  @staticmethod
197
- def _generate_compliant_filename(server: Server) -> str:
198
- server_number_match = re.search(r'\d+$', server.name)
199
- if not server_number_match:
200
- fallback_name = f"wg{server.station.replace('.', '')}"
201
- return f"{fallback_name[:15]}.conf"
226
+ def _extract_base_filename(server: Server) -> str:
227
+ s = server.name
228
+ num = ""
229
+ for i in range(len(s) - 1, -1, -1):
230
+ if s[i].isdigit():
231
+ start = i
232
+ while start >= 0 and s[start].isdigit():
233
+ start -= 1
234
+ num = s[start+1 : i+1]
235
+ break
202
236
 
203
- server_number = server_number_match.group(0)
204
- base_name = f"{server.country_code}{server_number}"
205
- return f"{base_name[:15]}.conf"
237
+ if not num:
238
+ fallback = f"wg{server.station.replace('.', '')}"
239
+ return f"{fallback[:15]}.conf"
240
+
241
+ base = f"{server.country_code}{num}"
242
+ return f"{base[:15]}.conf"
206
243
 
207
244
  @staticmethod
208
245
  def _generate_wireguard_config_string(server: Server, preferences: UserPreferences, private_key: str) -> str:
@@ -210,14 +247,11 @@ class ConfigurationOrchestrator:
210
247
  return f"[Interface]\nPrivateKey = {private_key}\nAddress = 10.5.0.2/16\nDNS = {preferences.dns}\n\n[Peer]\nPublicKey = {server.public_key}\nAllowedIPs = 0.0.0.0/0, ::/0\nEndpoint = {endpoint}:51820\nPersistentKeepalive = {preferences.persistent_keepalive}"
211
248
 
212
249
  @staticmethod
213
- def _parse_server_data(server_data: Dict[str, Any], user_location: Tuple[float, float], country_code_map: Dict[str, str]) -> Optional[Server]:
250
+ def _parse_server_data(server_data: Dict[str, Any], user_location: Tuple[float, float]) -> Optional[Server]:
214
251
  try:
215
252
  location = server_data['locations'][0]
216
- country_name = location['country']['name']
217
- country_code = country_code_map.get(country_name)
218
- if not country_code:
219
- return None
220
-
253
+ country_info = location['country']
254
+
221
255
  public_key = next(
222
256
  m['value'] for t in server_data['technologies']
223
257
  if t['identifier'] == 'wireguard_udp'
@@ -229,8 +263,8 @@ class ConfigurationOrchestrator:
229
263
  return Server(
230
264
  name=server_data['name'], hostname=server_data['hostname'],
231
265
  station=server_data['station'], load=int(server_data.get('load', 0)),
232
- country=country_name, country_code=country_code,
233
- city=location['country'].get('city', {}).get('name', 'Unknown'),
266
+ country=country_info['name'], country_code=country_info['code'].lower(),
267
+ city=country_info.get('city', {}).get('name', 'Unknown'),
234
268
  latitude=location['latitude'], longitude=location['longitude'],
235
269
  public_key=public_key, distance=distance
236
270
  )
@@ -246,9 +280,9 @@ class ConfigurationOrchestrator:
246
280
  c = 2 * asin(sqrt(a))
247
281
  return c * 6371
248
282
 
249
- @staticmethod
250
- def _sanitize_path_part(part: str) -> str:
251
- return re.sub(r'[<>:"/\\|?*\0]', '', part.lower().replace(' ', '_')).replace('#', '')
283
+ @classmethod
284
+ def _sanitize_path_part(cls, part: str) -> str:
285
+ return part.lower().replace(' ', '_').replace('#', '').translate(cls._path_sanitizer)
252
286
 
253
287
  class Application:
254
288
  def __init__(self):
@@ -301,22 +335,29 @@ class Application:
301
335
  user_input = self._console.get_preferences(defaults)
302
336
 
303
337
  dns_input = user_input.get("dns")
304
- dns = dns_input if dns_input and re.match(r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$', dns_input) else defaults.dns
305
-
338
+ if dns_input:
339
+ parts = dns_input.split('.')
340
+ if len(parts) == 4 and all(p.isdigit() and 0 <= int(p) <= 255 for p in parts):
341
+ defaults.dns = dns_input
342
+
306
343
  use_ip = user_input.get("endpoint_type", "").lower() == 'y'
307
344
 
308
- keepalive = defaults.persistent_keepalive
309
345
  keepalive_input = user_input.get("keepalive")
310
346
  if keepalive_input and keepalive_input.isdigit():
311
347
  keepalive_val = int(keepalive_input)
312
348
  if 15 <= keepalive_val <= 120:
313
- keepalive = keepalive_val
349
+ defaults.persistent_keepalive = keepalive_val
314
350
 
315
- return UserPreferences(dns=dns, use_ip_for_endpoint=use_ip, persistent_keepalive=keepalive)
351
+ return UserPreferences(
352
+ dns=defaults.dns,
353
+ use_ip_for_endpoint=use_ip,
354
+ persistent_keepalive=defaults.persistent_keepalive
355
+ )
316
356
 
317
357
  async def _get_validated_private_key(self, api_client: NordVpnApiClient) -> Optional[str]:
318
358
  token = self._console.get_user_input("Please enter your NordVPN access token: ", is_secret=True)
319
- if not re.match(r'^[a-fA-F0-9]{64}$', token):
359
+ is_hex = len(token) == 64 and all(c in '0123456789abcdefABCDEF' for c in token)
360
+ if not is_hex:
320
361
  self._console.print_message("error", "Invalid token format.")
321
362
  return None
322
363
 
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: nord-config-generator
3
+ Version: 1.0.4
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.9
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: aiohttp<4.0,>=3.12.14
16
+ Requires-Dist: aiofiles<25.0,>=24.1.0
17
+ Requires-Dist: rich<15.0,>=14.0.0
@@ -1,4 +1,3 @@
1
- README.md
2
1
  pyproject.toml
3
2
  src/nord_config_generator/__init__.py
4
3
  src/nord_config_generator/main.py
@@ -1,90 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: nord-config-generator
3
- Version: 1.0.2
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.9
14
- Description-Content-Type: text/markdown
15
- Requires-Dist: aiohttp<4.0,>=3.12.14
16
- Requires-Dist: aiofiles<25.0,>=24.1.0
17
- Requires-Dist: rich<15.0,>=14.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 rich-CLI for interactive use. The core logic is structured to be scriptable.
43
-
44
- ## Installation
45
-
46
- Prerequisites: Python 3.9+
47
-
48
- Install the package using `pip`:
49
-
50
- ```bash
51
- pip install nord-config-generator
52
- ```
53
-
54
- ## Usage
55
-
56
- ### Generate Configurations (Default Action)
57
-
58
- Execute the application without any arguments. This is the primary function.
59
-
60
- ```bash
61
- nordgen
62
- ```
63
-
64
- The application will prompt for the required access token and configuration preferences.
65
-
66
- ### Retrieve Private Key
67
-
68
- To retrieve and display your NordLynx private key without generating configurations, use the `get-key` command:
69
-
70
- ```bash
71
- nordgen get-key
72
- ```
73
-
74
- ## Web Version
75
-
76
- A graphical alternative is available for direct use in a web browser.
77
-
78
- * **Current Version:** [https://nord-configs.selfhoster.nl/](https://nord-configs.selfhoster.nl/)
79
- * **Legacy Version:** [https://wg-nord.pages.dev/](https://wg-nord.pages.dev/)
80
-
81
- ## Support
82
-
83
- Project visibility and continued development are supported by two actions:
84
-
85
- 1. **Star the Repository:** Starring the project on GitHub increases its visibility.
86
- 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)
87
-
88
- ## License
89
-
90
- This project is distributed under the GNU General Public License v3.0. See the `LICENSE` file for full details.
@@ -1,72 +0,0 @@
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 rich-CLI for interactive use. The core logic is structured to be scriptable.
25
-
26
- ## Installation
27
-
28
- Prerequisites: Python 3.9+
29
-
30
- Install the package using `pip`:
31
-
32
- ```bash
33
- pip install nord-config-generator
34
- ```
35
-
36
- ## Usage
37
-
38
- ### Generate Configurations (Default Action)
39
-
40
- Execute the application without any arguments. This is the primary function.
41
-
42
- ```bash
43
- nordgen
44
- ```
45
-
46
- The application will prompt for the required access token and configuration preferences.
47
-
48
- ### Retrieve Private Key
49
-
50
- To retrieve and display your NordLynx private key without generating configurations, use the `get-key` command:
51
-
52
- ```bash
53
- nordgen get-key
54
- ```
55
-
56
- ## Web Version
57
-
58
- A graphical alternative is available for direct use in a web browser.
59
-
60
- * **Current Version:** [https://nord-configs.selfhoster.nl/](https://nord-configs.selfhoster.nl/)
61
- * **Legacy Version:** [https://wg-nord.pages.dev/](https://wg-nord.pages.dev/)
62
-
63
- ## Support
64
-
65
- Project visibility and continued development are supported by two actions:
66
-
67
- 1. **Star the Repository:** Starring the project on GitHub increases its visibility.
68
- 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)
69
-
70
- ## License
71
-
72
- This project is distributed under the GNU General Public License v3.0. See the `LICENSE` file for full details.
@@ -1,90 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: nord-config-generator
3
- Version: 1.0.2
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.9
14
- Description-Content-Type: text/markdown
15
- Requires-Dist: aiohttp<4.0,>=3.12.14
16
- Requires-Dist: aiofiles<25.0,>=24.1.0
17
- Requires-Dist: rich<15.0,>=14.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 rich-CLI for interactive use. The core logic is structured to be scriptable.
43
-
44
- ## Installation
45
-
46
- Prerequisites: Python 3.9+
47
-
48
- Install the package using `pip`:
49
-
50
- ```bash
51
- pip install nord-config-generator
52
- ```
53
-
54
- ## Usage
55
-
56
- ### Generate Configurations (Default Action)
57
-
58
- Execute the application without any arguments. This is the primary function.
59
-
60
- ```bash
61
- nordgen
62
- ```
63
-
64
- The application will prompt for the required access token and configuration preferences.
65
-
66
- ### Retrieve Private Key
67
-
68
- To retrieve and display your NordLynx private key without generating configurations, use the `get-key` command:
69
-
70
- ```bash
71
- nordgen get-key
72
- ```
73
-
74
- ## Web Version
75
-
76
- A graphical alternative is available for direct use in a web browser.
77
-
78
- * **Current Version:** [https://nord-configs.selfhoster.nl/](https://nord-configs.selfhoster.nl/)
79
- * **Legacy Version:** [https://wg-nord.pages.dev/](https://wg-nord.pages.dev/)
80
-
81
- ## Support
82
-
83
- Project visibility and continued development are supported by two actions:
84
-
85
- 1. **Star the Repository:** Starring the project on GitHub increases its visibility.
86
- 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)
87
-
88
- ## License
89
-
90
- This project is distributed under the GNU General Public License v3.0. See the `LICENSE` file for full details.