diff --git a/roles/authelia/defaults/main.json b/roles/authelia/defaults/main.json index 9b5e676..47b1e01 100644 --- a/roles/authelia/defaults/main.json +++ b/roles/authelia/defaults/main.json @@ -5,6 +5,8 @@ "var_authelia_jwt_secret": "REPLACE_ME", "var_authelia_users_file_path": "/var/authelia/users.yml", "var_authelia_log_file_path": "/var/authelia/log.jsonl", + "var_authelia_domain": "authelia.example.org", + "var_authelia_redirect_url": "https://example.org", "var_authelia_session_domain": "example.org", "var_authelia_session_secret": "REPLACE_ME", "var_authelia_storage_encryption_key": "REPLACE_ME", @@ -22,6 +24,7 @@ "var_authelia_storage_data_mariadb_schema": "authelia", "var_authelia_ntp_server": "time.cloudflare.com:123", "var_authelia_password_reset_enabled": false, + "var_authelia_password_reset_custom_url": null, "var_authelia_notification_mode": "smtp", "var_authelia_notification_file_path": "/var/authelia/notifications", "var_authelia_notification_smtp_host": "smtp.example.org", diff --git a/roles/authelia/templates/conf-main.json.j2 b/roles/authelia/templates/conf-main.json.j2 index 98c0437..475cda4 100644 --- a/roles/authelia/templates/conf-main.json.j2 +++ b/roles/authelia/templates/conf-main.json.j2 @@ -56,7 +56,7 @@ {% else %} "disable": true, {% endif %} - "custom_url": "" + "custom_url": "{{var_authelia_password_reset_custom_url}}" }, "refresh_interval": "5m", "file": { @@ -120,12 +120,18 @@ }, "session": { "name": "authelia_session", - "domain": "{{var_authelia_session_domain}}", "same_site": "lax", "secret": "{{var_authelia_session_secret}}", "expiration": "1h", "inactivity": "5m", - "remember_me": "1M" + "remember_me": "1M", + "cookies": [ + { + "domain": "{{var_authelia_session_domain}}", + "authelia_url": "https://{{var_authelia_domain}}/", + "default_redirection_url": "{{var_authelia_redirect_url}}" + } + ] }, "regulation": { "max_retries": 3, diff --git a/roles/authelia/vardef.json b/roles/authelia/vardef.json index 8370d6b..9b7d5bc 100644 --- a/roles/authelia/vardef.json +++ b/roles/authelia/vardef.json @@ -23,6 +23,14 @@ "type": "string", "mandatory": false }, + "domain": { + "type": "string", + "mandatory": false + }, + "redirect_url": { + "type": "string", + "mandatory": false + }, "session_domain": { "type": "string", "mandatory": false @@ -91,6 +99,11 @@ "type": "boolean", "mandatory": false }, + "password_reset_custom_url": { + "nullable": true, + "type": "string", + "mandatory": false + }, "notification_mode": { "type": "string", "mandatory": false, diff --git a/roles/nginx/defaults/main.json b/roles/nginx/defaults/main.json index bfd870e..997702e 100644 --- a/roles/nginx/defaults/main.json +++ b/roles/nginx/defaults/main.json @@ -1,3 +1,3 @@ { + "var_nginx_auto_reload_interval": null } - diff --git a/roles/nginx/tasks/main.json b/roles/nginx/tasks/main.json index 26f2739..183ff43 100644 --- a/roles/nginx/tasks/main.json +++ b/roles/nginx/tasks/main.json @@ -25,6 +25,36 @@ "dest": "/etc/nginx/ssl-hardening.conf" } }, + { + "name": "auto reload", + "when": "var_nginx_auto_reload_interval == None", + "become": true, + "ansible.builtin.cron": { + "name": "nginx_auto_reload", + "disabled": true, + "minute": "0", + "hour": "*/{{var_nginx_auto_reload_interval | string}}", + "day": "*", + "month": "*", + "weekday": "*", + "job": "systemctl reload nginx" + } + }, + { + "name": "auto reload", + "when": "var_nginx_auto_reload_interval != None", + "become": true, + "ansible.builtin.cron": { + "name": "nginx_auto_reload", + "disabled": false, + "minute": "0", + "hour": "*/{{var_nginx_auto_reload_interval | string}}", + "day": "*", + "month": "*", + "weekday": "*", + "job": "systemctl reload nginx" + } + }, { "name": "restart service", "become": true, diff --git a/roles/nginx/tasks/main.json.orig b/roles/nginx/tasks/main.json.orig new file mode 100644 index 0000000..3941ce5 --- /dev/null +++ b/roles/nginx/tasks/main.json.orig @@ -0,0 +1,86 @@ +[ + { + "name": "install packages", + "become": true, + "ansible.builtin.apt": { + "update_cache": true, + "pkg": [ + "nginx" + ] + } + }, + { +<<<<<<< HEAD +======= + "name": "generate dhparams file", + "become": true, + "ansible.builtin.command": { + "cmd": "openssl dhparam -out /etc/nginx/dhparam 4096" + }, + "args": { + "creates": "/etc/nginx/dhparam" + } + }, + { + "name": "place hardening config", + "become": true, + "ansible.builtin.copy": { + "src": "ssl-hardening.conf", + "dest": "/etc/nginx/ssl-hardening.conf" + } + }, + { + "name": "ufw | check", + "become": true, + "check_mode": true, + "community.general.ufw": { + "state": "enabled" + }, + "register": "ufw_enable_check" + }, + { + "name": "ufw | allow port 80", + "when": "not ufw_enable_check.changed", + "become": true, + "community.general.ufw": { + "rule": "allow", + "port": "80", + "proto": "tcp" + } + }, + { + "name": "ufw | allow port 443", + "when": "not ufw_enable_check.changed", + "become": true, + "community.general.ufw": { + "rule": "allow", + "port": "443", + "proto": "tcp" + } + }, + { + "name": "auto reload", + "when": "auto_reload_interval != None", + "become": true, + "ansible.builtin.cron": { + "name": "nginx_auto_reload", + "disabled": true, + "minute": "0", + "hour": "*/{{var_nginx_auto_reload_interval | string}}", + "day": "*", + "month": "*", + "weekday": "*", + "job": "systemctl reload nginx" + } + }, + { +>>>>>>> f55f317 ([fix] role:nginx) + "name": "restart service", + "become": true, + "ansible.builtin.systemd_service": { + "state": "restarted", + "name": "nginx" + } + } +] + diff --git a/roles/nginx/vardef.json b/roles/nginx/vardef.json new file mode 100644 index 0000000..c03ddc6 --- /dev/null +++ b/roles/nginx/vardef.json @@ -0,0 +1,8 @@ +{ + "auto_reload_interval": { + "description": "in hours", + "nullable": true, + "type": "integer", + "mandatory": false + } +} diff --git a/roles/system_basics/tasks/main.json b/roles/system_basics/tasks/main.json new file mode 100644 index 0000000..d19d6fb --- /dev/null +++ b/roles/system_basics/tasks/main.json @@ -0,0 +1,49 @@ +[ + { + "name": "Set timezone to Berlin", + "become": true, + "community.general.timezone": { + "name": "Europe/Berlin" + } + }, + { + "name": "Limit syslogs", + "become": true, + "ansible.builtin.lineinfile": { + "dest": "/etc/systemd/journald.conf", + "regexp": "^#?\\s*SystemMaxFileSize", + "line": "SystemMaxFileSize=2G" + }, + "notify": "restart journal" + }, + { + "name": "install packages", + "become": true, + "ansible.builtin.apt": { + "pkg": [ + "vim", + "htop", + "tmux", + "rsync" + ] + } + }, + { + "name": "Set vim as default editor", + "become": true, + "community.general.alternatives": { + "name": "editor", + "path": "/usr/bin/vim.basic" + } + }, + { + "name": "Disable root login without key", + "become": true, + "ansible.builtin.lineinfile": { + "dest": "/etc/ssh/sshd_config", + "regexp": "^#?PermitRootLogin ", + "line": "PermitRootLogin without-password" + }, + "notify": "restart sshd" + } +] diff --git a/roles/tlscert_acme_inwx/files/inwx b/roles/tlscert_acme_inwx/files/inwx index 2fdeb41..53b950c 100755 --- a/roles/tlscert_acme_inwx/files/inwx +++ b/roles/tlscert_acme_inwx/files/inwx @@ -10,6 +10,32 @@ import argparse as _argparse import pathlib as _pathlib import time as _time +def convey(x, fs): + y = x + for f in fs: + y = f(y) + return y + + +def string_coin( + template : str, + arguments : dict +): + result = template + for (key, value, ) in arguments.items(): + result = result.replace("{{%s}}" % key, value) + return result + + +def file_read( + path : str +): + handle = open(path, "r") + content = handle.read() + handle.close() + return content + + def log( messsage : str ): @@ -28,30 +54,6 @@ def path_read( return position -def path_write( - thing, - steps : List[str], - value -): - steps_first = steps[:-1] - step_last = steps[-1] - position = thing - for step in steps_first: - if (not (step in position)): - position[step] = {} - position = position[step] - position[step_last] = value - - -def merge( - core, - mantle -): - result = core.copy() - result.update(mantle) - return result - - def http_call( request : dict, ) -> dict: @@ -76,41 +78,124 @@ def http_call( return response -_conf_data = { - "url": { - "test": { - "scheme": "https", - "host": "api.ote.domrobot.com", - "port": 443, - "path": "jsonrpc/" - }, - "production": { - "scheme": "https", - "host": "api.domrobot.com", - "port": 443, - "path": "jsonrpc/" - } - }, - "environment": "production", - "account": { - "username": None, - "password": None - } -} +_conf_data = None +def conf_schema( +): + return { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + }, + "additionalProperties": { + "type": "object", + "properties": { + "scheme": { + "type": "string", + }, + "host": { + "type": "string", + }, + "port": { + "type": "number", + }, + "path": { + "type": "string", + }, + }, + "additionalProperties": False, + "required": [ + "host", + ] + }, + "required": [ + ] + }, + "environment": { + "type": "string", + }, + "account": { + "type": "object", + "properties": { + "username": { + "type": "string", + }, + "password": { + "type": "string", + }, + }, + "additionalProperties": False, + "required": [ + ] + } + }, + "additionalProperties": False, + "required": [ + ], + } + + def conf_load( path : str ): global _conf_data - if (not _os.path.exists(path)): - pass - else: - handle = open(path, "r") - content = handle.read() - handle.close() - data = _json.loads(content) - _conf_data = merge(_conf_data, data) + conf_data_raw = ( + _json.loads(file_read(path)) + if _os.path.exists(path) else + {} + ) + for pair in conf_data_raw.get("url", {}).items(): + if ("host" in pair[1]): + pass + else: + raise ValueError("flawed conf: missing mandatory value 'host' for url entry '%s'" % pair[0]) + _conf_data = { + "url": convey( + ( + { + "test": { + "scheme": "https", + "host": "api.ote.domrobot.com", + "port": 443, + "path": "jsonrpc/" + }, + "production": { + "scheme": "https", + "host": "api.domrobot.com", + "port": 443, + "path": "jsonrpc/" + } + } + | + conf_data_raw.get("url", {}) + ), + [ + lambda x: x.items(), + lambda pairs: map( + lambda pair: ( + pair[0], + { + "scheme": pair[1].get("scheme", "https"), + "host": pair[1]["host"], + "port": pair[1].get("port", 443), + "path": pair[1].get("path", "jsonrpc/"), + } + ), + pairs + ), + dict, + ] + ), + "environment": conf_data_raw.get("environment", "production"), + "account": { + "username": conf_data_raw.get("account", {}).get("username", None), + "password": conf_data_raw.get("account", {}).get("password", None), + } + } + # print(_json.dumps(_conf_data, indent = "\t")) def conf_get( @@ -119,15 +204,6 @@ def conf_get( global _conf_data return path_read(_conf_data, path.split(".")) - -def conf_set( - path : str, - value -): - global _conf_data - path_write(_conf_data, path.split("."), value) - - def api_call( environment : str, accesstoken : str, @@ -172,6 +248,9 @@ def api_call( return result +''' +@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.login +''' def api_macro_login( environment : str, username : str, @@ -195,6 +274,9 @@ def api_macro_login( return response["_accesstoken"] +''' +@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.logout +''' def api_macro_logout( environment : str, accesstoken : str @@ -210,6 +292,9 @@ def api_macro_logout( return None +''' +@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.info +''' def api_macro_info( environment : str, username : str, @@ -228,6 +313,9 @@ def api_macro_info( return info +''' +@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.info +''' def api_macro_list( environment : str, username : str, @@ -248,12 +336,17 @@ def api_macro_list( return info +''' +@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.info +@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.createRecord +@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.updateRecord +''' def api_macro_save( environment : str, username : str, password : str, - domain : str, - name : str, + domain_base : str, + domain_path, type_ : str, content : str ): @@ -264,12 +357,28 @@ def api_macro_save( "nameserver", "info", { - "domain": domain, + "domain": domain_base, } ) matching = list( filter( - lambda record: ((record["name"] == (name + "." + domain)) and (record["type"] == type_)), + lambda record: ( + ( + ( + (domain_path is None) + and + (record["name"] == domain_base) + ) + or + ( + (domain_path is not None) + and + (record["name"] == (domain_path + "." + domain_base)) + ) + ) + and + (record["type"] == type_) + ), info["record"] ) ) @@ -281,8 +390,8 @@ def api_macro_save( "nameserver", "createRecord", { - "domain": domain, - "name": name, + "domain": domain_base, + "name": domain_path, "type": type_, "content": content, } @@ -308,153 +417,324 @@ def api_macro_save( -def args( +''' +@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.info +@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.deleteRecord +''' +def api_macro_delete( + environment : str, + username : str, + password : str, + domain_base : str, + domain_path, + type_ ): - argumentparser = _argparse.ArgumentParser( + accesstoken = api_macro_login(environment, username, password) + info = api_call( + environment, + accesstoken, + "nameserver", + "info", + { + "domain": domain_base, + } + ) + matching = list( + filter( + lambda record: ( + ( + ( + (domain_path is None) + and + (record["name"] == domain_base) + ) + or + ( + (domain_path is not None) + and + (record["name"] == (domain_path + "." + domain_base)) + ) + ) + and + ( + (type_ is None) + or + (record["type"] == type_) + ) + ), + info["record"] + ) + ) + for entry in matching: + id_ = entry["id"] + result = api_call( + environment, + accesstoken, + "nameserver", + "deleteRecord", + { + "id": id_, + } + ) + api_macro_logout(environment, accesstoken) + + +def main( +): + ## args + argument_parser = _argparse.ArgumentParser( description = "INWX CLI Frontend" ) - argumentparser.add_argument( + argument_parser.add_argument( "-c", "--conf", + type = str, dest = "conf", default = _os.path.join(str(_pathlib.Path.home()), ".inwx-conf.json"), metavar = "", help = "path to configuration file", ) - argumentparser.add_argument( + argument_parser.add_argument( "-e", "--environment", + type = str, dest = "environment", metavar = "", default = None, - help = "environment to use; one of the keys in the 'url' filed of the configuration; overwrites the configuration value", + help = "environment to use; one of the keys in the 'url' node of the configuration; overwrites the configuration value", ) - argumentparser.add_argument( + argument_parser.add_argument( "-u", "--username", + type = str, dest = "username", metavar = "", default = None, help = "username; overwrites the configuration value", ) - argumentparser.add_argument( + argument_parser.add_argument( "-p", "--password", + type = str, dest = "password", metavar = "", default = None, help = "password; overwrites the configuration value", ) - ''' - argumentparser.add_argument( + argument_parser.add_argument( "-d", "--domain", + type = str, dest = "domain", default = None, metavar = "", help = "the domain to work with" ) - ''' - argumentparser.add_argument( + argument_parser.add_argument( + "-t", + "--type", + type = str, + dest = "type", + default = None, + metavar = "", + help = "the record type (A, AAAA, TXT, …)" + ) + argument_parser.add_argument( + "-v", + "--value", + type = str, + dest = "value", + default = None, + metavar = "", + help = "value for the record" + ) + argument_parser.add_argument( "-x", "--challenge-prefix", + type = str, dest = "challenge_prefix", metavar = "", default = "_acme-challenge", help = "which subdomain to use for ACME challanges", ) - argumentparser.add_argument( + argument_parser.add_argument( "-w", "--delay", - dest = "delay", type = float, + dest = "delay", default = 60.0, metavar = "", help = "seconds to wait at end of certbot auth hook", ) - argumentparser.add_argument( - "action", + argument_parser.add_argument( type = str, - choices = ["info", "list", "save", "certbot-hook"], + dest = "action", + choices = [ + "conf-schema", + "info", + "list", + "save", + "delete", + "certbot-hook", + ], metavar = "", - help = "action to execute", + help = string_coin( + "action to execute; options:\n{{options}}", + { + "options": convey( + [ + {"name": "conf-schema", "requirements": []}, + {"name": "info", "requirements": []}, + {"name": "list", "requirements": [""]}, + {"name": "save", "requirements": ["", "", ""]}, + {"name": "delete", "requirements": [""]}, + {"name": "certbot-hook", "requirements": []}, + ], + [ + lambda x: map( + lambda entry: string_coin( + "{{name}}{{macro_requirements}}", + { + "name": entry["name"], + "macro_requirements": ( + "" + if (len(entry["requirements"]) <= 0) else + string_coin( + " (requires: {{requirements}})", + { + "requirements": ",".join(entry["requirements"]), + } + ) + ), + } + ), + x + ), + " | ".join, + ] + ) + } + ), ) - argumentparser.add_argument( - "parameter", - nargs = "*", - type = str, - metavar = "", - help = "action specific parameters", - ) - arguments = argumentparser.parse_args() - return arguments + args = argument_parser.parse_args() - -def main( -): - arguments = args() + ## conf + conf_load(args.conf) - conf_load(arguments.conf) - if (not (arguments.environment is None)): conf_set("environment", arguments.environment) - if (not (arguments.username is None)): conf_set("account.username", arguments.username) - if (not (arguments.password is None)): conf_set("account.password", arguments.password) + ## vars + environment = (args.environment or conf_get("environment")) + account_username = (args.username or conf_get("account.username")) + account_password = (args.password or conf_get("account.password")) + domain_parts = (None if (args.domain is None) else args.domain.split(".")) + domain_base = (None if (domain_parts is None) else ".".join(domain_parts[-2:])) + domain_path = (None if ((domain_parts is None) or (len(domain_parts[:-2]) <= 0)) else ".".join(domain_parts[:-2])) - if (arguments.action == "info"): - result = api_macro_info( - conf_get("environment"), - conf_get("account.username"), - conf_get("account.password") - ) - print(_json.dumps(result, indent = "\t")) - elif (arguments.action == "list"): - domain = arguments.parameter[0] - result = api_macro_list( - conf_get("environment"), - conf_get("account.username"), - conf_get("account.password"), - domain - ) - print(_json.dumps(result, indent = "\t")) - elif (arguments.action == "save"): - domain = arguments.parameter[0] - name = arguments.parameter[1] - type_ = arguments.parameter[2] - content = arguments.parameter[3] - api_macro_save( - conf_get("environment"), - conf_get("account.username"), - conf_get("account.password"), - domain, - name, - type_, - content - ) - # print(_json.dumps(result, indent = "\t")) - elif (arguments.action == "certbot-hook"): - domain_full_parts = _os.environ["CERTBOT_DOMAIN"].split(".") - account = ".".join(domain_full_parts[-2:]) - concern = ".".join(domain_full_parts[:-2]) - domain = account - name = (arguments.challenge_prefix + "." + concern) - type_ = "TXT" - content = _os.environ["CERTBOT_VALIDATION"] - api_macro_save( - conf_get("environment"), - conf_get("account.username"), - conf_get("account.password"), - domain, - name, - type_, - content - ) - _time.sleep(arguments.delay) - # print(_json.dumps(result, indent = "\t")) + ## exec + if (args.action == "conf-schema"): + print(_json.dumps(conf_schema(), indent = "\t")) + elif (args.action == "info"): + if (account_username is None): + raise ValueError("account username required") + else: + if (account_password is None): + raise ValueError("account password required") + else: + result = api_macro_info( + environment, + account_username, + account_password + ) + print(_json.dumps(result, indent = "\t")) + elif (args.action == "list"): + if (account_username is None): + raise ValueError("account username required") + else: + if (account_password is None): + raise ValueError("account password required") + else: + if (args.domain is None): + raise ValueError("domain required") + else: + result = api_macro_list( + environment, + account_username, + account_password, + domain_base + ) + print(_json.dumps(result, indent = "\t")) + elif (args.action == "save"): + if (account_username is None): + raise ValueError("account username required") + else: + if (account_password is None): + raise ValueError("account password required") + else: + if (args.domain is None): + raise ValueError("domain required") + else: + if (args.type is None): + raise ValueError("type required") + else: + if (args.value is None): + raise ValueError("value required") + else: + api_macro_save( + environment, + account_username, + account_password, + domain_base, + domain_path, + args.type, + args.value + ) + elif (args.action == "delete"): + if (account_username is None): + raise ValueError("account username required") + else: + if (account_password is None): + raise ValueError("account password required") + else: + if (args.domain is None): + raise ValueError("domain required") + else: + api_macro_delete( + environment, + account_username, + account_password, + domain_base, + domain_path, + args.type + ) + elif (args.action == "certbot-hook"): + if (account_username is None): + raise ValueError("account username required") + else: + if (account_password is None): + raise ValueError("account password required") + else: + domain_full_parts = _os.environ["CERTBOT_DOMAIN"].split(".") + domain_base = ".".join(domain_full_parts[-2:]) + domain_path_stripped = ".".join(domain_full_parts[:-2]) + domain_path = (args.challenge_prefix + "." + domain_path_stripped) + type_ = "TXT" + content = _os.environ["CERTBOT_VALIDATION"] + api_macro_save( + environment, + account_username, + account_password, + domain_base, + domain_path, + type_, + content + ) + _time.sleep(args.delay) + # print(_json.dumps(result, indent = "\t")) else: - log("unhandled action '%s'" % (arguments.action, )) + log("unhandled action '%s'" % (args.action, )) try: main() except ValueError as error: - _sys.stderr.write(str(error) + "\n") + _sys.stderr.write("-- %s\n" % str(error)) diff --git a/roles/tlscert_acme_inwx/tasks/main.json b/roles/tlscert_acme_inwx/tasks/main.json index 0e8a365..ac20ecf 100644 --- a/roles/tlscert_acme_inwx/tasks/main.json +++ b/roles/tlscert_acme_inwx/tasks/main.json @@ -6,17 +6,19 @@ "update_cache": true, "pkg": [ "openssl", - "python3-cryptography" + "python3-cryptography", + "certbot" ] } }, { - "name": "directories | ssl", + "name": "directories", "become": true, "loop": [ "{{var_tlscert_acme_inwx_ssl_directory}}/private", "{{var_tlscert_acme_inwx_ssl_directory}}/csr", "{{var_tlscert_acme_inwx_ssl_directory}}/certs", + "{{var_tlscert_acme_inwx_ssl_directory}}/chains", "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains" ], "ansible.builtin.file": { @@ -25,54 +27,7 @@ } }, { - "name": "directories | Let's Encrypt account key", - "become": true, - "ansible.builtin.file": { - "state": "directory", - "path": "{{var_tlscert_acme_inwx_acme_account_key_path | dirname}}" - } - }, - { - "name": "key", - "become": true, - "community.crypto.openssl_privatekey": { - "path": "{{var_tlscert_acme_inwx_ssl_directory}}/private/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem" - } - }, - { - "name": "csr", - "become": true, - "community.crypto.openssl_csr": { - "common_name": "{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}", - "privatekey_path": "{{var_tlscert_acme_inwx_ssl_directory}}/private/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "path": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem" - } - }, - { - "name": "acme | account key", - "become": true, - "ansible.builtin.shell": { - "cmd": "test -f {{var_tlscert_acme_inwx_acme_account_key_path}} || openssl genrsa 4096 > {{var_tlscert_acme_inwx_acme_account_key_path}}" - } - }, - { - "name": "acme | init", - "become": true, - "community.crypto.acme_certificate": { - "acme_version": 2, - "acme_directory": "https://acme-v02.api.letsencrypt.org/directory", - "account_email": "{{var_tlscert_acme_inwx_acme_account_email}}", - "account_key_src": "{{var_tlscert_acme_inwx_acme_account_key_path}}", - "terms_agreed": true, - "csr": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "challenge": "dns-01", - "dest": "{{var_tlscert_acme_inwx_ssl_directory}}/certs/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "fullchain_dest": "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem" - }, - "register": "temp_acme_data" - }, - { - "name": "dns challenge | place script", + "name": "tools | inwx", "become": true, "ansible.builtin.copy": { "src": "inwx", @@ -81,33 +36,60 @@ } }, { - "name": "dns challenge | execute", - "when": "'challenge_data' in temp_acme_data", - "ansible.builtin.command": { - "cmd": "/usr/local/bin/inwx --username={{var_tlscert_acme_inwx_inwx_account_username}} --password={{var_tlscert_acme_inwx_inwx_account_password}} save {{var_tlscert_acme_inwx_domain_base}} _acme-challenge.{{var_tlscert_acme_inwx_domain_path}} TXT {{temp_acme_data['challenge_data'][var_tlscert_acme_inwx_domain_path + '.' + var_tlscert_acme_inwx_domain_base]['dns-01']['resource_value']}}" - } - }, - { - "name": "dns challenge | wait", - "when": "'challenge_data' in temp_acme_data", - "ansible.builtin.pause": { - "seconds": 60 - } - }, - { - "name": "acme | finalize", + "name": "tools | tls-get | script", "become": true, - "community.crypto.acme_certificate": { - "acme_version": 2, - "acme_directory": "https://acme-v02.api.letsencrypt.org/directory", - "account_email": "{{var_tlscert_acme_inwx_acme_account_email}}", - "account_key_src": "{{var_tlscert_acme_inwx_acme_account_key_path}}", - "terms_agreed": true, - "csr": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "challenge": "dns-01", - "dest": "{{var_tlscert_acme_inwx_ssl_directory}}/certs/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "fullchain_dest": "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "data": "{{temp_acme_data}}" + "ansible.builtin.copy": { + "src": "tls-get", + "dest": "/usr/local/bin/tls-get", + "mode": "a+x" + } + }, + { + "name": "tools | tls-get | conf", + "become": true, + "ansible.builtin.template": { + "src": "tls-get-conf.json.j2", + "dest": "/root/.tls-get-conf.json" + } + }, + { + "name": "tools | pseudo queue | setup", + "become": true, + "ansible.builtin.cron": { + "state": "present", + "disabled": false, + "name": "pseudo queue", + "special_time": "reboot", + "job": "bash -c \"(test -p /var/pseudoqueue || mkfifo --mode=0600 /var/pseudoqueue) && (while true ; do bash < /var/pseudoqueue ; done)\"" + } + }, + { + "name": "tools | pseudo queue | run", + "become": true, + "ansible.builtin.shell": { + "cmd": "bash -c \"test -p /var/pseudoqueue || (mkfifo --mode=0600 /var/pseudoqueue && (while true ; do bash < /var/pseudoqueue ; done))\" &" + } + }, + { + "name": "setup auto renewal", + "become": true, + "ansible.builtin.cron": { + "state": "present", + "disabled": false, + "name": "TLS certificate for {{var_tlscert_acme_inwx_domain}}", + "minute": "0", + "hour": "2", + "day": "1", + "month": "*", + "weekday": "*", + "job": "echo '/usr/local/bin/tls-get {{var_tlscert_acme_inwx_domain}} --conf-path=/root/.tls-get-conf.json --target-directory={{var_tlscert_acme_inwx_ssl_directory}}' > /var/pseudoqueue" + } + }, + { + "name": "run", + "become": true, + "ansible.builtin.shell": { + "cmd": "/usr/local/bin/tls-get {{var_tlscert_acme_inwx_domain}} --conf-path=/root/.tls-get-conf.json --target-directory={{var_tlscert_acme_inwx_ssl_directory}}" } } ] diff --git a/todo.md b/todo.md index 70a9c86..b56dd59 100644 --- a/todo.md +++ b/todo.md @@ -1 +1,5 @@ - postgresql:hba-setup +- [Gitea](https://about.gitea.com/) +- [Vikunja](https://vikunja.io/) +- [Seafile](https://www.seafile.com/en/home/) +