"""main""" import subprocess import argparse from pathlib import Path import yaml from src.logger import get_logger from src.nginx import NginxConfig, NginxConfigurator logger = get_logger(__name__) # 配置常量 FILE_PATH = Path(__file__).parent DOCKER_COMPOSE_FILE = Path(FILE_PATH, "docker-compose.yml") NETWORK_NAME = "nginx-net" def validate_docker_network(): """检查Docker网络是否存在""" try: result = subprocess.run( ["docker", "network", "inspect", NETWORK_NAME], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True ) return result.returncode == 0 except subprocess.CalledProcessError: logger.error("Docker网络 %s 不存在", NETWORK_NAME) return False except Exception as e: logger.error("网络检查异常: %s", str(e), exc_info=True) return False def parse_compose_config(file_path: Path) -> list[NginxConfig]: """解析Docker Compose文件""" try: with open(file_path, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) services:dict = config['services'] # networks = config['networks'][NETWORK_NAME] # if not networks['external']: # raise ValueError("network 必须为external") nginx_configs:list[NginxConfig] = [] for name, service in services.items(): try: labels = service.get('labels') if labels is not None: n = labels.get('nginx_prefix_name') if n is not None: name = n conf = NginxConfig( name = f"{name}.zzyxyz.com", host = service['container_name'], port = service['expose'][0] ) if len(service['expose']) != 1: raise ValueError("expose 必须为一项") networks = service.get('networks') # TODO if networks is None or networks[0] != NETWORK_NAME: raise ValueError(f"networks 需要设置为{NETWORK_NAME}") # required_fields = ['com.lingma.nginx.domain', 'com.lingma.nginx.port'] # for field in required_fields: # if field not in labels: # raise ValueError(f"服务 {name} 缺少必要标签: {field}") nginx_configs.append(conf) except Exception as e: logger.error("解析服务 %s 配置失败: %s", name, str(e)) continue return nginx_configs except FileNotFoundError: logger.error("文件不存在: %s", file_path) return False except PermissionError: logger.error("无权限访问文件: %s", file_path) return False except yaml.YAMLError as e: logger.error("YAML解析错误: %s", str(e)) return False except Exception as e: logger.error("未知错误: %s", str(e), exc_info=True) return False def parse_yaml_config(file_path: Path) -> tuple[list[NginxConfig], list[NginxConfig]]: """解析conf.yml配置文件""" with open(file_path, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) inner_configs = [] for compose_file in config['inner']['compose_file']: inner_configs.extend(parse_compose_config(Path(compose_file))) outer_configs = [] for domain, service_config in config['outter']['services'].items(): outer_configs.append(NginxConfig( name=domain, host=service_config.get('host', config['outter']['host']), port=service_config['port'], web_socket_proxy=service_config.get('web_socket_proxy', False) )) return inner_configs, outer_configs def main(): """main""" parser = argparse.ArgumentParser(description="Nginx自动化配置工具", formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('-i', '--input', type=str, default='conf.yml', help='配置文件路径 (默认: conf.yml)') parser.add_argument('-t', '--type', choices=['all', 'inner', 'outter'], default='all', help='配置类型选择:\n' 'all - 同时处理内部和外部服务(默认)\n' 'inner - 仅处理Docker compose服务\n' 'outter - 仅处理外部服务') parser.add_argument('--no-reload', action='store_true', help='生成配置后禁用立即重载Nginx') parser.add_argument('--rollback', action='store_true', help='回滚到最近的有效配置') parser.add_argument('--dry-run', action='store_true', help='只生成配置不实际写入') parser.add_argument('--verbose', action='store_true', help='显示详细调试信息') args = parser.parse_args() # 初始化配置器 conf = NginxConfigurator() # 回滚操作 if args.rollback: if conf.rollback(): logger.info("回滚成功") return # 配置生成 try: if not validate_docker_network(): logger.error("请先创建Docker网络,否则nginx将会失效: docker network create %s", NETWORK_NAME) inner, outter = parse_yaml_config(Path(args.input)) # 根据类型选择配置 if args.type == 'inner': configs = inner elif args.type == 'outter': configs = outter else: configs = inner + outter logger.info("共生成 %d 项配置(内部:%d 外部:%d)", len(configs), len(inner), len(outter)) # 备份当前配置 conf.backup_config() # 预览模式 if args.dry_run: print(conf.gen_all_config(configs)) return # 写入配置 if conf.gen_and_save_config(configs): if not args.no_reload: conf.safe_reload() except Exception as e: logger.critical("流程异常: %s", str(e), exc_info=args.verbose) if __name__ == "__main__": main()