"""Add domain servers to nginx.conf executable script.""" from __future__ import annotations import argparse import os from dataclasses import dataclass, field from typing import Any import jinja2 import yaml @dataclass class Server: """Server entry for nginx.conf file.""" name: str all_names: str | None = None redirect: str | None = None certificate_dir: str | None = None port: int = None ssl_port: int = None server_options: list[str] = field(default_factory=list) location_options: list[str] = field(default_factory=list) certificates_path: str = "/etc/letsencrypt/live" def __post_init__(self) -> None: if self.server_options is None: self.server_options = [] if self.location_options is None: self.location_options = [] if self.all_names is None: self.all_names = self.name if self.port is None: self.port = 80 if self.ssl_port is None: self.ssl_port = 443 def to_dict(self) -> dict: """Convert server class to dict for nginx.conf.j2 jinja2-template""" return { "name": self.name, "all_names": self.all_names, "redirect": self.redirect, "server_options": self.server_options, "location_options": self.location_options, "certificate_dir": self.certificate_dir, "port": self.port, "ssl_port": self.ssl_port, } @dataclass class CLIParams: """add_servers CLI parameters""" nginx_template: str domains_list_txt: str servers_config: str certificates_path: str http_only: bool output: str | None def main() -> None: """Parse arguments and add domains to nginx.conf""" parser = argparse.ArgumentParser("add-servers", description="Add domain servers to a given nginx.conf file") parser.add_argument("--nginx-template", "-f", required=True, help="Path to nginx.conf.j2 template file") parser.add_argument( "--domains_list_txt", "-d", required=True, help="Path to file with domains which have ssl certificates", ) parser.add_argument( "--servers-config", "-s", required=False, default=None, help="Path to servers configuration yaml file", ) parser.add_argument( "--certificates-path", "-c", required=False, default="/etc/letsencrypt/live", help="Path to a directory containing certificates", ) parser.add_argument( "--http-only", action="store_true", help="Remove certificates usage from servers section", ) parser.add_argument("--output", "-o", help="Path to nginx.conf output file") args: CLIParams = parser.parse_args() if args.domains_list_txt is not None: with open(args.domains_list_txt, "r", encoding="utf-8") as file: domains_with_certs = [ domain.strip() for domain in file.readlines() if domain.strip() != "" and not domain.startswith("#") ] else: domains_with_certs = [] nginx_servers: list[Server] = [] if args.servers_config is not None: with open(args.servers_config, "r", encoding="utf-8") as file: data: dict = yaml.safe_load(file) resolver: str = data.get("resolver", "127.0.0.1") acme_challenge_location: str | None = data.get("acme_challenge_location") servers: dict[str, dict[str, Any]] = data["servers"] for server_name, params in servers.items(): nginx_servers.append( Server( name=(server_name if "*" not in server_name else f"{server_name} {server_name.replace('*.', '')}"), all_names=params.get("all_names"), redirect=params.get("redirect"), certificate_dir=_get_certificate_path( args.http_only, domains_with_certs, args.certificates_path, server_name ), port=params.get("port"), ssl_port=params.get("ssl_port"), server_options=params.get("server_options"), location_options=params.get("location_options"), ) ) for domain in domains_with_certs: if not any( ( domain == server.name or (domain == f"*.{server.name[server.name.find('.') + 1:]}" if "." in server.name else False) ) for server in nginx_servers ): nginx_servers.append(Server(name=domain)) with open(args.nginx_template, "r", encoding="utf-8") as file: template = jinja2.Environment().from_string(file.read()) result = template.render( resolver=resolver, acme_challenge_location=acme_challenge_location, servers=[server.to_dict() for server in nginx_servers], ) print(result) if args.output is not None: with open(args.output, "w", encoding="utf-8") as file: file.write(result) def _get_certificate_path( http_only: bool, domains_with_certs: list[str], base_certs_path: str, server_name: str ) -> str | None: if http_only: return None if server_name in domains_with_certs: return os.path.join(base_certs_path, server_name.replace("*.", "")) if "." in server_name and f"*.{server_name[server_name.find('.') + 1:]}" in domains_with_certs: return os.path.join(base_certs_path, server_name[server_name.find(".") + 1 :]) return None if __name__ == "__main__": main()