This commit is contained in:
roydfalk 2024-06-09 10:58:24 +02:00
commit aac2308647
11 changed files with 633 additions and 235 deletions

View file

@ -5,6 +5,8 @@
"var_authelia_jwt_secret": "REPLACE_ME", "var_authelia_jwt_secret": "REPLACE_ME",
"var_authelia_users_file_path": "/var/authelia/users.yml", "var_authelia_users_file_path": "/var/authelia/users.yml",
"var_authelia_log_file_path": "/var/authelia/log.jsonl", "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_domain": "example.org",
"var_authelia_session_secret": "REPLACE_ME", "var_authelia_session_secret": "REPLACE_ME",
"var_authelia_storage_encryption_key": "REPLACE_ME", "var_authelia_storage_encryption_key": "REPLACE_ME",
@ -22,6 +24,7 @@
"var_authelia_storage_data_mariadb_schema": "authelia", "var_authelia_storage_data_mariadb_schema": "authelia",
"var_authelia_ntp_server": "time.cloudflare.com:123", "var_authelia_ntp_server": "time.cloudflare.com:123",
"var_authelia_password_reset_enabled": false, "var_authelia_password_reset_enabled": false,
"var_authelia_password_reset_custom_url": null,
"var_authelia_notification_mode": "smtp", "var_authelia_notification_mode": "smtp",
"var_authelia_notification_file_path": "/var/authelia/notifications", "var_authelia_notification_file_path": "/var/authelia/notifications",
"var_authelia_notification_smtp_host": "smtp.example.org", "var_authelia_notification_smtp_host": "smtp.example.org",

View file

@ -56,7 +56,7 @@
{% else %} {% else %}
"disable": true, "disable": true,
{% endif %} {% endif %}
"custom_url": "" "custom_url": "{{var_authelia_password_reset_custom_url}}"
}, },
"refresh_interval": "5m", "refresh_interval": "5m",
"file": { "file": {
@ -120,12 +120,18 @@
}, },
"session": { "session": {
"name": "authelia_session", "name": "authelia_session",
"domain": "{{var_authelia_session_domain}}",
"same_site": "lax", "same_site": "lax",
"secret": "{{var_authelia_session_secret}}", "secret": "{{var_authelia_session_secret}}",
"expiration": "1h", "expiration": "1h",
"inactivity": "5m", "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": { "regulation": {
"max_retries": 3, "max_retries": 3,

View file

@ -23,6 +23,14 @@
"type": "string", "type": "string",
"mandatory": false "mandatory": false
}, },
"domain": {
"type": "string",
"mandatory": false
},
"redirect_url": {
"type": "string",
"mandatory": false
},
"session_domain": { "session_domain": {
"type": "string", "type": "string",
"mandatory": false "mandatory": false
@ -91,6 +99,11 @@
"type": "boolean", "type": "boolean",
"mandatory": false "mandatory": false
}, },
"password_reset_custom_url": {
"nullable": true,
"type": "string",
"mandatory": false
},
"notification_mode": { "notification_mode": {
"type": "string", "type": "string",
"mandatory": false, "mandatory": false,

View file

@ -1,3 +1,3 @@
{ {
"var_nginx_auto_reload_interval": null
} }

View file

@ -9,6 +9,21 @@
] ]
} }
}, },
{
"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", "name": "restart service",
"become": true, "become": true,

View file

@ -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"
}
}
]

8
roles/nginx/vardef.json Normal file
View file

@ -0,0 +1,8 @@
{
"auto_reload_interval": {
"description": "in hours",
"nullable": true,
"type": "integer",
"mandatory": false
}
}

View file

@ -23,7 +23,8 @@
"pkg": [ "pkg": [
"vim", "vim",
"htop", "htop",
"tmux" "tmux",
"rsync"
] ]
} }
}, },

View file

