{#
# Copyright (c) 2023-2024 Cedrik Pischem
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#}

# DO NOT EDIT THIS FILE -- OPNsense auto-generated file

{% set generalSettings = helpers.getNodeByTag('Pischem.caddy.general') %}

# Global Options
{
    {#
    #   Section: Global Log Settings
    #   Purpose: Sets up global log settings. The time format and unix socket make Caddy compatible
    #            with the syslog-ng instance running on the OPNsense.
    #}
    log {
        {% if generalSettings.LogAccessPlain|default("0") == "0" %}
            {% for reverse in helpers.toList('Pischem.caddy.reverseproxy.reverse') %}
                {% if reverse.enabled|default("0") == "1" and reverse.AccessLog|default("0") == "1" %}
                    include http.log.access.{{ reverse['@uuid'] }}
                {% endif %}
            {% endfor %}
        {% endif %}
        output net unixgram//var/caddy/var/run/log {
        }
        format json {
            time_format rfc3339
        }
        {% if generalSettings.LogLevel %}
            level {{ generalSettings.LogLevel }}
        {% endif %}
    }

    {# Change default ports on demand #}
    {% set httpPort = generalSettings.HttpPort %}
    {% set httpsPort = generalSettings.HttpsPort %}

    {% if httpPort %}
        http_port {{ httpPort }}
    {% endif %}
    {% if httpsPort %}
        https_port {{ httpsPort }}
    {% endif %}

    {#
    #   Section: Global Trusted Proxy and Credential Logging
    #   Purpose: The trusted proxy section is important when using CDNs so that headers are trusted.
    #            Credential logging is useful for troubleshooting basic auth.
    #}
    {% set accessListUuid = generalSettings.accesslist %}
    {% set logCredentials = generalSettings.LogCredentials %}

    {% set hasAccessList = false %}
    {% set hasLogCredentials = false %}

    {% if accessListUuid %}
        {% set accessList = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', accessListUuid) | first %}
        {% if accessList %}
            {% set hasAccessList = true %}
        {% endif %}
    {% endif %}

    {% if logCredentials == '1' %}
        {% set hasLogCredentials = true %}
    {% endif %}

    {% if hasAccessList or hasLogCredentials %}
    servers {
        {% if hasAccessList %}
            trusted_proxies static {{ accessList.clientIps.split(',') | join(' ') }}
        {% endif %}
        {% if hasLogCredentials %}
            log_credentials
        {% endif %}
    }
    {% endif %}

    {#
    #   Section: Dynamic DNS Global Configuration
    #   Purpose: Sets up global configuration for Dynamic DNS. Caddy needs to be compiled with
    #            https://github.com/mholt/caddy-dynamicdns and https://github.com/caddy-dns. Otherwise the
    #            generated Caddyfile won't run. Each DNS Provider that is added below has to be compiled in.
    #            Some Providers don't support setting A and AAAA-Records, like acmedns.
    #            Most need specific configurations. Since only one provider can be used at the same time,
    #            they all share the same fields for configuration.
    #   Parameters:
    #   - @param dnsProvider (string): Specifies the DNS provider for DDNS updates.
    #   - @param dnsApiKey (string): The API key for authenticating with the DNS provider.
    #   - @param dnsSecretApiKey (string): A secret API key or token for additional authentication security.
    #   - @param dnsOptionalField1 to 4 (string): Optional configuration field for the DNS provider.
    #   - @param dynDnsSimpleHttp (string): URL for a simple HTTP-based service to discover the server's public IP.
    #   - @param dynDnsInterface (string): Network interface(s) to use for IP discovery.
    #   - @param dynDnsCheckInterval (integer): Interval in seconds to check for IP changes. Can be empty for defaults.
    #   - @param dynDnsIpVersions (string): The IP version(s) (IPv4, IPv6) for the DDNS update.
    #   - @param dynDnsTtl (integer): Time-To-Live for the DNS records, in seconds. Can be empty for defaults.
    #   - @param dynDnsDomains (list): Domains and subdomains list for which DDNS updates are enabled.
    #   - @param dynDnsUpdateOnly (boolean): If set, only updates DNS records, not creating new ones.
    #}
    {% set dnsProvider = helpers.toList('Pischem.caddy.general.TlsDnsProvider') | first %}
    {% set dnsApiKey = generalSettings.TlsDnsApiKey %}
    {% set dnsSecretApiKey = generalSettings.TlsDnsSecretApiKey %}
    {% set dnsOptionalField1 = generalSettings.TlsDnsOptionalField1 %}
    {% set dnsOptionalField2 = generalSettings.TlsDnsOptionalField2 %}
    {% set dnsOptionalField3 = generalSettings.TlsDnsOptionalField3 %}
    {% set dnsOptionalField4 = generalSettings.TlsDnsOptionalField4 %}
    {% set dynDnsSimpleHttp = generalSettings.DynDnsSimpleHttp %}
    {% set dynDnsInterface = generalSettings.DynDnsInterface %}
    {% set dynDnsUpdateOnly = generalSettings.DynDnsUpdateOnly %}
    {% set dynDnsCheckInterval = generalSettings.DynDnsInterval %}
    {% set dynDnsIpVersions = generalSettings.DynDnsIpVersions %}
    {% set dynDnsTtl = generalSettings.DynDnsTtl %}
    {% set dynDnsDomains = [] %}

    {% for reverse in helpers.toList('Pischem.caddy.reverseproxy.reverse') %}
        {% if reverse.enabled|default("0") == "1" and reverse.DynDns|default("0") == "1" %}
            {% set cleanedDomain = reverse.FromDomain | replace("*.","") %}
            {% if reverse.FromDomain.startswith("*.") %}
                {% do dynDnsDomains.append(cleanedDomain + " *") %}
            {% else %}
                {% do dynDnsDomains.append(cleanedDomain + " @") %}
            {% endif %}
        {% endif %}

        {% for subdomain in helpers.toList('Pischem.caddy.reverseproxy.subdomain') %}
            {% if subdomain.enabled|default("0") == "1" and subdomain.DynDns|default("0") == "1" and subdomain.reverse == reverse['@uuid'] %}
                {% set fullSubdomain = subdomain.FromDomain %}
                {% set baseDomain = fullSubdomain.split('.')[1:] | join('.') %}
                {% set subDomainPart = fullSubdomain.split('.')[0] %}
                {% set subdomainEntry = baseDomain + " " + subDomainPart %}
                {% do dynDnsDomains.append(subdomainEntry) %}
            {% endif %}
        {% endfor %}
    {% endfor %}

    {#
    # Define special DNS Providers that have more than one API key, or special requirements that do not allow the use of the default.
    # The same providers have to be added to "OPNsense/Caddy/includeDnsProvider", best in the same order as in this array for maintainability.
    # For a new provider to work, it has to be compiled into the caddy binary.
    #}
    {% set dnsProviderSpecialConfig = ['duckdns', 'porkbun', 'desec', 'route53', 'acmedns', 'googleclouddns', 'azure', 'ovh', 'namecheap', 'powerdns', 'ddnss', 'linode', 'tencentcloud', 'dinahosting', 'hexonet', 'mailinabox', 'netcup', 'rfc2136', 'dnsmadeeasy', 'civo', 'scaleway', 'acmeproxy', 'inwx', 'namedotcom', 'easydns', 'directadmin'] %}

    {# Conditionally add the dynamic_dns section, acmedns provider is special, it does not support dynamic_dns. #}
    {% if dnsProvider and dynDnsDomains|length > 0 and dnsProvider != "acmedns" %}
        dynamic_dns {
            {# duckdns provider is special, it has a different configuration for dynamic dns than for the dns-01 challenge. #}
            {% if dnsProvider in dnsProviderSpecialConfig and dnsProvider != "duckdns" %}
                provider {{ dnsProvider }} {
                    {% include "OPNsense/Caddy/includeDnsProvider" %}
                }
            {% else %}
                {# Other DNS Providers fall under this default #}
                provider {{ dnsProvider }} {{ dnsApiKey }}
            {% endif %}
            domains {
                {% for domain in dynDnsDomains %}
                    {{ domain }}
                {% endfor %}
            }
        {% if dynDnsSimpleHttp %}
            ip_source simple_http {{ dynDnsSimpleHttp }}
        {% endif %}
        {% if dynDnsInterface %}
            {% set physicalInterfaceNames = [] %}
            {% for intfName in dynDnsInterface.split(',') %}
                {% do physicalInterfaceNames.append(helpers.physical_interface(intfName)) %}
            {% endfor %}
            ip_source interface {{ physicalInterfaceNames | join(',') }}
        {% endif %}
        {% if dynDnsCheckInterval %}
            check_interval {{ dynDnsCheckInterval }}s
        {% endif %}
        {% if dynDnsIpVersions %}
            versions {{ dynDnsIpVersions }}
        {% endif %}
        {% if dynDnsTtl %}
            ttl {{ dynDnsTtl }}s
        {% endif %}
        {% if dynDnsUpdateOnly|default("0") == "1" %}
            update_only
        {% endif %}
    }
    {% endif %}

    {#
    #   Section: ACME Email, Auto HTTPS selection and global import statement
    #   Purpose: The ACME email is optional for receiving certificate notices.
    #            Auto HTTPS is optional, the default is on (which means the section is empty).
    #            The import statement is for user specific configuration out of scope of this template.
    #}
    {% set emailValue = helpers.toList('Pischem.caddy.general.TlsEmail') | first %}
    {% if emailValue %}
        email {{ emailValue }}
    {% endif %}
    {% set autoHttpsValue = helpers.toList('Pischem.caddy.general.TlsAutoHttps') | first %}
    {% if autoHttpsValue %}
        auto_https {{ autoHttpsValue }}
    {% endif %}
    {#
    # Important: Grace Period influences how fast the server can finish reloads with open connections, by forcing termination.
    # Default of Caddy is to wait for all connections to close before allowing reload, meaning the higher the value, the longer applies take.
    #}
    grace_period {{ generalSettings.GracePeriod }}s
    import /usr/local/etc/caddy/caddy.d/*.global
}

# Reverse Proxy Configuration

{#
#   Section: HTTP-01 Challenge Redirection
#   Purpose: A small premade reverse_proxy section
#            that can redirect the HTTP-01 challenge to a different webserver.
#}
{% for reverse in helpers.toList('Pischem.caddy.reverseproxy.reverse') %}
    {% if reverse.enabled|default("0") == "1" and reverse.AcmePassthrough %}
        # HTTP-01 challenge redirection for domain: "{{ reverse['@uuid'] }}"
        http://{{ reverse.FromDomain|default("") }} {
            handle /.well-known/acme-challenge/* {
                reverse_proxy {{ reverse.AcmePassthrough }}
            }
            handle {
                redir https://{host}{uri} 308
            }
        }
    {% endif %}
{% endfor %}
{# Process redirection for subdomains afterwards, since a redirection of a wildcard domain has to match before them. #}
{% for subdomain in helpers.toList('Pischem.caddy.reverseproxy.subdomain') %}
    {% if subdomain.enabled|default("0") == "1" and subdomain.AcmePassthrough %}
        # HTTP-01 challenge redirection for subdomain: "{{ subdomain['@uuid'] }}"
        http://{{ subdomain.FromDomain|default("") }} {
            handle /.well-known/acme-challenge/* {
                reverse_proxy {{ subdomain.AcmePassthrough }}
            }
            handle {
                redir https://{host}{uri} 308
            }
        }
    {% endif %}
{% endfor %}

{#
#   Macro: tls_configuration
#   Purpose: Configures TLS settings based on the DNS provider, API keys, and optional fields.
#            Sets up the Caddyfile to update TXT Records with the chosen DNS Provider and receive
#            certificates with the DNS-01 challenge. Refer to Dynamic DNS section for more details.
#   Parameters:
#   - @param dnsProvider (string): The DNS provider used for the DNS challenge.
#   - @param dnsApiKey (string): API key for the DNS provider, essential for authentication.
#   - @param customCert (string, optional): The config extracted name of a certificate.
#   - @param dnsChallenge (boolean): Indicates if a DNS challenge is used for certificate authentication.
#   - @param dnsSecretApiKey (string, optional): A secret API key or token for additional security, depending on the provider.
#   - @param TlsDnsOptionalField1 to 4 (string, optional): Additional fields for specific DNS provider configurations.
#}
{% macro tls_configuration(dnsProvider, dnsApiKey, customCert, dnsChallenge, dnsSecretApiKey, TlsDnsOptionalField1, TlsDnsOptionalField2, TlsDnsOptionalField3, TlsDnsOptionalField4) %}
    {% if dnsChallenge == "1" and dnsProvider %}
        {% if dnsProvider in dnsProviderSpecialConfig %}
            tls {
                dns {{ dnsProvider }} {
                    {% include "OPNsense/Caddy/includeDnsProvider" %}
                }
            }
        {% else %}
            tls {
                {# Other DNS Providers fall under this default #}
                dns {{ dnsProvider }} {{ dnsApiKey }}
            }
        {% endif %}
    {% endif %}
    {% if customCert %}
        tls /var/db/caddy/data/caddy/certificates/temp/{{ customCert }}.pem /var/db/caddy/data/caddy/certificates/temp/{{ customCert }}.key
    {% endif %}
{% endmacro %}

{#
#   Macro: header_manipulation
#   Purpose: Customizes HTTP headers for requests or responses; to add, remove, or modify headers.
#            It uses a 'handle' object that specifies which headers to manipulate based on their @UUIDs.
#            Each handle can have multiple of these HTTP headers assigned.
#   Parameters:
#   @param handle (object):
#       - @uuid (string)
#       - HeaderUpDown (string): Determines the direction of the header.
#       - HeaderType (string): Specifies the name of the header.
#       - HeaderValue (string, optional): The new value to set for the header, if any.
#       - HeaderReplace (string, optional): Specifies a value to replace in the header.
#}
{% macro header_manipulation(handle) %}
    {% if handle.header %}
        {% for header_uuid in handle.header.split(',') %}
            {% set header = helpers.toList('Pischem.caddy.reverseproxy.header') | selectattr('@uuid', 'equalto', header_uuid) | first %}
            {# Generate directive only if HeaderUpDown and HeaderType are present #}
            {% if header.HeaderUpDown and header.HeaderType %}
                {# Prepare variables, making HeaderValue and HeaderReplace optional #}
                {% set header_value = header.HeaderValue | default('') %}
                {% set header_replace = header.HeaderReplace | default('') %}
                {# Adjust output formatting based on the presence and style of HeaderValue #}
                {% if header.HeaderReplace and header.HeaderValue %}
                    {% if header_value.startswith('{') %}
                        {{ header.HeaderUpDown }} {{ header.HeaderType }} {{ header_value }} "{{ header_replace }}"
                    {% else %}
                        {{ header.HeaderUpDown }} {{ header.HeaderType }} "{{ header_value }}" "{{ header_replace }}"
                    {% endif %}
                {% elif header.HeaderValue %}
                    {% if header_value.startswith('{') %}
                        {{ header.HeaderUpDown }} {{ header.HeaderType }} {{ header_value }}
                    {% else %}
                        {{ header.HeaderUpDown }} {{ header.HeaderType }} "{{ header_value }}"
                    {% endif %}
                {% else %}
                    {{ header.HeaderUpDown }} {{ header.HeaderType }}
                {% endif %}
            {% endif %}
        {% endfor %}
    {% endif %}
{% endmacro %}

{#
#   Macro: reverse_proxy_configuration
#   Purpose: Sets up the handle with the reverse proxy configurations. The TLS Settings are generated here for the Upstream.
#   Integrated Macros: header_manipulation
#   Parameters:
#   @param handle (object):
#       - @uuid (string)
#       - HandleType (string): Specifies the handling strategy.
#       - HandlePath (string, optional): The path the handle should match on.
#       - ToDomain (string): Target domain for the reverse proxy.
#       - ToPort (string, optional): Target port on the ToDomain.
#       - ToPath (string, optional): Destination path on the ToDomain.
#       - HttpTls (boolean, optional): Enable TLS for the connection.
#       - HttpNtlm (boolean, optional): Enable NTLM authentication for the connection. Not all HTTP options apply to NTLM.
#       - HttpTlsInsecureSkipVerify (boolean, optional): If true, the server's SSL certificate is not verified.
#       - HttpTlsTrustedCaCerts (string, optional): The config extracted name of a CA certificate.
#       - HttpTlsServerName (string, optional): Specifies the server name for the TLS handshake.
#       - PassiveHealthFailDuration (integer, optional): Enables passive health checks when set > 0.
#       - HttpVersion (string, optional): Choose HTTP version. Empty (default) is 1.1 and 2.
#       - HttpKeepalive (string, optional): Keeaplive is either off (0) or a value. Empty (default) 120s.
#}
{% macro reverse_proxy_configuration(handle) %}
    {{ handle.HandleType }} {{ handle.HandlePath|default("") }} {
        {% if handle.ForwardAuth|default("0") == "1" %}
            {% include "OPNsense/Caddy/includeAuthProvider" %}
        {% endif %}
        {% if handle.ToPath|default("") != "" %}
            rewrite * {{ handle.ToPath }}{uri}
        {% endif %}
        reverse_proxy {% for domain in handle.ToDomain.split(',') %}
            {# Check if the domain is IPv6 and wrap in square brackets if necessary #}
            {% set is_ipv6 = (':' in domain and domain.count(':') >= 2) %}
            {# For each domain/IP, append the port if it's specified, followed by a space #}
            {{- '[' if is_ipv6 else '' -}}{{ domain }}{{ ']' if is_ipv6 else '' -}}{% if handle.ToPort %}:{{ handle.ToPort }}{% endif %}{% if not loop.last %} {% endif %}
        {% endfor %} {
            {{ header_manipulation(handle) }}
            {% if handle.PassiveHealthFailDuration|default("") %}
                fail_duration {{ handle.PassiveHealthFailDuration }}s
            {% endif %}
            {% if handle.HttpTls|default("0") == "1" or handle.HttpTlsInsecureSkipVerify|default("0") == "1" or handle.HttpTlsTrustedCaCerts or handle.HttpTlsServerName or handle.HttpVersion or handle.HttpKeepalive %}
                {% if handle.HttpNtlm|default("0") == "1" %}
                transport http_ntlm {
                    {% if handle.HttpTls|default("0") == "1" %}
                        tls
                    {% endif %}
                    {% if handle.HttpTlsInsecureSkipVerify|default("0") == "1" %}
                        tls_insecure_skip_verify
                    {% endif %}
                    {% if handle.HttpTlsTrustedCaCerts %}
                        tls_trust_pool file /var/db/caddy/data/caddy/certificates/temp/{{ handle.HttpTlsTrustedCaCerts }}.pem
                    {% endif %}
                    {% if handle.HttpTlsServerName %}
                        tls_server_name {{ handle.HttpTlsServerName }}
                    {% endif %}
                }
                {% else %}
                transport http {
                    {# The model does not allow to set a single number as option directly, so we have to map them. #}
                    {% set version_map = {'http1': 1.1, 'http2': 2, 'http3': 3} %}
                    {% if handle.HttpVersion %}
                        versions {{ version_map[handle.HttpVersion] }}
                    {% endif %}
                    {% if handle.HttpKeepalive %}
                        {% if handle.HttpKeepalive == "0" %}
                            keepalive off
                        {% else %}
                            keepalive {{ handle.HttpKeepalive }}s
                        {% endif %}
                    {% endif %}
                    {% if handle.HttpTls|default("0") == "1" %}
                        tls
                    {% endif %}
                    {% if handle.HttpTlsInsecureSkipVerify|default("0") == "1" %}
                        tls_insecure_skip_verify
                    {% endif %}
                    {% if handle.HttpTlsTrustedCaCerts %}
                        tls_trust_pool file /var/db/caddy/data/caddy/certificates/temp/{{ handle.HttpTlsTrustedCaCerts }}.pem
                    {% endif %}
                    {% if handle.HttpTlsServerName %}
                        tls_server_name {{ handle.HttpTlsServerName }}
                    {% endif %}
                }
                {% endif %}
            {% endif %}
        }
    }
{% endmacro %}

{#
#   Macro: access_list_configuration
#   Purpose: Defines access lists based on client IP addresses. The standard logic is "allow these IP addresses, deny all others."
#            A handle with an @ matcher is created that will put the reverse_proxy_configuration inside. That means, the traffic will
#            only get to the reverse proxy, when the access list matches. Invert is also possible, to explicitely deny IPs.
#            The assembly is handled by the "Section: Reverse Proxy Configurations".
#   Parameters:
#   @param accesslist (object):
#       - uuid (string)
#       - clientIps (string): A comma-separated list of client IP addresses
#       - invert (boolean): A flag that inverts the logic of the access list
#}
{% macro access_list_configuration(accesslist, invert) %}
    {% set client_ips = accesslist.clientIps.split(',') %}
    {% set client_ips_space_separated = client_ips | join(' ') %}
    @{{ accesslist['@uuid'] }} {
        {{ 'not' if invert else '' }} client_ip {{ client_ips_space_separated }}
    }
{% endmacro %}

{#
#   Macro: basicauth_configuration
#   Purpose: Implements basic authentication with a username and password for access.
#   Parameters:
#   @param basicauth_uuids (string): A comma-separated list of UUIDs, each UUID corresponding to
#                                     a specific user credentials (username and password).
#       - @uuid (string)
#       - basicauthuser (string): The username required for authentication.
#       - basicauthpass (string): The password associated with the username.
#}
{% macro basicauth_configuration(basicauth_uuids) %}
    {% if basicauth_uuids %}
    basic_auth {
        {% for uuid in basicauth_uuids.split(',') %}
            {% set basicauth = helpers.toList('Pischem.caddy.reverseproxy.basicauth') | selectattr('@uuid', 'equalto', uuid) | first %}
            {% if basicauth %}
                {{ basicauth.basicauthuser }} {{ basicauth.basicauthpass }}
            {% endif %}
        {% endfor %}
    }
    {% endif %}
{% endmacro %}

{#
#   Section: Reverse Proxy Configurations
#   Purpose: Assembles reverse proxy configurations using predefined macros.
#            This is the main logic of the whole template, handle with care.
#   Macros Used:
#   - tls_configuration
#   - basicauth_configuration
#   - access_list_configuration
#   - reverse_proxy_configuration
#   - indirect: header_manipulation
#   Important Details:
#   - Order of Path specific Handles - Prioritizes order of specific path handles over catch-all handles.
#   - Order of Wildcard Domains and Subdomains: Handles for wildcard domains come after all subdomains.
#}
{% for reverse in helpers.toList('Pischem.caddy.reverseproxy.reverse') %}
    {% if reverse.enabled|default("0") == "1" %}
        # Reverse Proxy Domain: "{{ reverse['@uuid'] }}"
        {# The default are encrypted connections, uncencrypted connections have to render http:// #}
        {% if reverse.DisableTls|default("0") == "1" %}http://{% endif %}{{ reverse.FromDomain|default("") }}{% if reverse.FromPort %}:{{ reverse.FromPort }}{% endif %} {
            {% if reverse.AccessLog|default("0") == "1" %}
                {% if generalSettings.LogAccessPlain|default("0") == "0" %}
                    log {{ reverse['@uuid'] }}
                {% else %}
                    log {
                        output file /var/log/caddy/access/{{ reverse['@uuid'] }}.log {
                            roll_keep_for {{ generalSettings.LogAccessPlainKeep|default("10") }}d
                        }
                    }
                {% endif %}
            {% endif %}
            {% set customCert = reverse.CustomCertificate|default("") %}
            {% set dnsChallenge = reverse.DnsChallenge|default("0") %}
            {{ tls_configuration(dnsProvider, dnsApiKey, customCert, dnsChallenge, dnsSecretApiKey, TlsDnsOptionalField1, TlsDnsOptionalField2, TlsDnsOptionalField3, TlsDnsOptionalField4) }}

            {% if not reverse.accesslist %}
                {% set basicauth_uuids = reverse.basicauth %}
                {{ basicauth_configuration(basicauth_uuids) }}
            {% endif %}

            {% for subdomain in helpers.toList('Pischem.caddy.reverseproxy.subdomain') %}
                {% if subdomain.enabled|default("0") == "1" and subdomain.reverse == reverse['@uuid'] %}
                    @{{ subdomain['@uuid'] }} {
                        host {{ subdomain.FromDomain }}
                    }
                    handle @{{ subdomain['@uuid'] }} {

                        {% if not subdomain.accesslist %}
                            {% set subdomain_basicauth_uuids = subdomain.basicauth %}
                            {{ basicauth_configuration(subdomain_basicauth_uuids) }}
                        {% endif %}

                        {% if subdomain.accesslist %}
                            {% set accesslist = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', subdomain.accesslist) | first %}
                            {{ access_list_configuration(accesslist, accesslist.accesslistInvert|default("0") == "1") }}
                            handle @{{ accesslist['@uuid'] }} {

                                {% set subdomain_basicauth_uuids = subdomain.basicauth %}
                                {{ basicauth_configuration(subdomain_basicauth_uuids) }}

                                {% set subdomain_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('subdomain', 'equalto', subdomain['@uuid']) | list %}
                                {% for handle in subdomain_handles %}
                                    {% if handle.enabled|default("0") == "1" and handle.HandlePath %}
                                        {{ reverse_proxy_configuration(handle) }}
                                    {% endif %}
                                {% endfor %}
                            {% for handle in subdomain_handles %}
                                {% if handle.enabled|default("0") == "1" and not handle.HandlePath %}
                                    {{ reverse_proxy_configuration(handle) }}
                                {% endif %}
                            {% endfor %}
                            }
                        {% else %}
                            {% set subdomain_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('subdomain', 'equalto', subdomain['@uuid']) | list %}
                            {% for handle in subdomain_handles %}
                                {% if handle.enabled|default("0") == "1" and handle.HandlePath %}
                                    {{ reverse_proxy_configuration(handle) }}
                                {% endif %}
                            {% endfor %}
                            {% for handle in subdomain_handles %}
                                {% if handle.enabled|default("0") == "1" and not handle.HandlePath %}
                                    {{ reverse_proxy_configuration(handle) }}
                                {% endif %}
                            {% endfor %}
                        {% endif %}

                        {% if subdomain.accesslist %}
                            {% if accesslist.HttpResponseCode or accesslist.HttpResponseMessage %}
                                respond {{ '"' + accesslist.HttpResponseMessage|default('') + '"' if accesslist.HttpResponseMessage else '' }} {{ accesslist.HttpResponseCode|default(403) }}
                            {% elif Pischem.caddy.general.abort|default("0") == "1" %}
                                abort
                            {% endif %}
                        {% else %}
                            {% if Pischem.caddy.general.abort|default("0") == "1" %}
                                abort
                            {% endif %}
                        {% endif %}
                    }
                {% endif %}
            {% endfor %}

            {% if reverse.accesslist %}
                {% set accesslist = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', reverse.accesslist) | first %}
                {{ access_list_configuration(accesslist, accesslist.accesslistInvert|default("0") == "1") }}
                handle @{{ accesslist['@uuid'] }} {

                    {% set basicauth_uuids = reverse.basicauth %}
                    {{ basicauth_configuration(basicauth_uuids) }}

                    {% set wildcard_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('reverse', 'equalto', reverse['@uuid']) | selectattr('subdomain', 'undefined') | list %}
                        {% for handle in wildcard_handles %}
                            {% if handle.enabled|default("0") == "1" and handle.HandlePath %}
                                {{ reverse_proxy_configuration(handle) }}
                            {% endif %}
                        {% endfor %}
                    {% for handle in wildcard_handles %}
                        {% if handle.enabled|default("0") == "1" and not handle.HandlePath %}
                            {{ reverse_proxy_configuration(handle) }}
                        {% endif %}
                    {% endfor %}
                }
            {% else %}
                {% set wildcard_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('reverse', 'equalto', reverse['@uuid']) | selectattr('subdomain', 'undefined') | list %}
                {% for handle in wildcard_handles %}
                    {% if handle.enabled|default("0") == "1" and handle.HandlePath %}
                        {{ reverse_proxy_configuration(handle) }}
                    {% endif %}
                {% endfor %}
                {% for handle in wildcard_handles %}
                    {% if handle.enabled|default("0") == "1" and not handle.HandlePath %}
                        {{ reverse_proxy_configuration(handle) }}
                    {% endif %}
                {% endfor %}
            {% endif %}

            {% set accesslist = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', reverse.accesslist) | first %}
            {% if accesslist %}
                {% if accesslist.HttpResponseCode or accesslist.HttpResponseMessage %}
                    respond {{ '"' + accesslist.HttpResponseMessage|default('') + '"' if accesslist.HttpResponseMessage else '' }} {{ accesslist.HttpResponseCode|default(403) }}
                {% elif Pischem.caddy.general.abort|default("0") == "1" %}
                    abort
                {% endif %}
            {% else %}
                {% if Pischem.caddy.general.abort|default("0") == "1" %}
                    abort
                {% endif %}
            {% endif %}
        }
    {% endif %}
{% endfor %}

import /usr/local/etc/caddy/caddy.d/*.conf
