This commit is contained in:
bain 2023-11-04 23:02:34 +01:00
parent ef0ab2752b
commit 8d87c063cd
Signed by: bain
GPG key ID: 31F0F25E3BED0B9B
17 changed files with 577 additions and 314 deletions

2
.gitignore vendored
View file

@ -6,3 +6,5 @@ clusters.json
/nginx
/autossl
.env
ncc
/venv

8
build.sh Executable file
View file

@ -0,0 +1,8 @@
DIR=$(mktemp -d)
pip install -r requirements.txt --target="$DIR"
cp -r nginx_configurator "$DIR"
python3 -m zipapp -p "/bin/python3" -m "nginx_configurator.main:cli" -o ncc "$DIR"
rm -r "$DIR"

166
n_gen.py
View file

@ -1,166 +0,0 @@
import os
import json
import pyinputplus as pyip
from jinja2 import Environment, PackageLoader, select_autoescape
from dotenv import load_dotenv
import n_ssl
# Get clusters from json config
with open("clusters.json") as json_file:
CLUSTERS = json.load(json_file)["clusters"]
# Setup Jinja2
jin = Environment(loader=PackageLoader("n_gen"), autoescape=select_autoescape())
load_dotenv()
NGINX_DIR = os.getenv("NGINX_DIR")
# Go through config names and find highest config number. Increment by 1 and use as new ID
def get_conf_id(nginx_dir):
list_auto = os.listdir(nginx_dir + "/sites/auto")
list_custom = os.listdir(nginx_dir + "/sites/custom")
domain_list = list_auto + list_custom
last_id = 0
for dom in domain_list:
id = int(dom.split("-")[0])
if id > last_id:
last_id = id
new_id = last_id + 1
return new_id
def get_domains():
new_domain = True
domains = []
while new_domain:
domain = pyip.inputStr("Enter a full domain name: ")
domains.append(domain)
next_domain = pyip.inputYesNo("Do you want to add another domain? (y/n) ")
if next_domain == "no":
new_domain = False
return domains
def get_upstreams(clusters):
print("\nNow, we will select upstream server(s).")
if (
pyip.inputYesNo(
"Is the service located on existing upstream cluster (like Swarm)? (y/n) "
)
== "yes"
):
cluster_list = [d["name"] for d in clusters]
sel_cluster_name = pyip.inputMenu(cluster_list, lettered=True, blank=True)
cluster = [
element for element in clusters if element["name"] == sel_cluster_name
][0]
print("Selected cluster " + cluster["name"] + " with nodes:")
for node in cluster["nodes"]:
print(node)
return cluster["nodes"]
else:
new_upstream = True
upstreams = []
while new_upstream:
upstream = pyip.inputStr("Enter IPv4 address of one upstream server: ")
upstreams.append(upstream)
next_upstream = pyip.inputYesNo(
"Do you want to add another upstream server? (y/n) "
)
if next_upstream == "no":
new_upstream = False
return upstreams
def get_port():
return pyip.inputInt(
"\nEnter a port number for the upstream servers: ", min=81, max=65534
)
def get_proto():
print("\nEnter the upstream protocol (between service and reverse proxy)")
return pyip.inputMenu(["http://", "https://"], lettered=True)
def input_check(domains, upstreams, port, proto):
print("\n-----------------------------------------------")
print("You have entered following service information:")
print("Domains:")
for domain in domains:
print("\t" + domain)
print("Upstream servers with proto and port:")
for upstream in upstreams:
print("\t" + proto + upstream + ":" + str(port))
if pyip.inputYesNo("Is this information correct? (y/n) ") == "yes":
return True
else:
print("Sorry to hear that, please start again. Exiting")
exit()
def create_nginx_config(id, domains, upstreams, port, proto):
template = jin.get_template("nginx-site.conf")
return template.render(
id=id, domains=domains, upstreams=upstreams, port=port, proto=proto
)
def write_nginx_config(config, nginx_dir, domains, conf_id):
filename = str(conf_id) + "-" + domains[0] + ".conf"
path = nginx_dir + "/sites/auto/" + filename
with open(path, "w") as conf_file:
conf_file.write(config)
def create_ssl_config(conf_id):
template = jin.get_template("ssl.conf")
return template.render(id=conf_id)
def write_ssl_config(config, conf_id, nginx_dir):
filename = str(conf_id) + ".conf"
path = nginx_dir + "/ssl/" + filename
with open(path, "w") as conf_file:
conf_file.write(config)
def ssl_continue():
if (
pyip.inputYesNo(
"Do you want to prepare ssl certs and replicate the config? (y/n) "
)
== "yes"
):
n_ssl.main()
else:
print("Ok, you can run n_ssl.py to do it later.")
exit()
def main():
print("This script will generate nginx configuration and for new service.\n")
conf_id = get_conf_id(NGINX_DIR)
domains = get_domains()
upstreams = get_upstreams(CLUSTERS)
port = get_port()
proto = get_proto()
input_check(domains, upstreams, port, proto)
nginx_config = create_nginx_config(conf_id, domains, upstreams, port, proto)
write_nginx_config(nginx_config, NGINX_DIR, domains, conf_id)
ssl_config = create_ssl_config(conf_id)
write_ssl_config(ssl_config, conf_id, NGINX_DIR)
print("Nginx config created.")
ssl_continue()
# def test():
# print(create_nginx_config("1110", ['nolog.cz', 'www.nolog.cz'], ['10.0.0.1', '10.0.0.2'], 80, 'https://'))
if __name__ == "__main__":
main()

