From d21f838e98fc69eef383a97730d66e408acfb5d2 Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Sat, 17 May 2025 15:47:24 +0800 Subject: [PATCH] update: several improvements --- src/boot.py | 70 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/src/boot.py b/src/boot.py index cb7d5b5..3cb4e10 100755 --- a/src/boot.py +++ b/src/boot.py @@ -4,8 +4,8 @@ """ Syncplay Bootstrap using to convert redesigned parameter fields into arguments that are non-intrusive to Syncplay Server. It supports command line arguments, -environment variables, JSON / YAML / TOML configuration input, and process them -according to priority. +environment variables, JSON / YAML / TOML configuration input, and processes +them according to priority. The command line parameters of Syncplay server are not convenient for container startup, especially for scenarios that require specified file, which can easily @@ -91,12 +91,12 @@ DESC = { 'listen_ipv6': ('ADDR', 'listening address of ipv6'), } +ARG_OPTS: dict[str, dict] = {} # for loading cli arguments + ENV_OPTS: dict[str, type] = {} # for loading env variables CFG_OPTS: dict[str, tuple[type, bool]] = {} # for loading configure file -ARG_OPTS: dict[str, dict[str, type | str]] = {} # for loading command line arguments - def debug_msg(prefix: str, message: Any) -> None: """ Output debug message. """ @@ -104,7 +104,7 @@ def debug_msg(prefix: str, message: Any) -> None: print(f'\033[33m{prefix}\033[0m -> \033[90m{message}\033[0m', file=sys.stderr) -def build_opts() -> None: +def init_opts() -> None: """ Build syncplay formatting options. """ for name, field in SyncplayOptions.__annotations__.items(): field_t, is_list = field.__args__[0], False @@ -148,10 +148,11 @@ def load_from_env() -> SyncplayOptions: def load_from_args() -> SyncplayOptions: """ Load syncplay options from command line arguments. """ - def __build_args(name: str) -> list[str]: - match name := name.replace('_', '-'): - case 'port': return ['-p', f'--{name}'] - case _: return [f'--{name}'] + def __build_args(opt: str) -> list[str]: + match opt := opt.replace('_', '-'): + case 'port': return ['-p', f'--{opt}'] + case 'motd': return ['-m', f'--{opt}'] + case _: return [f'--{opt}'] parser = argparse.ArgumentParser(description='Syncplay Docker Bootstrap') for name, opts in ARG_OPTS.items(): @@ -159,7 +160,13 @@ def load_from_args() -> SyncplayOptions: args = parser.parse_args(sys.argv[1:]) debug_msg('Command line arguments', args) - return {x: y for x, y in vars(args).items() if not (y is None or y is False)} + + options: SyncplayOptions = {} + for arg, value in vars(args).items(): + if value is None or value is False: + continue + options[arg] = value + return options def load_from_config(path: str) -> SyncplayOptions: @@ -174,7 +181,7 @@ def load_from_config(path: str) -> SyncplayOptions: elif path.endswith('.toml'): return tomllib.loads(content) else: - return yaml.safe_load(content) # assume yaml format + return yaml.safe_load(content) # assume YAML format assert type(config := __load_file()) is dict debug_msg('Configure content', config) @@ -185,14 +192,32 @@ def load_from_config(path: str) -> SyncplayOptions: if value is not None: if is_list: assert type(value) is list - assert all(type(x) == field_t for x in value) + assert all(type(x) is field_t for x in value) else: - assert type(value) == field_t + assert type(value) is field_t options[key] = value return options -def convert(opts: SyncplayOptions) -> list[str]: +def load_opts() -> SyncplayOptions: + """ Combine syncplay options from multiple source. """ + env_opts = load_from_env() + cli_opts = load_from_args() + cfg_opts = load_from_config((env_opts | cli_opts).get('config', 'config.yml')) + + debug_msg('Environment options', env_opts) + debug_msg('Command line options', cli_opts) + debug_msg('Configure file options', cfg_opts) + + final_opts: SyncplayOptions = {} + for opt, value in (env_opts | cfg_opts | cli_opts).items(): + if type(value) is not bool or value: + final_opts[opt] = value + debug_msg('Bootstrap final options', final_opts) + return final_opts + + +def sp_convert(opts: SyncplayOptions) -> list[str]: """ Construct the startup arguments for syncplay server. """ def __temp_file(file: str, content: str) -> str: @@ -234,7 +259,7 @@ def convert(opts: SyncplayOptions) -> list[str]: rooms = '\n'.join(opts['permanent_rooms']) args += ['--permanent-rooms-file', __temp_file('rooms.list', rooms)] - if 'listen_ipv4' in opts and 'listen_ipv6' in opts: + if 'listen_ipv4' in opts and 'listen_ipv6' in opts: # dual stack args += ['--interface-ipv4', opts['listen_ipv4']] args += ['--interface-ipv6', opts['listen_ipv6']] elif 'listen_ipv4' in opts: @@ -245,19 +270,8 @@ def convert(opts: SyncplayOptions) -> list[str]: if __name__ == '__main__': - build_opts() - env_opts = load_from_env() - cli_opts = load_from_args() - cfg_opts = load_from_config((env_opts | cli_opts).get('config', 'config.yml')) - - debug_msg('Environment options', env_opts) - debug_msg('Command line options', cli_opts) - debug_msg('Configure file options', cfg_opts) - - final_opts = {x: y for x, y in (env_opts | cfg_opts | cli_opts).items() if y != False} - debug_msg('Bootstrap final options', final_opts) - - sys.argv = ['syncplay'] + convert(final_opts) + init_opts() + sys.argv = ['syncplay'] + sp_convert(load_opts()) debug_msg('Syncplay startup arguments', sys.argv) from syncplay import ep_server