@@ -960,46 +960,18 @@ def songs_preview():
960960
961961
962962def signal_handler (signum , frame ):
963- print ( f "Received signal { signum } , shutting down gracefully..." )
963+ logger . info ( "Received signal %d , shutting down gracefully..." , signum )
964964 WebRTCMicrophoneManager ().stop ()
965- print ("All microphones stopped. Exiting now." )
966- # Cleanup iptables entries for port remapping if they were created
967- if 'args' in globals () and getattr (args , 'remap_ssl_port' , False ):
968- print ("Cleaning up iptables port remapping rules..." )
969- try :
970- ip_list = []
971- for iface in socket .if_nameindex ():
972- name = iface [1 ]
973- try :
974- for fam , _ , _ , _ , sockaddr in socket .getaddrinfo (name , None ):
975- if fam == socket .AF_INET :
976- ip = sockaddr [0 ]
977- if ip != "0.0.0.0" and ip != "127.0.0.1" and ip not in ip_list :
978- ip_list .append (ip )
979- except Exception :
980- continue
981- for ip in ip_list :
982- del_cmd = [
983- "sudo" , "iptables" , "-t" , "nat" , "-D" , "PREROUTING" ,
984- "-p" , "tcp" , "-d" , ip , "--dport" , "443" ,
985- "-j" , "REDIRECT" , "--to-port" , str (args .port )
986- ]
987- print ("Removing:" , " " .join (del_cmd ))
988- result = subprocess .run (del_cmd , capture_output = True , text = True )
989- if result .returncode == 0 :
990- print (f"Removed iptables rule for { ip } :443 -> { args .port } " )
991- else :
992- print (f"Failed to remove rule for { ip } : { result .stderr } " )
993- except Exception as e :
994- print ("Error cleaning up iptables rules:" , e )
995- #time.sleep(0.1) # Give some time for cleanup
996- raise RuntimeError ("Server going down" )
965+ print ("Terminating server..." )
966+ sys .exit (0 )
997967
998968def initialize_record_section ():
969+ """Initialize the [Record] section in config.ini for 6 virtual sinks."""
970+ print ("Initializing [Record] section in config.ini for 6 virtual sinks..." )
999971 base_dir = os .path .dirname (__file__ )
1000972 cfg_path = os .path .realpath (os .path .join (base_dir , args .usdx_dir , 'config.ini' ))
1001973 if not os .path .exists (cfg_path ):
1002- print (f"Config path not found: { cfg_path } " )
974+ logger . error (f"Config path not found: { cfg_path } " )
1003975 sys .exit (1 )
1004976 cp = NoSpaceConfigParser ()
1005977 cp .optionxform = str
@@ -1013,8 +985,8 @@ def initialize_record_section():
1013985 for k in keys_to_remove :
1014986 cp .remove_option ('Record' , k )
1015987 # Add 6 virtual sinks
1016- for i in range (1 , 7 ):
1017- cp ['Record' ][f'DeviceName[{ i } ]' ] = f'smartphone-mic-{ i - 1 } -sink Audio/Source/Virtual sink'
988+ for i in range (1 , 6 ):
989+ cp ['Record' ][f'DeviceName[{ i } ]' ] = f'smartphone-mic-{ i } -sink Audio/Source/Virtual sink'
1018990 cp ['Record' ][f'Input[{ i } ]' ] = '0'
1019991 cp ['Record' ][f'Latency[{ i } ]' ] = '-1'
1020992 cp ['Record' ][f'Channel1[{ i } ]' ] = str (i )
@@ -1023,11 +995,11 @@ def initialize_record_section():
1023995 with open (tmp_path , 'w' , encoding = 'utf-8' ) as fh :
1024996 cp .write (fh )
1025997 os .replace (tmp_path , cfg_path )
1026- print ( f "[Record] section in config.ini initialized for 6 virtual sinks." )
998+ logger . info ( "[Record] section in config.ini initialized for 6 virtual sinks." )
1027999
10281000def setup_domain_hotspot_mapping (domain ):
1001+ print (f"Setting up domain '{ domain } ' to map to Wi-Fi hotspot IP addresses..." )
10291002 CONF_PATH = "/etc/NetworkManager/dnsmasq-shared.d/usdx.conf"
1030- print ("Detecting Wi-Fi hotspots..." )
10311003 try :
10321004 iw_result = subprocess .run (["iw" , "dev" ], capture_output = True , text = True )
10331005 hotspots = []
@@ -1039,7 +1011,7 @@ def setup_domain_hotspot_mapping(domain):
10391011 hotspots .append (iface )
10401012 iface = None
10411013 if not hotspots :
1042- print ("No Wi-Fi hotspot found." )
1014+ logger . info ("No Wi-Fi hotspot found." )
10431015 for IFACE in hotspots :
10441016 ip_result = subprocess .run (["ip" , "-4" , "-o" , "addr" , "show" , "dev" , IFACE ], capture_output = True , text = True )
10451017 ip = None
@@ -1048,11 +1020,11 @@ def setup_domain_hotspot_mapping(domain):
10481020 ip = part .split ("/" )[0 ]
10491021 break
10501022 if not ip :
1051- print (f"Could not determine IP address for { IFACE } . Skipping." )
1023+ logger . info (f"Could not determine IP address for { IFACE } . Skipping." )
10521024 continue
1053- print (f"Hotspot device: { IFACE } " )
1054- print (f"IP address: { ip } " )
1055- print ("----" )
1025+ logger . info (f"Hotspot device: { IFACE } " )
1026+ logger . info (f"IP address: { ip } " )
1027+ logger . info ("----" )
10561028 # Prepare new config content
10571029 new_content = f"address=/{ domain } /{ ip } \n local-ttl=86400\n "
10581030 # Read current config if exists
@@ -1068,33 +1040,33 @@ def setup_domain_hotspot_mapping(domain):
10681040 # Backup config with sudo
10691041 if os .path .exists (CONF_PATH ):
10701042 subprocess .run (["sudo" , "cp" , CONF_PATH , CONF_PATH + ".bak" ])
1071- print (f"Backed up { CONF_PATH } to { CONF_PATH } .bak" )
1043+ logger . info (f"Backed up { CONF_PATH } to { CONF_PATH } .bak" )
10721044 # Write new config with sudo tee
10731045 proc = subprocess .run (["echo" , new_content ], capture_output = True , text = True )
10741046 tee_proc = subprocess .run (["sudo" , "tee" , CONF_PATH ], input = proc .stdout , text = True , capture_output = True )
10751047 if tee_proc .returncode != 0 :
1076- print (f"Failed to write { CONF_PATH } : { tee_proc .stderr } " )
1048+ logger . error (f"Failed to write { CONF_PATH } : { tee_proc .stderr } " )
10771049 sys .exit (1 )
10781050 # Set world-readable permissions
10791051 subprocess .run (["sudo" , "chmod" , "644" , CONF_PATH ])
1080- print (f"Updated { CONF_PATH } :" )
1081- print (new_content )
1052+ logger . info (f"Updated { CONF_PATH } :" )
1053+ logger . info (new_content )
10821054 # Restart NetworkManager
1083- print ("Restarting NetworkManager to apply changes..." )
1055+ logger . info ("Restarting NetworkManager to apply changes..." )
10841056 result = subprocess .run (["sudo" , "systemctl" , "restart" , "NetworkManager" ])
10851057 if result .returncode == 0 :
1086- print ("NetworkManager restarted successfully." )
1058+ logger . info ("NetworkManager restarted successfully." )
10871059 else :
1088- print ("Failed to restart NetworkManager. Please check manually." )
1060+ logger . error ("Failed to restart NetworkManager. Please check manually." )
10891061 else :
1090- print (f"No changes needed for { CONF_PATH } ." )
1091- print ("All done!" )
1062+ logger . info (f"No changes needed for { CONF_PATH } ." )
1063+ logger . info ("All done!" )
10921064 except Exception as e :
1093- print ("Error during domain setup:" , e , file = sys . stderr )
1065+ logger . error ("Error during domain setup:" , e )
10941066 sys .exit (1 )
10951067
10961068def remap_ssl_port ():
1097- print ("Remapping port 443 to port " , args .port , "using iptables..." )
1069+ print ("Remapping port 443 to" , args .port , "using iptables..." )
10981070 try :
10991071 # Use 'ip -4 -o addr show' to get all IPv4 addresses
11001072 ip_list = []
@@ -1112,43 +1084,29 @@ def remap_ssl_port():
11121084 continue
11131085 ip_list .append (ip )
11141086 if not ip_list :
1115- print ("No valid global IPv4 addresses found for remapping." )
1087+ logger . info ("No valid global IPv4 addresses found for remapping." )
11161088 for ip in ip_list :
11171089 # Check if rule already exists
11181090 check_cmd = ["sudo" , "iptables" , "-t" , "nat" , "-C" , "PREROUTING" , "-p" , "tcp" , "-d" , ip , "--dport" , "443" , "-j" , "REDIRECT" , "--to-port" , str (args .port )]
11191091 check_result = subprocess .run (check_cmd , capture_output = True , text = True )
11201092 if check_result .returncode == 0 :
1121- print (f"Rule for { ip } :443 -> { args .port } already exists, skipping." )
1093+ logger . info (f"Rule for { ip } :443 -> { args .port } already exists, skipping." )
11221094 continue
11231095 cmd = ["sudo" , "iptables" , "-t" , "nat" , "-A" , "PREROUTING" , "-p" , "tcp" , "-d" , ip , "--dport" , "443" , "-j" , "REDIRECT" , "--to-port" , str (args .port )]
1124- print ("Running:" , " " .join (cmd ))
1096+ logger . info ("Running: %s " , " " .join (cmd ))
11251097 result = subprocess .run (cmd , capture_output = True , text = True )
11261098 if result .returncode == 0 :
1127- print ( f "Remapped 443 to { args .port } for { ip } " )
1099+ logger . info ( "Remapped 443 to %d for %s" , args .port , ip )
11281100 else :
1129- print ( f "Failed to remap for { ip } : { result .stderr } " )
1101+ logger . error ( "Failed to remap for %s: %s" , ip , result .stderr )
11301102 except Exception as e :
1131- print ("Error remapping SSL port:" , e )
1103+ logger . error ("Error remapping SSL port: %s " , e )
11321104
11331105def handle_start_hotspot (hotspot_name ):
11341106 if not hotspot_name :
11351107 return
1136- status = subprocess .run (["nmcli" , "c" , "show" , hotspot_name ], capture_output = True , text = True )
1137- if status .returncode == 0 :
1138- for line in status .stdout .splitlines ():
1139- if line .strip ().startswith ("ipv4.addresses:" ):
1140- ip = line .split (":" , 1 )[1 ].strip ()
1141- if ip and ip != '--' :
1142- print (f"Hotspot '{ hotspot_name } ' is up with IP { ip } ." )
1143- return
1144- print (f"Bringing up hotspot '{ hotspot_name } ' with nmcli..." )
1145- result = subprocess .run (["nmcli" , "c" , "up" , hotspot_name ], capture_output = True , text = True )
1146- if result .returncode != 0 :
1147- print (f"Failed to start hotspot '{ hotspot_name } ': { result .stderr } " )
1148- sys .exit (1 )
1149- print (f"Hotspot '{ hotspot_name } ' activation requested. Waiting for it to have an IP address..." )
1150- # Wait for the hotspot to be up and have an IP address
1151- for _ in range (20 ):
1108+ waiting = 0
1109+ while waiting < 30 :
11521110 status = subprocess .run (["nmcli" , "c" , "show" , hotspot_name ], capture_output = True , text = True )
11531111 if status .returncode == 0 :
11541112 for line in status .stdout .splitlines ():
@@ -1157,10 +1115,48 @@ def handle_start_hotspot(hotspot_name):
11571115 if ip and ip != '--' :
11581116 print (f"Hotspot '{ hotspot_name } ' is up with IP { ip } ." )
11591117 return
1160- time .sleep (1 )
1161- print (f"Timeout waiting for hotspot '{ hotspot_name } ' to have an IP address." )
1118+ elif waiting == 0 :
1119+ logger .info (f"Bringing up hotspot '{ hotspot_name } ' with nmcli..." )
1120+ result = subprocess .run (["nmcli" , "c" , "up" , hotspot_name ], capture_output = True , text = True )
1121+ waiting = 1
1122+ if result .returncode != 0 :
1123+ logger .error (f"Failed to start hotspot '{ hotspot_name } ': { result .stderr } " )
1124+ sys .exit (1 )
1125+ else :
1126+ logger .info (f"Waiting for an IP address..." )
1127+ waiting += 1
1128+ time .sleep (1 )
1129+ logger .error (f"Timeout waiting for hotspot '{ hotspot_name } ' to have an IP address." )
11621130 sys .exit (1 )
11631131
1132+ def setup_iptables_forwarding (internet_device , hotspot_device ):
1133+ print (f"Setting up forwarding from internet={ internet_device } to hotspot={ hotspot_device } " )
1134+ rules = [
1135+ {
1136+ "check" : ["sudo" , "iptables" , "-t" , "nat" , "-C" , "POSTROUTING" , "-o" , hotspot_device , "-j" , "MASQUERADE" ],
1137+ "add" : ["sudo" , "iptables" , "-t" , "nat" , "-A" , "POSTROUTING" , "-o" , hotspot_device , "-j" , "MASQUERADE" ]
1138+ },
1139+ {
1140+ "check" : ["sudo" , "iptables" , "-C" , "FORWARD" , "-i" , hotspot_device , "-o" , internet_device , "-m" , "state" , "--state" , "RELATED,ESTABLISHED" , "-j" , "ACCEPT" ],
1141+ "add" : ["sudo" , "iptables" , "-A" , "FORWARD" , "-i" , hotspot_device , "-o" , internet_device , "-m" , "state" , "--state" , "RELATED,ESTABLISHED" , "-j" , "ACCEPT" ]
1142+ },
1143+ {
1144+ "check" : ["sudo" , "iptables" , "-C" , "FORWARD" , "-i" , internet_device , "-o" , hotspot_device , "-j" , "ACCEPT" ],
1145+ "add" : ["sudo" , "iptables" , "-A" , "FORWARD" , "-i" , internet_device , "-o" , hotspot_device , "-j" , "ACCEPT" ]
1146+ }
1147+ ]
1148+ for rule in rules :
1149+ logger .info ("Checking: %s" , " " .join (rule ["check" ]))
1150+ check_result = subprocess .run (rule ["check" ], capture_output = True , text = True )
1151+ if check_result .returncode == 0 :
1152+ logger .info ("Rule already exists: %s" , " " .join (rule ['add' ]))
1153+ continue
1154+ add_result = subprocess .run (rule ["add" ], capture_output = True , text = True )
1155+ if add_result .returncode == 0 :
1156+ logger .info ("Added rule: %s" , " " .join (rule ["add" ]))
1157+ else :
1158+ logger .error ("Failed to add rule: %s\n %s" , " " .join (rule ["add" ]), add_result .stderr )
1159+
11641160
11651161if __name__ == '__main__' :
11661162
@@ -1169,12 +1165,14 @@ def handle_start_hotspot(hotspot_name):
11691165 # Networking & Security
11701166 net_group = parser .add_argument_group ('Networking & Security' )
11711167 net_group .add_argument ('--start-hotspot' , type = str , default = '' , help = 'Start the given hotspot using nmcli before domain setup' )
1168+ net_group .add_argument ('--internet-device' , type = str , default = '' , help = 'Network interface providing internet connectivity (e.g., wlan0)' )
1169+ net_group .add_argument ('--hotspot-device' , type = str , default = '' , help = 'Network interface for the hotspot (e.g., wlan1)' )
11721170 net_group .add_argument ('--ssl' , action = 'store_true' , help = 'Enable SSL (requires --chain and --key)' )
11731171 net_group .add_argument ('--chain' , type = str , default = None , help = 'SSL chain/cert file (fullchain.pem or cert.pem)' )
11741172 net_group .add_argument ('--key' , type = str , default = None , help = 'SSL private key file (privkey.pem)' )
11751173 net_group .add_argument ('--port' , type = int , default = 5000 , help = 'Port to run the server on (default: 5000)' )
11761174 net_group .add_argument ('--remap-ssl-port' , action = 'store_true' , help = 'Remap ports so that users can access the server on the default HTTPS port. Invokes iptables and sudo!' )
1177- net_group .add_argument ('--domain' , type = str , default = '' , help = 'Setup a domain to hotspot IP mapping via NetworkManager/dnsmasq, requires sudo' )
1175+ net_group .add_argument ('--domain' , type = str , default = 'localhost ' , help = 'Setup a domain to hotspot IP mapping via NetworkManager/dnsmasq, requires sudo' )
11781176
11791177 # UltraStar Deluxe Integration
11801178 usdx_group = parser .add_argument_group ('UltraStar Deluxe Integration' )
@@ -1194,13 +1192,17 @@ def handle_start_hotspot(hotspot_name):
11941192
11951193 logging .basicConfig (filename = 'virtual-microphone.log' , level = logging .INFO if not args .debug else logging .DEBUG )
11961194
1195+ # Run iptables forwarding if both devices are provided
1196+ if args .internet_device and args .hotspot_device :
1197+ setup_iptables_forwarding (args .internet_device , args .hotspot_device )
1198+
11971199 if args .start_hotspot :
11981200 handle_start_hotspot (args .start_hotspot )
11991201
12001202 if args .set_inputs :
12011203 initialize_record_section ()
12021204
1203- if args .domain != '' :
1205+ if args .domain != 'localhost ' :
12041206 setup_domain_hotspot_mapping (args .domain )
12051207
12061208 WebRTCMicrophoneManager ()
@@ -1265,6 +1267,7 @@ def stale_cleanup_loop():
12651267
12661268 # Build/update song index at startup
12671269 try :
1270+ print ("Scanning songs and building index..." )
12681271 scan_songs_and_build_index (find_root = args .usdx_dir )
12691272 except Exception :
12701273 logger .exception ('Error scanning songs at startup' )
@@ -1278,7 +1281,7 @@ def stale_cleanup_loop():
12781281 # Truncate/create the file
12791282 with open (upl_path , 'w' , encoding = 'utf-8' ) as fh :
12801283 fh .truncate (0 )
1281- logger . info ( 'Initialized playlist at %s' , upl_path )
1284+ print ( f 'Initialized playlist { upl_path } ' )
12821285 except Exception :
12831286 logger .exception ('Failed to create/truncate playlist file' )
12841287
@@ -1296,21 +1299,23 @@ def stale_cleanup_loop():
12961299 exe_path = os .path .abspath (os .path .join (args .usdx_dir , "ultrastardx" ))
12971300 cwd_path = os .path .abspath (args .usdx_dir )
12981301 subprocess .Popen ([exe_path ], cwd = cwd_path )
1299- print ("UltraStar Deluxe launched." )
1302+ logger . info ("UltraStar Deluxe launched." )
13001303 except Exception as e :
1301- print ("Failed to launch UltraStar Deluxe:" , e )
1304+ logger . error ("Failed to launch UltraStar Deluxe: %s " , e )
13021305
13031306 # SSL context handling
13041307 ssl_context = None
13051308 if args .ssl :
13061309 if args .chain and args .key :
13071310 ssl_context = (args .chain , args .key )
1311+
13081312 if ssl_context :
1309- print ( f "Starting SmartMicrophone server with SSL on port { port } ..." )
1310- if port == 443 :
1311- print (f"Access the server at: https://<server-ip> /" )
1313+ logger . info ( "Starting SmartMicrophone server with SSL on port %d ..." , port )
1314+ if port == 443 or args . remap_ssl_port :
1315+ print (f"Access the server at: https://{ args . domain } /" )
13121316 else :
1313- print (f"Access the server at: https://<server-ip> :{ port } /" )
1317+ print (f"Access the server at: https://{ args . domain } :{ port } /" )
13141318 app .run (host = '0.0.0.0' , port = port , debug = args .debug , use_reloader = False , ssl_context = ssl_context )
13151319 else :
13161320 app .run (host = '0.0.0.0' , port = port , debug = args .debug , use_reloader = False )
1321+ print (f"Access the server at: https://{ args .domain } :{ port } /" )
0 commit comments