diff options
author | crupest <crupest@outlook.com> | 2022-11-19 13:00:13 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2022-11-19 13:00:13 +0800 |
commit | f775f4c76691fc61b2993d81d9f1b4690e76b141 (patch) | |
tree | 9fe248e5379c66cbaa740243bebc04bb7df7cd29 /tool/modules | |
parent | 1562a317e147954c5618ecdccbfd10c944bf81ce (diff) | |
download | crupest-f775f4c76691fc61b2993d81d9f1b4690e76b141.tar.gz crupest-f775f4c76691fc61b2993d81d9f1b4690e76b141.tar.bz2 crupest-f775f4c76691fc61b2993d81d9f1b4690e76b141.zip |
No caddy, only nginx and certbot.
Diffstat (limited to 'tool/modules')
-rw-r--r-- | tool/modules/configfile.py | 45 | ||||
-rwxr-xr-x | tool/modules/nginx.py | 72 | ||||
-rw-r--r-- | tool/modules/path.py | 13 | ||||
-rw-r--r-- | tool/modules/template.py | 32 |
4 files changed, 162 insertions, 0 deletions
diff --git a/tool/modules/configfile.py b/tool/modules/configfile.py new file mode 100644 index 0000000..49f2200 --- /dev/null +++ b/tool/modules/configfile.py @@ -0,0 +1,45 @@ +import os.path +from .path import config_file_path + +config_file_exist = os.path.isfile(config_file_path) + + +def parse_config(str: str) -> dict: + config = {} + for line_number, line in enumerate(str.splitlines()): + # check if it's a comment + if line.startswith("#"): + continue + # check if there is a '=' + if line.find("=") == -1: + raise ValueError( + f"Invalid config string. Please check line {line_number + 1}. There is even no '='!") + # split at first '=' + key, value = line.split("=", 1) + key = key.strip() + value = value.strip() + config[key] = value + return config + + +def get_domain() -> str: + if not config_file_exist: + raise ValueError("Config file not found!") + with open(config_file_path) as f: + config = parse_config(f.read()) + if "CRUPEST_DOMAIN" not in config: + raise ValueError("Domain not found in config file!") + return config["CRUPEST_DOMAIN"] + + +def config_to_str(config: dict) -> str: + return "\n".join([f"{key}={value}" for key, value in config.items()]) + + +def print_config(console, config: dict) -> None: + for key, value in config.items(): + console.print(f"[magenta]{key}[/] = [cyan]{value}") + + +__all__ = ["config_file_exist", "parse_config", + "get_domain", "config_to_str", "print_config"] diff --git a/tool/modules/nginx.py b/tool/modules/nginx.py new file mode 100755 index 0000000..6cb918c --- /dev/null +++ b/tool/modules/nginx.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 + +from .template import Template +from .path import nginx_template_dir +import json +import jsonschema +import os +import os.path +import shutil + +with open(os.path.join(nginx_template_dir, 'server.json')) as f: + server = json.load(f) + +with open(os.path.join(nginx_template_dir, 'server.schema.json')) as f: + schema = json.load(f) + +jsonschema.validate(server, schema) + +root_template = Template(os.path.join( + nginx_template_dir, 'root.conf.template')) +static_file_template = Template(os.path.join( + nginx_template_dir, 'static-file.conf.template')) +reverse_proxy_template = Template(os.path.join( + nginx_template_dir, 'reverse-proxy.conf.template')) + + +def nginx_config_gen(domain: str, dest: str) -> None: + if not os.path.isdir(dest): + raise ValueError('dest must be a directory') + # copy ssl.conf and https-redirect.conf which need no variable substitution + for filename in ['ssl.conf', 'https-redirect.conf']: + src = os.path.join(nginx_template_dir, filename) + dst = os.path.join(dest, filename) + shutil.copyfile(src, dst) + config = {"CRUPEST_DOMAIN": domain} + # generate root.conf + with open(os.path.join(dest, f'{domain}.conf'), 'w') as f: + f.write(root_template.generate(config)) + # generate nginx config for each site + sites: list = server["sites"] + for site in sites: + if site["type"] not in ['static-file', 'reverse-proxy']: + continue + subdomain = site["subdomain"] + local_config = config.copy() + local_config['CRUPEST_NGINX_SUBDOMAIN'] = subdomain + match site["type"]: + case 'static-file': + template = static_file_template + local_config['CRUPEST_NGINX_ROOT'] = site["root"] + case 'reverse-proxy': + template = reverse_proxy_template + local_config['CRUPEST_NGINX_UPSTREAM_NAME'] = site["upstream"]["name"] + local_config['CRUPEST_NGINX_UPSTREAM_SERVER'] = site["upstream"]["server"] + with open(os.path.join(dest, f'{subdomain}.{domain}.conf'), 'w') as f: + f.write(template.generate(local_config)) + + +def list_domains(domain: str) -> list[str]: + return [domain, *server.sites.map(lambda s: f"{s.subdomain}.{domain}")] + + +def certbot_command_gen(domain: str, action, test=False) -> str: + domains = list_domains(domain) + match action: + case 'create': + # create with standalone mode + return f'docker run -it --name certbot -v "./data/certbot/certs:/etc/letsencrypt" -v "./data/certbot/data:/var/lib/letsencrypt" certbot/certbot certonly --standalone -d {" -d ".join(domains)}{ " --test-cert" if test else "" }' + case 'renew': + # renew with webroot mode + return f'docker run -it --name certbot -v "./data/certbot/certs:/etc/letsencrypt" -v "./data/certbot/data:/var/lib/letsencrypt" -v "./data/certbot/webroot:/var/www/certbot" certbot/certbot renew --webroot -w /var/www/certbot' + raise ValueError('Invalid action') diff --git a/tool/modules/path.py b/tool/modules/path.py new file mode 100644 index 0000000..94adc27 --- /dev/null +++ b/tool/modules/path.py @@ -0,0 +1,13 @@ +import os.path + +script_dir = os.path.relpath(os.path.dirname(__file__)) +project_dir = os.path.normpath(os.path.join(script_dir, "../../")) +template_dir = os.path.join(project_dir, "template") +nginx_template_dir = os.path.join(template_dir, "nginx") +data_dir = os.path.join(project_dir, "data") +tool_dir = os.path.join(project_dir, "tool") +config_file_path = os.path.join(data_dir, "config") +nginx_config_dir = os.path.join(project_dir, "nginx-config") + +__all__ = ["script_dir", "project_dir", "template_dir", + "nginx_template_dir", "data_dir", "config_file_path", "tool_dir", "nginx_config_dir"] diff --git a/tool/modules/template.py b/tool/modules/template.py new file mode 100644 index 0000000..15238ea --- /dev/null +++ b/tool/modules/template.py @@ -0,0 +1,32 @@ +import os.path +import re + + +class Template: + def __init__(self, template_path: str, var_prefix: str = "CRUPEST"): + if len(var_prefix) != 0 and re.fullmatch(r"^[a-zA-Z_][a-zA-Z0-9_]*$", var_prefix) is None: + raise ValueError("Invalid var prefix.") + self.template_path = template_path + self.template_name = os.path.basename( + template_path)[:-len(".template")] + with open(template_path, "r") as f: + self.template = f.read() + self.var_prefix = var_prefix + self.__var_regex = re.compile(r"\$(" + var_prefix + r"_[a-zA-Z0-9_]+)") + self.__var_brace_regex = re.compile( + r"\$\{\s*(" + var_prefix + r"_[a-zA-Z0-9_]+)\s*\}") + var_set = set() + for match in self.__var_regex.finditer(self.template): + var_set.add(match.group(1)) + for match in self.__var_brace_regex.finditer(self.template): + var_set.add(match.group(1)) + self.var_set = var_set + + def generate(self, config: dict[str, str]) -> str: + result = self.template + for var in self.var_set: + if var not in config: + raise ValueError(f"Missing config var {var}.") + result = result.replace("$" + var, config[var]) + re.sub(r"\$\{\s*" + var + r"\s*\}", config[var], result) + return result |