124
n_ssl.py
View file

@ -1,124 +0,0 @@
import os
import subprocess
import re
import sysrsync
from dotenv import load_dotenv
# NGINX_DIR="/etc/nginx"
# DOMAINS_TXT = "/etc/autossl/domains.txt"
# DEHYDRATED_LOC = "/etc/autossl/dehydrated.sh"
load_dotenv()
NGINX_DIR = os.getenv("NGINX_DIR")
DOMAINS_TXT = os.getenv("DOMAINS_TXT")
DEHYDRATED_LOC = os.getenv("DEHYDRATED_LOC")
REMOTE = os.getenv("REMOTE")
REMOTE_SSH_KEY = os.getenv("REMOTE_SSH_KEY")
def create_domfile():
# Get nginx config files with "# AUTOSSL" tag, parse IDs and domains and create domains.txt file for Dehydrated
sites_path = NGINX_DIR + "/sites"
# It's probably not the best to use grep here, but it's really fast unlike reading files in Python directly. But what can go wrong? (lol)
grep_out = subprocess.run(
["grep", "-Rh", "AUTOSSL", sites_path], capture_output=True, text=True
)
if grep_out.returncode == 0:
DOMAIN_LINES = []
for line in grep_out.stdout.splitlines():
id = re.findall(r"\d+", line)[-1]
domains = re.findall(r"(?<=server_name )(.*)(?=;)", line)[0]
DOMAIN_LINES.append(domains + " > " + str(id))
if len(DOMAIN_LINES) > 0:
with open(DOMAINS_TXT, "w") as fp:
for line in DOMAIN_LINES:
# write each item on a new line
fp.write("%s\n" % line)
else:
print("No data to write to domains.txt. \n Aborting")
exit()
else:
print("Finding #AUTOSSL comments in nginx configs failed.")
exit()
def request_cert():
print("Requesting certificate")
dehydrated_run = subprocess.run(
[DEHYDRATED_LOC, "-c"], capture_output=True, text=True
)
if dehydrated_run.returncode != 0:
print("Something went wrong with dehydrated.sh")
print(dehydrated_run.stdout)
else:
print(
"Certificates are successfully dehydrated. (It went OK and cert is now generated)"
)
def reload_local_nginx():
nginx_check = subprocess.run(["nginx", "-t"], capture_output=True, text=True)
if nginx_check.returncode != 0:
print("nginx config is not valid! Aborting")
print(nginx_check.stdout)
exit()
nginx_reload = subprocess.run(
["systemctl", "reload", "nginx.service"], capture_output=True, text=True
)
if nginx_reload.returncode != 0:
print("Nginx reload returned non-zero status code")
print(nginx_reload.stdout)
exit()
def remote_replication(remote, ssh_key):
# Copy nginx config to second server
sysrsync.run(
source="/etc/nginx/",
destination="/etc/nginx/",
destination_ssh=remote,
private_key=ssh_key,
options=["-a", "--delete"],
)
# Copy certificates to second server
sysrsync.run(
source="/etc/autossl/",
destination="/etc/autossl/",
destination_ssh=remote,
private_key=ssh_key,
options=["-a", "--delete"],
)
def remote_reload(remote, ssh_key):
# Check and reload nginx on second server
nginx_check = subprocess.run(
["ssh", "-i", ssh_key, remote, "nginx", "-t"], capture_output=True, text=True
)
if nginx_check.returncode != 0:
print("Remote nginx config is not valid! Please check manually.")
print(nginx_check.stdout)
return False
else:
nginx_reload = subprocess.run(
["ssh", "-i", ssh_key, remote, "systemctl", "reload", "nginx.service"],
capture_output=True,
text=True,
)
if nginx_reload.returncode != 0:
print("Remote nginx reload failed, please check manually.")
print(nginx_reload.stdout)
def main():
create_domfile()
request_cert()
reload_local_nginx()
remote_replication(REMOTE, REMOTE_SSH_KEY)
remote_reload(REMOTE, REMOTE_SSH_KEY)
if __name__ == "__main__":
main()

