Files
ssl-nginx/nginx/add_servers.py
kanootoko 16bc1c0db7 update 2024-04-22
Changes:
- add jinja2 template engine instead of string-replacing
- fix certbot cron usage
- replace json servers configuration with yaml
2024-04-22 21:53:47 +03:00

170 lines
5.5 KiB
Python

"""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()