@ -10,6 +10,32 @@ import argparse as _argparse
import pathlib as _pathlib import pathlib as _pathlib
import time as _time 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( def log(
messsage : str messsage : str
): ):
@ -28,30 +54,6 @@ def path_read(
return position 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( def http_call(
request : dict, request : dict,
) -> dict: ) -> dict:
@ -76,8 +78,84 @@ def http_call(
return response return response
_conf_data = { _conf_data = None
def conf_schema(
):
return {
"type": "object",
"properties": {
"url": { "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
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": { "test": {
"scheme": "https", "scheme": "https",
"host": "api.ote.domrobot.com", "host": "api.ote.domrobot.com",
@ -90,27 +168,34 @@ _conf_data = {
"port": 443, "port": 443,
"path": "jsonrpc/" "path": "jsonrpc/"
} }
},
"environment": "production",
"account": {
"username": None,
"password": None
} }
} |
conf_data_raw.get("url", {})
),
def conf_load( [
path : str lambda x: x.items(),
): lambda pairs: map(
global _conf_data lambda pair: (
if (not _os.path.exists(path)): pair[0],
pass {
else: "scheme": pair[1].get("scheme", "https"),
handle = open(path, "r") "host": pair[1]["host"],
content = handle.read() "port": pair[1].get("port", 443),
handle.close() "path": pair[1].get("path", "jsonrpc/"),
data = _json.loads(content) }
_conf_data = merge(_conf_data, data) ),
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( def conf_get(
@ -119,15 +204,6 @@ def conf_get(
global _conf_data global _conf_data
return path_read(_conf_data, path.split(".")) 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( def api_call(
environment : str, environment : str,
accesstoken : str, accesstoken : str,
@ -172,6 +248,9 @@ def api_call(
return result return result
'''
@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.login
'''
def api_macro_login( def api_macro_login(
environment : str, environment : str,
username : str, username : str,
@ -195,6 +274,9 @@ def api_macro_login(
return response["_accesstoken"] return response["_accesstoken"]
'''
@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.logout
'''
def api_macro_logout( def api_macro_logout(
environment : str, environment : str,
accesstoken : str accesstoken : str
@ -210,6 +292,9 @@ def api_macro_logout(
return None return None
'''
@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.info
'''
def api_macro_info( def api_macro_info(
environment : str, environment : str,
username : str, username : str,
@ -228,6 +313,9 @@ def api_macro_info(
return info return info
'''
@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.info
'''
def api_macro_list( def api_macro_list(
environment : str, environment : str,
username : str, username : str,
@ -248,12 +336,17 @@ def api_macro_list(
return info 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( def api_macro_save(
environment : str, environment : str,
username : str, username : str,
password : str, password : str,
domain : str, domain_base : str,
name : str, domain_path,
type_ : str, type_ : str,
content : str content : str
): ):
@ -264,12 +357,28 @@ def api_macro_save(
"nameserver", "nameserver",
"info", "info",
{ {
"domain": domain, "domain": domain_base,
} }
) )
matching = list( matching = list(
filter( 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"] info["record"]
) )
) )
@ -281,8 +390,8 @@ def api_macro_save(
"nameserver", "nameserver",
"createRecord", "createRecord",
{ {
"domain": domain, "domain": domain_base,
"name": name, "name": domain_path,
"type": type_, "type": type_,
"content": content, "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" description = "INWX CLI Frontend"
) )
argumentparser.add_argument( argument_parser.add_argument(
"-c", "-c",
"--conf", "--conf",
type = str,
dest = "conf", dest = "conf",
default = _os.path.join(str(_pathlib.Path.home()), ".inwx-conf.json"), default = _os.path.join(str(_pathlib.Path.home()), ".inwx-conf.json"),
metavar = "<conf>", metavar = "<conf>",
help = "path to configuration file", help = "path to configuration file",
) )
argumentparser.add_argument( argument_parser.add_argument(
"-e", "-e",
"--environment", "--environment",
type = str,
dest = "environment", dest = "environment",
metavar = "<environment>", metavar = "<environment>",
default = None, 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", "-u",
"--username", "--username",
type = str,
dest = "username", dest = "username",
metavar = "<username>", metavar = "<username>",
default = None, default = None,
help = "username; overwrites the configuration value", help = "username; overwrites the configuration value",
) )
argumentparser.add_argument( argument_parser.add_argument(
"-p", "-p",
"--password", "--password",
type = str,
dest = "password", dest = "password",
metavar = "<password>", metavar = "<password>",
default = None, default = None,
help = "password; overwrites the configuration value", help = "password; overwrites the configuration value",
) )
''' argument_parser.add_argument(
argumentparser.add_argument(
"-d", "-d",
"--domain", "--domain",
type = str,
dest = "domain", dest = "domain",
default = None, default = None,
metavar = "<domain>", metavar = "<domain>",
help = "the domain to work with" help = "the domain to work with"
) )
''' argument_parser.add_argument(
argumentparser.add_argument( "-t",
"--type",
type = str,
dest = "type",
default = None,
metavar = "<type>",
help = "the record type (A, AAAA, TXT, …)"
)
argument_parser.add_argument(
"-v",
"--value",
type = str,
dest = "value",
default = None,
metavar = "<value>",
help = "value for the record"
)
argument_parser.add_argument(
"-x", "-x",
"--challenge-prefix", "--challenge-prefix",
type = str,
dest = "challenge_prefix", dest = "challenge_prefix",
metavar = "<challenge-prefix>", metavar = "<challenge-prefix>",
default = "_acme-challenge", default = "_acme-challenge",
help = "which subdomain to use for ACME challanges", help = "which subdomain to use for ACME challanges",
) )
argumentparser.add_argument( argument_parser.add_argument(
"-w", "-w",
"--delay", "--delay",
dest = "delay",
type = float, type = float,
dest = "delay",
default = 60.0, default = 60.0,
metavar = "<delay>", metavar = "<delay>",
help = "seconds to wait at end of certbot auth hook", help = "seconds to wait at end of certbot auth hook",
) )
argumentparser.add_argument( argument_parser.add_argument(
"action",
type = str, type = str,
choices = ["info", "list", "save", "certbot-hook"], dest = "action",
choices = [
"conf-schema",
"info",
"list",
"save",
"delete",
"certbot-hook",
],
metavar = "<action>", metavar = "<action>",
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": ["<domain>"]},
{"name": "save", "requirements": ["<domain>", "<type>", "<value>"]},
{"name": "delete", "requirements": ["<domain>"]},
{"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"]),
}
) )
argumentparser.add_argument( ),
"parameter", }
nargs = "*", ),
type = str, x
metavar = "<parameters>", ),
help = "action specific parameters", " | ".join,
]
) )
arguments = argumentparser.parse_args() }
return arguments ),
)
args = argument_parser.parse_args()
## conf
conf_load(args.conf)
def main( ## vars
): environment = (args.environment or conf_get("environment"))
arguments = args() 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]))
conf_load(arguments.conf) ## exec
if (not (arguments.environment is None)): conf_set("environment", arguments.environment) if (args.action == "conf-schema"):
if (not (arguments.username is None)): conf_set("account.username", arguments.username) print(_json.dumps(conf_schema(), indent = "\t"))
if (not (arguments.password is None)): conf_set("account.password", arguments.password) elif (args.action == "info"):
if (account_username is None):
if (arguments.action == "info"): raise ValueError("account username required")
else:
if (account_password is None):
raise ValueError("account password required")
else:
result = api_macro_info( result = api_macro_info(
conf_get("environment"), environment,
conf_get("account.username"), account_username,
conf_get("account.password") account_password
) )
print(_json.dumps(result, indent = "\t")) print(_json.dumps(result, indent = "\t"))
elif (arguments.action == "list"): elif (args.action == "list"):
domain = arguments.parameter[0] 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( result = api_macro_list(
conf_get("environment"), environment,
conf_get("account.username"), account_username,
conf_get("account.password"), account_password,
domain domain_base
) )
print(_json.dumps(result, indent = "\t")) print(_json.dumps(result, indent = "\t"))
elif (arguments.action == "save"): elif (args.action == "save"):
domain = arguments.parameter[0] if (account_username is None):
name = arguments.parameter[1] raise ValueError("account username required")
type_ = arguments.parameter[2] else:
content = arguments.parameter[3] 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( api_macro_save(
conf_get("environment"), environment,
conf_get("account.username"), account_username,
conf_get("account.password"), account_password,
domain, domain_base,
name, domain_path,
type_, args.type,
content args.value
) )
# print(_json.dumps(result, indent = "\t")) elif (args.action == "delete"):
elif (arguments.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:
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_full_parts = _os.environ["CERTBOT_DOMAIN"].split(".")
account = ".".join(domain_full_parts[-2:]) domain_base = ".".join(domain_full_parts[-2:])
concern = ".".join(domain_full_parts[:-2]) domain_path_stripped = ".".join(domain_full_parts[:-2])
domain = account domain_path = (args.challenge_prefix + "." + domain_path_stripped)
name = (arguments.challenge_prefix + "." + concern)
type_ = "TXT" type_ = "TXT"
content = _os.environ["CERTBOT_VALIDATION"] content = _os.environ["CERTBOT_VALIDATION"]
api_macro_save( api_macro_save(
conf_get("environment"), environment,
conf_get("account.username"), account_username,
conf_get("account.password"), account_password,
domain, domain_base,
name, domain_path,
type_, type_,
content content
) )
_time.sleep(arguments.delay) _time.sleep(args.delay)
# print(_json.dumps(result, indent = "\t")) # print(_json.dumps(result, indent = "\t"))
else: else:
log("unhandled action '%s'" % (arguments.action, )) log("unhandled action '%s'" % (args.action, ))
try: try:
main() main()
except ValueError as error: except ValueError as error:
_sys.stderr.write(str(error) + "\n") _sys.stderr.write("-- %s\n" % str(error))

View file

@ -6,17 +6,19 @@
"update_cache": true, "update_cache": true,
"pkg": [ "pkg": [
"openssl", "openssl",
"python3-cryptography" "python3-cryptography",
"certbot"
] ]
} }
}, },
{ {
"name": "directories | ssl", "name": "directories",
"become": true, "become": true,
"loop": [ "loop": [
"{{var_tlscert_acme_inwx_ssl_directory}}/private", "{{var_tlscert_acme_inwx_ssl_directory}}/private",
"{{var_tlscert_acme_inwx_ssl_directory}}/csr", "{{var_tlscert_acme_inwx_ssl_directory}}/csr",
"{{var_tlscert_acme_inwx_ssl_directory}}/certs", "{{var_tlscert_acme_inwx_ssl_directory}}/certs",
"{{var_tlscert_acme_inwx_ssl_directory}}/chains",
"{{var_tlscert_acme_inwx_ssl_directory}}/fullchains" "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains"
], ],
"ansible.builtin.file": { "ansible.builtin.file": {
@ -25,54 +27,7 @@
} }
}, },
{ {
"name": "directories | Let's Encrypt account key", "name": "tools | inwx",
"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",
"become": true, "become": true,
"ansible.builtin.copy": { "ansible.builtin.copy": {
"src": "inwx", "src": "inwx",
@ -81,33 +36,60 @@
} }
}, },
{ {
"name": "dns challenge | execute", "name": "tools | tls-get | script",
"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",
"become": true, "become": true,
"community.crypto.acme_certificate": { "ansible.builtin.copy": {
"acme_version": 2, "src": "tls-get",
"acme_directory": "https://acme-v02.api.letsencrypt.org/directory", "dest": "/usr/local/bin/tls-get",
"account_email": "{{var_tlscert_acme_inwx_acme_account_email}}", "mode": "a+x"
"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", "name": "tools | tls-get | conf",
"dest": "{{var_tlscert_acme_inwx_ssl_directory}}/certs/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", "become": true,
"fullchain_dest": "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", "ansible.builtin.template": {
"data": "{{temp_acme_data}}" "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}}"
} }
} }
] ]

View file

@ -1 +1,5 @@
- postgresql:hba-setup - postgresql:hba-setup
- [Gitea](https://about.gitea.com/)
- [Vikunja](https://vikunja.io/)
- [Seafile](https://www.seafile.com/en/home/)