| 
						
						
						
					 | 
					@ -1,8 +1,6 @@ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					#!/usr/bin/env python3 | 
					 | 
					 | 
					#!/usr/bin/env python3 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					# -*- coding: utf-8 -*- | 
					 | 
					 | 
					# -*- coding: utf-8 -*- | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					# from __future__ import annotations | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					import os | 
					 | 
					 | 
					import os | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					import sys | 
					 | 
					 | 
					import sys | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					import yaml | 
					 | 
					 | 
					import yaml | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -14,10 +12,17 @@ from syncplay import ep_server | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					class SyncplayBoot: | 
					 | 
					 | 
					class SyncplayBoot: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    """ Handle Syncplay bootstrap arguments. """ | 
					 | 
					 | 
					    """ Handle Syncplay bootstrap arguments. """ | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					    def __debug(self, msg: str) -> None: | 
					 | 
					 | 
					    def __debug(self, prefix: str, message: Any) -> None: | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					        """ Print out debug information. """ | 
					 | 
					 | 
					        """ Print out debug information. """ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        if self.__debug_mode: | 
					 | 
					 | 
					        if self.__debug_mode: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					            print(f'\033[90m{msg}\033[0m', file=sys.stderr) | 
					 | 
					 | 
					            print(f'\033[33m{prefix}\033[0m -> \033[90m{message}\033[0m', file=sys.stderr) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    def __temp_file(self, file: str, content: str) -> str: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        """ Create and save content to temporary files. """ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        file = os.path.join(self.__temp_dir, file) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        with open(file, 'w', encoding='utf-8') as fp: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					            fp.write(content) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        return file | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    def __build_parser(self) -> Generator: | 
					 | 
					 | 
					    def __build_parser(self) -> Generator: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        """ Build arguments parser for Syncplay bootstrap. """ | 
					 | 
					 | 
					        """ Build arguments parser for Syncplay bootstrap. """ | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					@ -48,33 +53,32 @@ class SyncplayBoot: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            yield action.dest, opt_type, is_list | 
					 | 
					 | 
					            yield action.dest, opt_type, is_list | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    def __init__(self, args: list[str], config: dict[str, Any], | 
					 | 
					 | 
					    def __init__(self, args: list[str], config: dict[str, Any], | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					                 work_dir: str, cert_dir: str, debug_mode: bool = False): | 
					 | 
					 | 
					                 cert_dir: str, temp_dir: str, work_dir: str, debug_mode: bool = False): | 
				
			
			
				
				
			
		
	
		
		
			
				
					 | 
					 | 
					        self.__work_dir = work_dir | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        self.__cert_dir = cert_dir | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					        self.__debug_mode = debug_mode | 
					 | 
					 | 
					        self.__debug_mode = debug_mode | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					        self.__cert_dir, self.__temp_dir, self.__work_dir = cert_dir, temp_dir, work_dir | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        self.__options = [x for x in self.__build_options()]  # list[(NAME, TYPE, IS_LIST)] | 
					 | 
					 | 
					        self.__options = [x for x in self.__build_options()]  # list[(NAME, TYPE, IS_LIST)] | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Bootstrap options -> {self.__options}\n') | 
					 | 
					 | 
					        self.__debug('Bootstrap options', self.__options) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        env_opts = self.__load_from_env() | 
					 | 
					 | 
					        env_opts = self.__load_from_env() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Environment options -> {env_opts}\n') | 
					 | 
					 | 
					        self.__debug('Environment options', env_opts) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					        cfg_opts = self.__load_from_config(config) | 
					 | 
					 | 
					        cfg_opts = self.__load_from_config(config) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Configure file options -> {cfg_opts}\n') | 
					 | 
					 | 
					        self.__debug('Configure file options', cfg_opts) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					        cli_opts = self.__load_from_args(args) | 
					 | 
					 | 
					        cli_opts = self.__load_from_args(args) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Command line options -> {cli_opts}\n') | 
					 | 
					 | 
					        self.__debug('Command line options', cli_opts) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        self.__opts = env_opts | cfg_opts | cli_opts | 
					 | 
					 | 
					        self.__opts = env_opts | cfg_opts | cli_opts | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Bootstrap final options -> {self.__opts}\n') | 
					 | 
					 | 
					        self.__debug('Bootstrap final options', self.__opts) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    def __load_from_args(self, raw_args: list[str]) -> dict[str, Any]: | 
					 | 
					 | 
					    def __load_from_args(self, raw_args: list[str]) -> dict[str, Any]: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        """ Loading options from command line arguments. """ | 
					 | 
					 | 
					        """ Loading options from command line arguments. """ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        args = self.__parser.parse_args(raw_args) | 
					 | 
					 | 
					        args = self.__parser.parse_args(raw_args) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Command line arguments -> {args}') | 
					 | 
					 | 
					        self.__debug('Command line arguments', args) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					        arg_filter = lambda x: x is not None and x is not False | 
					 | 
					 | 
					        arg_filter = lambda x: x is not None and x is not False | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        return {x: y for x, y in vars(args).items() if arg_filter(y)} | 
					 | 
					 | 
					        return {x: y for x, y in vars(args).items() if arg_filter(y)} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    def __load_from_config(self, config: dict[str, Any]) -> dict[str, Any]: | 
					 | 
					 | 
					    def __load_from_config(self, config: dict[str, Any]) -> dict[str, Any]: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        """ Loading options from configure file. """ | 
					 | 
					 | 
					        """ Loading options from configure file. """ | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Configure file -> {config}') | 
					 | 
					 | 
					        self.__debug('Configure file', config) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					        options = {x[0].replace('_', '-'): x[0] for x in self.__options} | 
					 | 
					 | 
					        options = {x[0].replace('_', '-'): x[0] for x in self.__options} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        return {options[x]: config[x] for x in options if x in config} | 
					 | 
					 | 
					        return {options[x]: config[x] for x in options if x in config} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -88,25 +92,17 @@ class SyncplayBoot: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            elif opt_type is bool: | 
					 | 
					 | 
					            elif opt_type is bool: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					                return opt_field, opt_raw.upper() in ['ON', 'TRUE'] | 
					 | 
					 | 
					                return opt_field, opt_raw.upper() in ['ON', 'TRUE'] | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Environment variables -> {os.environ}') | 
					 | 
					 | 
					        self.__debug('Environment variables', os.environ) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					        options = {x.upper(): (x, t) for x, t, is_list in self.__options if not is_list}  # filter non-list options | 
					 | 
					 | 
					        options = {x.upper(): (x, t) for x, t, is_list in self.__options if not is_list}  # filter non-list options | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        return dict([__convert(os.environ[x], *y) for x, y in options.items() if x in os.environ]) | 
					 | 
					 | 
					        return dict([__convert(os.environ[x], *y) for x, y in options.items() if x in os.environ]) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    @staticmethod | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    def __temp_file(file: str, content: str) -> str: | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        """ Create and save content to temporary files. """ | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        file = os.path.join('/tmp/', file) | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        with open(file, 'w') as fp: | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            fp.write(content) | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        return file | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    def release(self) -> list[str]: | 
					 | 
					 | 
					    def release(self) -> list[str]: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        """ Construct the startup arguments for syncplay server. """ | 
					 | 
					 | 
					        """ Construct the startup arguments for syncplay server. """ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        args = ['--port', str(self.__opts.get('port', 8999))] | 
					 | 
					 | 
					        args = ['--port', str(self.__opts.get('port', 8999))] | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        if 'password' in self.__opts: | 
					 | 
					 | 
					        if 'password' in self.__opts: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            args += ['--password', self.__opts['password']] | 
					 | 
					 | 
					            args += ['--password', self.__opts['password']] | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        if 'motd' in self.__opts: | 
					 | 
					 | 
					        if 'motd' in self.__opts: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					            args += ['--motd-file', SyncplayBoot.__temp_file('motd.data', self.__opts['motd'])] | 
					 | 
					 | 
					            args += ['--motd-file', self.__temp_file('motd.data', self.__opts['motd'])] | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        salt = self.__opts.get('salt', None if 'random_salt' in self.__opts else '') | 
					 | 
					 | 
					        salt = self.__opts.get('salt', None if 'random_salt' in self.__opts else '') | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        if salt is not None: | 
					 | 
					 | 
					        if salt is not None: | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -128,7 +124,7 @@ class SyncplayBoot: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            args += ['--max-chat-message-length', str(self.__opts['max_chat_message'])] | 
					 | 
					 | 
					            args += ['--max-chat-message-length', str(self.__opts['max_chat_message'])] | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        if 'permanent_rooms' in self.__opts: | 
					 | 
					 | 
					        if 'permanent_rooms' in self.__opts: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            rooms = '\n'.join(self.__opts['permanent_rooms']) | 
					 | 
					 | 
					            rooms = '\n'.join(self.__opts['permanent_rooms']) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					            args += ['--permanent-rooms-file', SyncplayBoot.__temp_file('rooms.list', rooms)] | 
					 | 
					 | 
					            args += ['--permanent-rooms-file', self.__temp_file('rooms.list', rooms)] | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        if 'listen_ipv4' in self.__opts and 'listen_ipv6' in self.__opts: | 
					 | 
					 | 
					        if 'listen_ipv4' in self.__opts and 'listen_ipv6' in self.__opts: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            args += ['--interface-ipv4', self.__opts['listen_ipv4']] | 
					 | 
					 | 
					            args += ['--interface-ipv4', self.__opts['listen_ipv4']] | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -138,19 +134,20 @@ class SyncplayBoot: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					        elif 'listen_ipv6' in self.__opts: | 
					 | 
					 | 
					        elif 'listen_ipv6' in self.__opts: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					            args += ['--ipv6-only', '--interface-ipv6', self.__opts['listen_ipv6']] | 
					 | 
					 | 
					            args += ['--ipv6-only', '--interface-ipv6', self.__opts['listen_ipv6']] | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					        self.__debug(f'Syncplay startup arguments -> {args}') | 
					 | 
					 | 
					        self.__debug('Syncplay startup arguments', args) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					        return args | 
					 | 
					 | 
					        return args | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					def syncplay_boot() -> None: | 
					 | 
					 | 
					def syncplay_boot() -> None: | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    """ Bootstrap the syncplay server. """ | 
					 | 
					 | 
					    """ Bootstrap the syncplay server. """ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					    temp_dir = os.environ.get('TEMP_DIR', '/tmp') | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    work_dir = os.environ.get('WORK_DIR', '/data') | 
					 | 
					 | 
					    work_dir = os.environ.get('WORK_DIR', '/data') | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    cert_dir = os.environ.get('CERT_DIR', '/certs') | 
					 | 
					 | 
					    cert_dir = os.environ.get('CERT_DIR', '/certs') | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    config_file = os.environ.get('CONFIG', 'config.yml') | 
					 | 
					 | 
					    config_file = os.environ.get('CONFIG', 'config.yml') | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    debug_mode = os.environ.get('DEBUG', '').upper() in ['ON', 'TRUE'] | 
					 | 
					 | 
					    debug_mode = os.environ.get('DEBUG', '').upper() in ['ON', 'TRUE'] | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					    config = yaml.safe_load(open(config_file).read()) if os.path.exists(config_file) else {} | 
					 | 
					 | 
					    config = yaml.safe_load(open(config_file).read()) if os.path.exists(config_file) else {} | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					    bootstrap = SyncplayBoot(sys.argv[1:], config, work_dir, cert_dir, debug_mode) | 
					 | 
					 | 
					    bootstrap = SyncplayBoot(sys.argv[1:], config, cert_dir, temp_dir, work_dir, debug_mode) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					    sys.argv = ['syncplay'] + bootstrap.release() | 
					 | 
					 | 
					    sys.argv = ['syncplay'] + bootstrap.release() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
					
  |