Changes: - fix README.md old json format usage - rename "redirect" option to "proxy_pass" - move docker-compose.yml and nginx.conf.j2 to examples and add to .gitignore - fix situation when one domain from domains.txt and servers.yaml appeared twice in nginx.conf
181 lines
5.8 KiB
Python
181 lines
5.8 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
|
|
proxy_pass: 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,
|
|
"proxy_pass": self.proxy_pass,
|
|
"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] = []
|
|
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(
|
|
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(
|
|
(
|
|
(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()
|