"""Add domain servers to nginx.conf executable script.""" from __future__ import annotations import argparse import os from dataclasses import dataclass, field, fields from typing import Any import jinja2 import yaml DEFAULT_HTTP_PORT = 80 DEFAULT_HTTPS_PORT = 80 @dataclass class Server: """Server entry for nginx.conf file.""" name: str all_names: str | None = None proxy_pass: str | None = None certificate_dir: str | None = None http_port: int = None https_port: int = None https_custom_params: str = None server_options: list[str] = field(default_factory=list) location_options: list[str] = field(default_factory=list) def __post_init__(self) -> None: if self.all_names is None: self.all_names = self.name if self.http_port is None: self.http_port = DEFAULT_HTTP_PORT if self.https_port is None: self.https_port = DEFAULT_HTTPS_PORT if self.https_custom_params is None: self.https_custom_params = "" def to_dict(self) -> dict: """Convert server class to dict for nginx.conf.j2 jinja2-template""" return {field.name: getattr(self, field.name) for field in fields(self)} @dataclass class CLIParams: """add_servers CLI parameters""" nginx_template: str domains_list_txt: str servers_config: str default_http_port: str default_https_port: 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( "--default-http-port", required=False, default=80, help="Default port for https protocol", ) parser.add_argument( "--default-https-port", required=False, default=443, help="Default port for https protocol", ) 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() global DEFAULT_HTTP_PORT DEFAULT_HTTP_PORT = args.default_http_port global DEFAULT_HTTPS_PORT DEFAULT_HTTPS_PORT = args.default_https_port 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] = [] resolver: str = "127.0.0.1" acme_challenge_location: str | None = None 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", resolver) acme_challenge_location = 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, all_names=params.get( "all_names", None if "*" not in server_name else f"{server_name} {server_name.replace('*.', '', 1)}", ), proxy_pass=params.get("proxy_pass"), certificate_dir=_get_certificate_path( http_only=args.http_only, domains_with_certs=domains_with_certs, base_certs_path=args.certificates_path, server_name=params.get("certificate_name") or server_name, ), https_custom_params=params.get("https_custom_params"), http_port=params.get("http_port"), https_port=params.get("https_port"), server_options=params.get("server_options", []), location_options=params.get("location_options", []), ) ) for domain in domains_with_certs: if not any( ( (server.all_names is None and domain == server.name) or ( server.all_names is not None and f" {domain}" in server.all_names or server.all_names.startswith(domain) ) ) 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()