View file

@ -1,10 +0,0 @@
#!/bin/bash
kill -s $(keepalived --signum=DATA) $(cat /var/run/keepalived.pid)
STATE=$(cat /tmp/keepalived.data |grep MASTER)
if [[ -z "${STATE}" ]]; then
echo "This is a secondary backup server, run the script on current master"
else
python3 n_gen.py
fi

View file

View file

@ -0,0 +1,85 @@
import os
import re
from pathlib import Path
from typing import List
from .templating import jinja
import glob
DOMAINS_RE = re.compile(
r"^(((?!-))(xn--|_)?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$"
)
def _walk_nginx_conf(nginx_conf: Path):
"""Recursively finds all configuration files of a nginx config"""
# this path is not necessarily correct... someone could have a weird prefix and
# conf file combination
conf_dir = nginx_conf.parent
stack = [nginx_conf]
while len(stack):
file = stack.pop()
conf = file.read_text()
yield file, conf
for include in re.finditer(r"(?:^|\n\s*|[{;]\s*)include (.+);", conf):
pattern = include.group(1)
for file in glob.glob(
pattern if pattern.startswith("/") else f"{conf_dir}/pattern"
):
stack.append(Path(file))
def gather_autossl_directives(nginx_conf: Path):
"""
Finds #AUTOSSL directives inside an nginx configuration. The server_name
must be on a separate line. (which it usually is)
"""
directives = []
for _, conf in _walk_nginx_conf(nginx_conf):
for directive in re.finditer(
r"(?:^|\n\s*|[{;]\s*)server_name (.*); *# *AUTOSSL *> *(\S+)", conf
):
domains, alias = directive.groups()
domains = domains.split()
if any(not re.match(DOMAINS_RE, domain) for domain in domains):
raise ValueError(
f"Cannot get SSL cert for \"{''.join(domains)}\". Invalid domains."
)
if not re.match(r"^[a-zA-Z0-9-_]$", alias):
raise ValueError(f'Invalid cert alias "{alias}"')
directives.append((domains, alias))
return directives
def get_site_files(nginx_dir: Path) -> List[Path]:
names = []
for file in (nginx_dir / "sites/auto").iterdir():
if file.is_file() and re.match(r"\d+-.+\.conf", file.name):
names.append(file)
for file in (nginx_dir / "sites/custom").iterdir():
if file.is_file() and re.match(r"\d+-.+\.conf", file.name):
names.append(file)
return names
def build_domains_txt(directives):
return (
"\n".join(" ".join(domains) + " > " + alias for domains, alias in directives)
+ "\n"
)
def generate_ssl_configs(dir: Path, cert_aliases: List[str]):
for alias in cert_aliases:
file = dir / (alias + ".conf")
template = jinja.get_template("ssl.conf")
file.write_text(template.render(alias=alias))

