@@ -1471,6 +1471,7 @@ def __init__(
14711471 relay_protocol : str = None ,
14721472 relay_host : str = None ,
14731473 relay_port : int = None ,
1474+ uuid_seed : str = None ,
14741475 ):
14751476 """Initialize a SingBoxProxy instance.
14761477
@@ -1508,6 +1509,7 @@ def __init__(
15081509 (e.g., "vmess", "ss", "trojan").
15091510 relay_host: Hostname or IP address of the relay server.
15101511 relay_port: Port of the relay server. If None, an unused port is automatically selected
1512+ uuid_seed: Optional seed string for generating consistent UUIDs in relay mode.
15111513
15121514 Raises:
15131515 TypeError: If config is not a string or path-like object.
@@ -1571,6 +1573,7 @@ def __init__(
15711573 self .relay_host = relay_host
15721574 self .relay_port = relay_port or (self ._pick_unused_port ([self .http_port , self .socks_port ]) if relay_protocol else None )
15731575 self .relay_url = None
1576+ self .uuid_seed = uuid_seed
15741577 self ._relay_credentials = {} # Store credentials for URL generation
15751578
15761579 # Runtime state
@@ -1704,6 +1707,40 @@ def stderr(self) -> str:
17041707 """
17051708 return "" .join (self ._stderr_lines )
17061709
1710+ def _generate_deterministic_uuid (self , seed : str , suffix : str = "" ) -> str :
1711+ """Generate a deterministic UUID from a seed string.
1712+
1713+ Args:
1714+ seed: The seed string to generate UUID from
1715+ suffix: Optional suffix to append to seed for different UUIDs
1716+
1717+ Returns:
1718+ str: A valid UUID v5 string
1719+ """
1720+ import uuid
1721+
1722+ # Use UUID5 with a namespace for deterministic generation
1723+ namespace = uuid .NAMESPACE_DNS
1724+ combined_seed = f"{ seed } { suffix } "
1725+ return str (uuid .uuid5 (namespace , combined_seed ))
1726+
1727+ def _generate_deterministic_password (self , seed : str , length : int = 16 ) -> str :
1728+ """Generate a deterministic password from a seed string.
1729+
1730+ Args:
1731+ seed: The seed string to generate password from
1732+ length: Length of the password (for shadowsocks compatibility)
1733+
1734+ Returns:
1735+ str: A deterministic password
1736+ """
1737+ import hashlib
1738+
1739+ # Generate deterministic password from seed
1740+ hash_obj = hashlib .sha256 (seed .encode ())
1741+ # Take hex digest and truncate to desired length
1742+ return hash_obj .hexdigest ()[:length ]
1743+
17071744 def _get_public_ip (self ) -> str :
17081745 """Get the public IP address of this machine.
17091746
@@ -1813,7 +1850,10 @@ def _generate_relay_inbound(self) -> dict:
18131850 port = self .relay_port
18141851
18151852 if protocol == "vmess" :
1816- user_id = str (uuid .uuid4 ())
1853+ if self .uuid_seed :
1854+ user_id = self ._generate_deterministic_uuid (self .uuid_seed , "vmess" )
1855+ else :
1856+ user_id = str (uuid .uuid4 ())
18171857 self ._relay_credentials ["uuid" ] = user_id
18181858 return {
18191859 "type" : "vmess" ,
@@ -1824,12 +1864,18 @@ def _generate_relay_inbound(self) -> dict:
18241864 }
18251865
18261866 elif protocol == "trojan" :
1827- password = str (uuid .uuid4 ())
1867+ if self .uuid_seed :
1868+ password = self ._generate_deterministic_password (self .uuid_seed + "trojan" , 36 )
1869+ else :
1870+ password = str (uuid .uuid4 ())
18281871 self ._relay_credentials ["password" ] = password
18291872 return {"type" : "trojan" , "tag" : "relay-in" , "listen" : "0.0.0.0" , "listen_port" : port , "users" : [{"password" : password }]}
18301873
18311874 elif protocol in ("ss" , "shadowsocks" ):
1832- password = str (uuid .uuid4 ())[:16 ]
1875+ if self .uuid_seed :
1876+ password = self ._generate_deterministic_password (self .uuid_seed + "shadowsocks" , 16 )
1877+ else :
1878+ password = str (uuid .uuid4 ())[:16 ]
18331879 self ._relay_credentials ["password" ] = password
18341880 return {
18351881 "type" : "shadowsocks" ,
0 commit comments