diff --git a/.gitignore b/.gitignore index f77e93e..0c125f9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ __pycache__ .vscode clusters.json /nginx -/autossl \ No newline at end of file +/autossl +.env \ No newline at end of file diff --git a/README.md b/README.md index 66b91ff..877fd07 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,21 @@ # Nginx configurator (patent for that name is pending...) # TODO -* Add a way to generate autoincrementing config ID -* document dhparam.pem generation - +* Prepare config templates for nginx and dehydrated? +* document dhparam.pem generation (`openssl dhparam -out ssl-dhparams.pem 4096` in /etc/autossl) +* Limit current SSH keys to only config rsync and nginx reload +* Write down how it works in human language +* Create a guide how to use it to intrawiki +* Teach everybody how to use it... + +# Setup +* `python3 -m venv .venv` +* `source .venv/bin/activate` +* `pip3 install -r ./requirements.txt` +* `cp env.sample .env` # and customize to your needs + + + # Contributions Please use `black` formatter. diff --git a/env.sample b/env.sample new file mode 100644 index 0000000..9927f73 --- /dev/null +++ b/env.sample @@ -0,0 +1,5 @@ +NGINX_DIR="/etc/nginx" +DOMAINS_TXT="/etc/autossl/domains.txt" +DEHYDRATED_LOC="/etc/autossl/dehydrated.sh" +REMOTE="10.0.0.1" +REMOTE_SSH_KEY="./ssh.key" \ No newline at end of file diff --git a/n-gen.py b/n_gen.py similarity index 61% rename from n-gen.py rename to n_gen.py index dfe72a2..fe87d03 100755 --- a/n-gen.py +++ b/n_gen.py @@ -1,18 +1,34 @@ +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()) +jin = Environment(loader=PackageLoader("n_gen"), autoescape=select_autoescape()) -# ID of next config - TBD - read this from list of configs and increment! -CONF_ID = 1 +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 = [] @@ -85,26 +101,59 @@ def input_check(domains, upstreams, port, proto): exit() -def fill_template(id, domains, upstreams, port, proto): +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) - print(fill_template(CONF_ID, 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(fill_template("1110", ['nolog.cz', 'www.nolog.cz'], ['10.0.0.1', '10.0.0.2'], 80, 'https://')) +# print(create_nginx_config("1110", ['nolog.cz', 'www.nolog.cz'], ['10.0.0.1', '10.0.0.2'], 80, 'https://')) if __name__ == "__main__": main() - # test() + + \ No newline at end of file diff --git a/n-ssl.py b/n_ssl.py similarity index 61% rename from n-ssl.py rename to n_ssl.py index ba94e04..2f43553 100644 --- a/n-ssl.py +++ b/n_ssl.py @@ -1,16 +1,19 @@ 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" -NGINX_DIR = "./nginx" -DOMAINS_TXT = "./autossl/domains.txt" -DEHYDRATED_LOC = "./autossl/dehydrated.sh" - -REMOTE = "10.55.55.55" # make a .env variable or something like that. It will be different on each server +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(): @@ -70,22 +73,51 @@ def reload_local_nginx(): exit() -def remote_replication(remote): - # Do RSYNC to second server - return True +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"], + ) + # Copy certificates to second server + sysrsync.run( + source="/etc/autossl/", + destination="/etc/autossl/", + destination_ssh=remote, + private_key=ssh_key, + options=["-a"], + ) -def remote_reload(remote): +def remote_reload(remote, ssh_key): # Check and reload nginx on second server - return True + 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() + create_domfile() request_cert() reload_local_nginx() - remote_replication(REMOTE) - remote_reload(REMOTE) + remote_replication(REMOTE, REMOTE_SSH_KEY) + remote_reload(REMOTE, REMOTE_SSH_KEY) if __name__ == "__main__": diff --git a/new_service.sh b/new_service.sh new file mode 100644 index 0000000..e989a62 --- /dev/null +++ b/new_service.sh @@ -0,0 +1,10 @@ +#!/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 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 487d0c1..3fa2610 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ pyinputplus -Jinja2 \ No newline at end of file +Jinja2 +sysrsync +python-dotenv \ No newline at end of file diff --git a/templates/nginx-site.conf b/templates/nginx-site.conf index c933e85..0a61ab9 100644 --- a/templates/nginx-site.conf +++ b/templates/nginx-site.conf @@ -1,5 +1,5 @@ # ID: {{ id }} -# Service configured by nxa.py +# Service configured by n_gen.py upstream up_{{ id }} { {%- for upstream in upstreams %}