364
nginx_configurator/main.py Normal file
View file

@ -0,0 +1,364 @@
import json
import os
import re
import tempfile
from pathlib import Path
import click
from dotenv import load_dotenv
from . import sysaction, certs
from .sysaction import quit_on_err
from .templating import jinja
load_dotenv(os.getenv("DOTENV_PATH", "/etc/ncc/.env"))
NGINX_DIR = Path(os.getenv("NGINX_DIR", "/etc/nginx"))
DOMAINS_TXT = Path(os.getenv("DOMAINS_TXT", "/etc/dehydrated/domains.txt"))
REMOTE = os.getenv("REMOTE")
REMOTE_SSH_KEY = os.getenv("REMOTE_SSH_KEY")
DEHYDRATED_LOC = os.getenv("DEHYDRATED_LOC", "/etc/dehydrated/dehydrated.sh")
DEHYDRATED_TRIGGER_FILE = Path(
os.getenv("DEHYDRATED_TRIGGER_FILE", "/etc/dehydrated/trigger")
)
CLUSTERS_FILE = Path(os.getenv("CLUSTERS_FILE", "/etc/ncc/clusters.json"))
@click.group()
@click.option("--skip-master-check", type=bool, is_flag=True)
def cli(skip_master_check: bool):
"""Update the nginx cluster configuration
MUST BE RAN ON MASTER (will detect automatically)
"""
if not skip_master_check and not sysaction.is_keepalived_master():
click.echo("Refusing to start. Not running on master.", err=True)
exit(1)
@cli.command()
def reload():
"""Replicate the local config and reload the nginx cluster
Does the following:
1. checks the nginx configuration
2. generates domains.txt for dehydrated
3. runs dehydrated to obtain certificates
4. reloads nginx
5. replicates the validated configuration
"""
# check nginx config
quit_on_err(
sysaction.check_nginx_config(NGINX_DIR),
print_always=True,
additional_info="Nginx configuration is incorrect",
)
# build domains.txt
try:
directives = certs.gather_autossl_directives(NGINX_DIR / "nginx.conf")
DOMAINS_TXT.write_text(certs.build_domains_txt(directives))
except ValueError as e:
click.secho(e, err=True, fg="red")
exit(1)
# obtain certs
quit_on_err(
sysaction.run_dehydrated(DEHYDRATED_LOC),
additional_info="Failed to run dehydrated",
)
certs.generate_ssl_configs(NGINX_DIR / "ssl", [d[1] for d in directives])
# reload nginx
quit_on_err(sysaction.reload_nginx(), additional_info="Failed to reload nginx")
# replicate to remote
sysaction.remote_replication(REMOTE, REMOTE_SSH_KEY)
quit_on_err(
sysaction.remote_check_nginx_config(REMOTE, REMOTE_SSH_KEY, NGINX_DIR),
additional_info="Remote nginx configuration is incorrect",
)
quit_on_err(
sysaction.remote_reload_nginx(REMOTE, REMOTE_SSH_KEY),
additional_info="Remote nginx failed to reload",
)
def get_upstreams():
clusters = json.loads(Path(CLUSTERS_FILE).read_text())
click.echo("Existing clusters:")
line = ""
for cluster in clusters:
if len(line + " " + cluster["name"]) > 78:
click.echo(" " + line.strip())
line = cluster["name"]
else:
line += " " + cluster["name"]
click.echo(" " + line.strip())
upstreams = click.prompt("Comma-separated upstreams, or an existing cluster name")
cluster = next((c for c in clusters if c["name"] == upstreams), None)
if cluster:
upstreams = cluster["upstreams"]
else:
upstreams = [u.strip() for u in upstreams.split(",")]
return upstreams
def get_id():
return max(int(n.name.split("-")[0]) for n in certs.get_site_files(NGINX_DIR)) + 1
def test_config(config: str, file: Path):
old_config = None
if file.exists():
old_config = file.read_text()
if config:
file.write_text(config)
else:
file.unlink(missing_ok=True)
c, out = sysaction.check_nginx_config(NGINX_DIR)
if c:
try:
certs.gather_autossl_directives(NGINX_DIR / "nginx.conf")
except ValueError as e:
out = e
c = False
# rollback
if old_config is not None:
file.write_text(old_config)
else:
file.unlink()
return c, out
def edit_service(config, service_file: Path):
ok = False
new_config = None
while not ok:
new_config = click.edit(config, extension=".conf")
if new_config is not None:
config = new_config
c, out = test_config(config, service_file)
if not c:
click.echo(out, err=True)
click.secho("Failed to verify configuration", fg="red")
choice = click.prompt(
"Edit service configuration?", type=click.Choice(("yes", "abort"))
)
if choice == "abort":
tmp = Path(tempfile.mktemp())
tmp.write_text(config)
click.echo(f"Unfinished service written to {tmp}")
break
ok = c
return new_config, ok
@cli.command()
@click.pass_context
def new(ctx):
"""Create a new service"""
config = ""
should_open_editor = False
id = get_id()
if click.confirm("Would you like to use the default template?", default=True):
template = jinja.get_template("nginx-site.conf")
domains = [
d.strip() for d in click.prompt("Comma-separated domains").split(",")
]
upstreams = get_upstreams()
port = click.prompt("Upstream port", type=int)
proto = (
click.prompt(
"Upstream protocol",
type=click.Choice(("http", "https")),
show_choices=True,
)
+ "://"
)
config = template.render(
id=id, domains=domains, upstreams=upstreams, port=port, proto=proto
)
# create ssl file so configuration test passes
Path(f"/etc/nginx/ssl/{id}.conf").touch()
filename = f"{id}-{domains[0]}.conf"
else:
should_open_editor = True # force open the config
template = jinja.get_template("nginx-minimal.conf")
config = template.render(id=id)
filename = f'{id}-{click.prompt("Service name (used as filename (eg. 01-service.conf))")}.conf'
# XXX: beware of weird code below
should_open_editor = should_open_editor or click.confirm(
"Would you like to edit the config?", default=True
)
# assume the user wants to edit the file if we are opening the editor
service_file = Path(
NGINX_DIR / "sites" / ("custom" if should_open_editor else "auto") / filename
)
ok = False
while not ok:
if should_open_editor:
new_cfg, ok = edit_service(config, service_file)
if not ok:
break
if new_cfg:
config = new_cfg
else:
# correct previous assumption
service_file = NGINX_DIR / "sites/auto" / filename
else:
ok, out = test_config(config, service_file)
if not ok:
click.echo(out, err=True)
click.secho("Failed to verify nginx configuration", fg="red")
choice = click.prompt(
"Edit service configuration?", type=click.Choice(("yes", "abort"))
)
if choice == "abort":
break
should_open_editor = True
service_file = NGINX_DIR / "sites/custom" / filename
if not ok:
Path(f"/etc/nginx/ssl/{id}.conf").unlink(missing_ok=True)
exit(1)
service_file.write_text(config)
ctx.invoke(reload)
@cli.command()
@click.argument("service")
@click.pass_context
def edit(ctx, service: str):
"""Edit a service"""
filename = service if service.endswith(".conf") else service + ".conf"
auto = True
file = NGINX_DIR / "sites/auto" / filename
if not file.exists():
auto = False
file = NGINX_DIR / "sites/custom" / filename
if not file.exists():
click.secho(f"Service {filename} does not exist", fg="red")
exit(1)
config = file.read_text()
new_cfg, ok = edit_service(config, file)
if not new_cfg:
exit(0)
if not ok:
exit(1)
if auto:
file = file.rename(NGINX_DIR / "sites/custom" / filename)
file.write_text(new_cfg)
ctx.invoke(reload)
@cli.command()
@click.argument("service")
@click.pass_context
def delete(ctx, service: str):
"""Delete a service"""
filename = service if service.endswith(".conf") else service + ".conf"
file = NGINX_DIR / "sites/auto" / filename
if not file.exists():
file = NGINX_DIR / "sites/custom" / filename
if not file.exists():
click.secho(f"Service {filename} does not exist", fg="red")
exit(1)
c, out = test_config("", file)
if not c:
click.echo(out, err=True)
click.secho("Failed to verify nginx configuration", fg="red")
click.echo("Service was not deleted")
exit(1)
file.unlink()
ctx.invoke(reload)
@cli.command("list")
def list_():
"""List exsiting services and domain names associated with them"""
files = certs.get_site_files(NGINX_DIR)
for file in files:
config = file.read_text()
domain_names = set()
for directive in re.finditer(r"(?:^|\n\s*|[{;]\s*)server_name (.*);", config):
for domain in directive.group(1).split():
domain_names.add(domain)
click.echo(f"{file.name}: {' '.join(domain_names)}")
@cli.command()
def autossl():
"""Renew SSL certificates and replicate changes"""
# build domains.txt
try:
directives = certs.gather_autossl_directives(NGINX_DIR / "nginx.conf")
DOMAINS_TXT.write_text(certs.build_domains_txt(directives))
except ValueError as e:
click.secho(e, err=True, fg="red")
exit(1)
# obtain certs
quit_on_err(
sysaction.run_dehydrated(DEHYDRATED_LOC),
additional_info="Failed to run dehydrated",
)
if DEHYDRATED_TRIGGER_FILE.exists():
click.echo("Certificates changed - reloading cluster")
certs.generate_ssl_configs(NGINX_DIR / "ssl", [d[1] for d in directives])
# reload nginx
quit_on_err(sysaction.reload_nginx(), additional_info="Failed to reload nginx")
# replicate to remote
sysaction.remote_replication(REMOTE, REMOTE_SSH_KEY)
quit_on_err(
sysaction.remote_check_nginx_config(REMOTE, REMOTE_SSH_KEY, NGINX_DIR),
additional_info="Remote nginx configuration is incorrect",
)
quit_on_err(
sysaction.remote_reload_nginx(REMOTE, REMOTE_SSH_KEY),
additional_info="Remote nginx failed to reload",
)

