__init__.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. # -*- coding:utf-8 -*-
  2. """
  3. Configuration loader for DDNS.
  4. This module handles loading configuration from command-line arguments,
  5. JSON configuration files, and environment variables.
  6. @author: NewFuture
  7. """
  8. import os
  9. import sys
  10. import logging
  11. from .cli import load_config as load_cli_config
  12. from .file import load_config as load_file_config, save_config
  13. from .env import load_config as load_env_config
  14. from .config import Config, split_array_string
  15. def _get_config_paths(config_paths):
  16. # type: (list[str] | None) -> list[str]
  17. """
  18. 获取配置文件路径列表,支持多个配置文件
  19. """
  20. if not config_paths:
  21. # Find config file in default locations
  22. for p in [
  23. "config.json",
  24. os.path.expanduser("~/.ddns/config.json"),
  25. os.path.expanduser("~/.ddns.json"),
  26. "/etc/ddns/config.json",
  27. "/etc/ddns.json",
  28. ]:
  29. if os.path.exists(p):
  30. return [p]
  31. return []
  32. # 验证所有路径都存在
  33. for config_path in config_paths:
  34. if not os.path.exists(config_path):
  35. sys.stderr.write("Config file `%s` does not exist!\n" % config_path)
  36. sys.stdout.write("Please check the path or use `--new-config` to create new one.\n")
  37. sys.exit(1)
  38. return config_paths
  39. def _setup_logging(cli_config, env_config, all_json_configs):
  40. # type: (dict, dict, list[dict]) -> logging.Logger
  41. """Setup logging configuration and return logger."""
  42. # Include global config from config file when there's only one config
  43. json_config = all_json_configs[0] if len(all_json_configs) == 1 else {}
  44. global_conf = Config(cli_config=cli_config, json_config=json_config, env_config=env_config)
  45. log_format = global_conf.log_format # type: str # type: ignore
  46. if log_format:
  47. # A custom log format is already set; no further action is required.
  48. pass
  49. elif global_conf.log_level < logging.INFO:
  50. # Override log format in debug mode to include filename and line number for detailed debugging
  51. log_format = "%(asctime)s %(levelname)s [%(name)s.%(funcName)s](%(filename)s:%(lineno)d): %(message)s"
  52. elif global_conf.log_level > logging.INFO:
  53. log_format = "%(asctime)s %(levelname)s: %(message)s"
  54. else:
  55. log_format = "%(asctime)s %(levelname)s [%(name)s]: %(message)s"
  56. logging.basicConfig(
  57. level=global_conf.log_level, format=log_format, datefmt=global_conf.log_datefmt, filename=global_conf.log_file
  58. )
  59. return logging.getLogger().getChild("config") # type: logging.Logger
  60. def _load_json_configs(config_paths):
  61. # type: (list[str]) -> list[dict]
  62. """Load all JSON configurations from config paths."""
  63. all_json_configs = []
  64. for config_path in config_paths:
  65. json_configs = load_file_config(config_path)
  66. if isinstance(json_configs, list):
  67. all_json_configs.extend(json_configs)
  68. else:
  69. all_json_configs.append(json_configs)
  70. # 如果没有找到任何配置文件或JSON配置,创建一个空配置
  71. return all_json_configs or [{}]
  72. def _validate_configs(configs, logger):
  73. # type: (list[Config], logging.Logger) -> None
  74. """Validate that all configs have DNS providers."""
  75. for i, conf in enumerate(configs):
  76. if not conf.dns:
  77. logger.critical(
  78. "No DNS provider specified in config %d! Please set `dns` in config or use `--dns` CLI option.", i + 1
  79. )
  80. sys.exit(2)
  81. def load_configs(description, version, date):
  82. # type: (str, str, str) -> list[Config]
  83. """
  84. Load and merge configuration from CLI, JSON, and environment variables.
  85. Supports multiple config files and array config formats.
  86. This function loads configuration from all three sources and returns a
  87. list of Config objects that provides easy access to merged configuration values.
  88. Args:
  89. description (str): The program description for the CLI parser.
  90. version (str): The program version for the CLI parser.
  91. date (str): The program release date for the CLI parser.
  92. Returns:
  93. list[Config]: A list of Config objects with merged configuration from all sources.
  94. """
  95. doc = """
  96. ddns [v{version}@{date}]
  97. (i) homepage or docs [文档主页]: https://ddns.newfuture.cc/
  98. (?) issues or bugs [问题和反馈]: https://github.com/NewFuture/DDNS/issues
  99. Copyright (c) NewFuture (MIT License)
  100. """.format(
  101. version=version, date=date
  102. )
  103. # Load CLI configuration first
  104. cli_config = load_cli_config(description, doc, version, date)
  105. env_config = load_env_config()
  106. # 获取配置文件路径列表
  107. config_paths = split_array_string(cli_config.get("config", env_config.get("config", [])))
  108. config_paths = _get_config_paths(config_paths)
  109. # 加载所有配置文件
  110. all_json_configs = _load_json_configs(config_paths)
  111. # 为每个JSON配置创建Config对象
  112. configs = [
  113. Config(cli_config=cli_config, json_config=json_config, env_config=env_config)
  114. for json_config in all_json_configs
  115. ]
  116. # 设置日志
  117. logger = _setup_logging(cli_config, env_config, all_json_configs)
  118. # 处理无配置情况 - inline _handle_no_config logic
  119. if len(cli_config) <= 1 and len(all_json_configs) == 1 and not all_json_configs[0] and not env_config:
  120. # 没有配置时生成默认配置文件
  121. logger.warning("[deprecated] auto gernerate config file will be deprecated in future versions.")
  122. logger.warning("usage:\n `ddns --new-config` to generate a new config.\n `ddns -h` for help.")
  123. default_config_path = config_paths[0] if config_paths else "config.json"
  124. save_config(default_config_path, cli_config)
  125. logger.info("No config file found, generated default config at `%s`.", default_config_path)
  126. sys.exit(1)
  127. # 记录配置加载情况
  128. if config_paths:
  129. logger.info("load config: %s", config_paths)
  130. else:
  131. logger.debug("No config file specified, using CLI and environment variables only.")
  132. # 仅在没有配置文件且开启debug时自动设置debug provider
  133. if not config_paths and cli_config.get("debug") and len(configs) == 1 and not configs[0].dns:
  134. configs[0].dns = "debug"
  135. # 验证每个配置都有DNS provider
  136. _validate_configs(configs, logger)
  137. return configs
  138. __all__ = [
  139. "load_configs",
  140. "Config",
  141. ]