166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
"""main"""
|
||
import argparse
|
||
from pathlib import Path
|
||
import yaml
|
||
|
||
from src.certbot import CertbotConfigurator, parse_certbot, register_certbot
|
||
from src.logger import get_logger, run_cmd_with_log
|
||
from src.nginx import NginxConfig, NginxConfigurator
|
||
logger = get_logger(__name__)
|
||
|
||
# 配置常量
|
||
FILE_PATH = Path(__file__).parent
|
||
NETWORK_NAME = "nginx-net"
|
||
|
||
def validate_docker_network():
|
||
"""检查Docker网络是否存在"""
|
||
result = run_cmd_with_log(["docker", "network", "inspect", NETWORK_NAME], logger)
|
||
if result.returncode != 0:
|
||
logger.error("Docker网络 %s 不存在", NETWORK_NAME)
|
||
return False
|
||
return True
|
||
|
||
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
|
||
|
||
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='显示详细调试信息')
|
||
|
||
register_certbot(parser)
|
||
args = parser.parse_args()
|
||
parse_certbot(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()
|