@@ -29,7 +29,7 @@ class Group(db.Model):
2929 server_ip_v6 = db .Column (db .String (39 )) # Server's IPv6 in the range
3030
3131 # WireGuard configuration
32- listen_port = db .Column (db .Integer , default = 51820 )
32+ listen_port = db .Column (db .Integer , nullable = True ) # Auto-assigned if not provided
3333 dns = db .Column (db .String (255 ), default = '1.1.1.1, 8.8.8.8' )
3434 endpoint = db .Column (db .String (255 ))
3535 persistent_keepalive = db .Column (db .Integer , default = 25 )
@@ -106,6 +106,22 @@ def get_subnet_mask_v6(self):
106106 network = ipaddress .ip_network (self .ip_range_v6 , strict = False )
107107 return str (network .prefixlen )
108108
109+ def get_auto_listen_port (self ):
110+ """Get or assign an auto-generated unique listen port based on group ID.
111+
112+ Returns:
113+ int: A unique port number for this group's WireGuard interface
114+ """
115+ # If port is explicitly set, use it
116+ if self .listen_port and self .listen_port > 0 :
117+ return self .listen_port
118+
119+ # Generate unique port: base_port (51820) + group_id
120+ # This ensures each group gets a unique port
121+ # e.g., group 1 -> 51821, group 2 -> 51822, etc.
122+ base_port = 51820
123+ return base_port + self .id
124+
109125 def to_dict (self ):
110126 """Convert to dictionary."""
111127 return {
@@ -137,10 +153,13 @@ def generate_server_config(self):
137153 if self .server_ip_v6 and self .ip_range_v6 :
138154 address += f", { self .server_ip_v6 } /{ self .get_subnet_mask_v6 ()} "
139155
156+ # Get unique listen port
157+ listen_port = self .get_auto_listen_port ()
158+
140159 config = f"""[Interface]
141160PrivateKey = { self .server_private_key }
142161Address = { address }
143- ListenPort = { self . listen_port }
162+ ListenPort = { listen_port }
144163"""
145164
146165 # Build PostUp rules for NAT and forwarding (simplified - no peer-to-peer restrictions)
@@ -197,7 +216,7 @@ def generate_server_config(self):
197216
198217 def get_group_config_dir (self ):
199218 """Get the configuration directory path for this group.
200-
219+
201220 Returns a group-specific subdirectory for storing client configs.
202221 """
203222 from config import Config
@@ -210,10 +229,10 @@ def get_group_config_dir(self):
210229 interface_name = self .get_wireguard_interface_name ()
211230 group_dir = os .path .join (config_path , interface_name )
212231 return group_dir
213-
232+
214233 def get_server_config_path (self ):
215234 """Get the full path to the server config file.
216-
235+
217236 Returns a path like /etc/wireguard/wg-groupname.conf which wg-quick expects.
218237 Server config is stored in the base WireGuard directory for wg-quick compatibility.
219238 """
@@ -222,7 +241,7 @@ def get_server_config_path(self):
222241 config_path = Config .WG_CONFIG_PATH
223242 if not config_path :
224243 return None
225-
244+
226245 interface_name = self .get_wireguard_interface_name ()
227246 return os .path .join (config_path , f"{ interface_name } .conf" )
228247
@@ -240,14 +259,14 @@ def save_server_config(self):
240259 # Save server config with secure permissions (0600)
241260 try :
242261 config_content = self .generate_server_config ()
243-
262+
244263 # Write with secure permissions
245264 with open (server_filepath , 'w' ) as f :
246265 f .write (config_content )
247-
266+
248267 # Set secure permissions (0600 - owner read/write only)
249268 os .chmod (server_filepath , 0o600 )
250-
269+
251270 logger .info ("Server config saved for group_id=%s to %s" , self .id , server_filepath )
252271 except Exception as e :
253272 logger .error ("Failed to save server config for group_id=%s: %s" , self .id , e , exc_info = True )
@@ -265,7 +284,7 @@ def save_server_config(self):
265284
266285 def get_wireguard_interface_name (self ):
267286 """Get the WireGuard interface name for this group.
268-
287+
269288 Uses sanitized group name for the interface name (e.g., 'wg-groupname').
270289 Falls back to group ID if sanitization results in empty string.
271290 """
@@ -278,20 +297,20 @@ def get_wireguard_interface_name(self):
278297 sanitized = sanitized .replace ('--' , '-' )
279298 # Remove leading/trailing hyphens
280299 sanitized = sanitized .strip ('-' )
281-
300+
282301 # Fallback to group ID if name is empty after sanitization
283302 if not sanitized :
284303 sanitized = f"group{ self .id } "
285-
304+
286305 # Limit length to avoid filesystem issues (max 15 chars for interface name in Linux)
287306 if len (sanitized ) > 12 : # Leave room for 'wg-' prefix
288307 sanitized = sanitized [:12 ]
289308 sanitized = sanitized .rstrip ('-' )
290-
309+
291310 # Final check: ensure we have a valid name
292311 if not sanitized :
293312 sanitized = f"group{ self .id } "
294-
313+
295314 return f"wg-{ sanitized } "
296315
297316 def start_wireguard (self ):
@@ -364,7 +383,7 @@ def delete_server_config(self):
364383 logger .info ("Server config deleted for group_id=%s from %s" , self .id , server_filepath )
365384 except Exception as e :
366385 logger .error ("Failed to delete server config for group_id=%s: %s" , self .id , e , exc_info = True )
367-
386+
368387 # Delete group directory with all client configs
369388 group_dir = self .get_group_config_dir ()
370389 if group_dir :
0 commit comments