View file

@ -0,0 +1,85 @@
import os
from pathlib import Path
import signal
import subprocess
import time
import click
import sysrsync
def _run_shell(cmd):
out = subprocess.run(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
)
return out.returncode == 0, out.stdout
def quit_on_err(out, print_always=False, additional_info=""):
if (not out[0] or print_always) and len(out[1].strip()) > 0:
# print out nginx warnings
click.echo(out[1], err=True)
if not out[0]:
if additional_info:
click.secho(additional_info, err=True, fg="red")
exit(1)
def is_keepalived_master():
keepalived_data = Path("/tmp/keepalived.data")
keepalived_data.unlink(missing_ok=True) # do not read old data
sig = int(os.getenv("KEEPALIVED_DATA_SIGNAL", signal.SIGUSR1))
os.kill(int(Path("/run/keepalived.pid").read_text()), sig)
# wait for keepalived
for _ in range(10):
time.sleep(0.05)
if keepalived_data.exists():
break
else:
raise Exception(f"keepalived did not produce data on signal {sig}")
# TODO: could we do better?
return "State = MASTER" in Path("/tmp/keepalived.data").read_text()
def reload_nginx():
return _run_shell(("nginx", "-s", "reload"))
def check_nginx_config(nginx_dir: Path):
return _run_shell(("nginx", "-t", "-c", str(nginx_dir / "nginx.conf")))
def run_dehydrated(dehydrated_bin: str):
return _run_shell((dehydrated_bin, "-c"))
def remote_replication(remote, ssh_key):
# Copy nginx config to second server
sysrsync.run(
source="/etc/nginx/",
destination="/etc/nginx/",
destination_ssh=remote,
private_key=ssh_key,
options=["-a", "--delete"],
)
# Copy certificates to second server
sysrsync.run(
source="/etc/autossl/",
destination="/etc/autossl/",
destination_ssh=remote,
private_key=ssh_key,
options=["-a", "--delete"],
)
def remote_check_nginx_config(remote, ssh_key, nginx_dir: Path):
# Check and reload nginx on second server
nc = str(nginx_dir / "nginx.conf")
return _run_shell(("ssh", "-i", ssh_key, remote, "nginx", "-t", "-c", nc))
def remote_reload_nginx(remote, ssh_key):
return _run_shell(("ssh", "-i", ssh_key, remote, "nginx", "-s", "reload"))

View file

View file

@ -0,0 +1 @@
# ID: {{ id }}

View file

@ -1,5 +1,5 @@
# ID: {{ id }}
# Service configured by n_gen.py
# Service configured by ncc
upstream up_{{ id }} {
{%- for upstream in upstreams %}
@ -10,8 +10,8 @@ upstream up_{{ id }} {
server {
server_name{% for domain in domains %} {{ domain }}{% endfor %}; # AUTOSSL > {{ id }}
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 443 ssl http2;
listen [::]:443 ssl http2;
# ssl
include /etc/nginx/ssl/{{ id }}.conf;
@ -27,8 +27,8 @@ server {
# reverse proxy
location / {
proxy_pass {{ proto }}up_{{ id }};
include include/proxy-headers.conf;
proxy_pass {{ proto }}up_{{ id }};
include include/proxy-headers.conf;
}
}

View file

@ -0,0 +1,4 @@
ssl_certificate /etc/autossl/certs/{{ alias }}/fullchain.pem;
ssl_certificate_key /etc/autossl/certs/{{ alias }}/privkey.pem;
include include/ssl_defaults.conf;
ssl_dhparam /etc/autossl/ssl-dhparams.pem;

View file

@ -0,0 +1,15 @@
import pkgutil
from jinja2 import Environment, FunctionLoader, select_autoescape
def load_template(name):
"""
Loads file from the templates folder and returns file contents as a string.
See jinja2.FunctionLoader docs.
"""
return pkgutil.get_data(f"{__package__}.templates", name).decode("utf-8")
jinja = Environment(
loader=FunctionLoader(load_template), autoescape=select_autoescape()
)

View file

@ -1,4 +1,4 @@
pyinputplus
Jinja2
sysrsync
click
python-dotenv
sysrsync
Jinja2

3
run.py Normal file
View file

@ -0,0 +1,3 @@
from nginx_configurator import main
main.cli()

View file

@ -1,4 +0,0 @@
ssl_certificate /etc/autossl/certs/{{ id }}/fullchain.pem;
ssl_certificate_key /etc/autossl/certs/{{ id }}/privkey.pem;
include include/ssl_defaults.conf;
ssl_dhparam /etc/autossl/ssl-dhparams.pem;