{#
# 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') %}

{# Print as comments if Caddy runs as root or www user, as information in support cases. #}
{% if generalSettings.DisableSuperuser|default("0") == "1" %}
# caddy_user=www
{% else %}
# caddy_user=root
{% endif %}

# 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/run/caddy/log.sock {
        }
        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.
    #}
    {% if generalSettings.accesslist %}
        {% set accessList = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', generalSettings.accesslist) | first %}
    {% endif %}

    servers {
        protocols {{ generalSettings.HttpVersions.split(',') | join(' ') }}
        {% if accessList %}
            trusted_proxies static {{ accessList.clientIps.split(',') | join(' ') }}
        {% endif %}
        {% if generalSettings.ClientIpHeaders %}
            {% for header_uuid in generalSettings.ClientIpHeaders.split(',') %}
                {% set header = helpers.toList('Pischem.caddy.reverseproxy.header') | selectattr('@uuid', 'equalto', header_uuid) | first %}
                {% if header and header.HeaderType %}
                    client_ip_headers {{ header.HeaderType }}
                {% endif %}
            {% endfor %}
        {% endif %}
        {% if generalSettings.LogCredentials|default("0") == "1" %}
            log_credentials
        {% endif %}
        {% if generalSettings.EnableLayer4|default("0") == "1" %}
            listener_wrappers {
                layer4 {
                    import /usr/local/etc/caddy/caddy.d/*.layer4listener
                    {% set context_var = "listener_wrappers" %}
                    {% include "OPNsense/Caddy/includeLayer4" %}
                }
                {# Route all other traffic to HTTP App #}
                tls
            }
        {% endif %}
    }

    {% if generalSettings.EnableLayer4|default("0") == "1" %}
        layer4 {
            import /usr/local/etc/caddy/caddy.d/*.layer4global
            {% set context_var = "global" %}
            {% include "OPNsense/Caddy/includeLayer4" %}
        }
    {% 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 %}

    {% import "OPNsense/Caddy/includeDnsProvider" as dns_includes %}
    {% set dnsProviderSpecialConfig = dns_includes.dnsProviderSpecialConfig() %}

    {# 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 }} {
                    {% set context_var = 'dnsProviderSpecialLogic' %}
                    {% 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

{#
#   When Layer4 is active, set default ports.
#   Caddy does not set them up automatically under certain conditions.
#   For example when AutoHTTPS would be off, or no domains have been configured.
#   There are no regressions to set these ports up.
#}

{% if generalSettings.EnableLayer4|default("0") == "1" %}
    # Layer4 default HTTP port
    {% if httpPort %}
        :{{ httpPort }} {
        }
    {% else %}
        :80 {
        }
    {% endif %}
    # Layer4 default HTTPS port
    {% if httpsPort %}
        :{{ httpsPort }} {
        }
    {% else %}
        :443 {
        }
    {% endif %}
{% endif %}

{#
#   Section: HTTP-01 Challenge Redirection
#   Purpose: Redirects HTTP-01 challenges to a different webserver for reverse proxies and subdomains.
#}
{% macro http01_challenge_redirection(domain, acme_passthrough) %}
http://{{ domain }} {
    handle /.well-known/acme-challenge/* {
        reverse_proxy {{ acme_passthrough }}
    }
    handle {
        redir https://{host}{uri} 308
    }
}
{% endmacro %}

{% for reverse in helpers.toList('Pischem.caddy.reverseproxy.reverse') %}
    {% if reverse.enabled|default("0") == "1" and reverse.AcmePassthrough %}
        {{ http01_challenge_redirection(reverse.FromDomain|default(""), reverse.AcmePassthrough) }}
    {% endif %}
{% endfor %}

{% for subdomain in helpers.toList('Pischem.caddy.reverseproxy.subdomain') %}
    {% if subdomain.enabled|default("0") == "1" and subdomain.AcmePassthrough %}
        {{ http01_challenge_redirection(subdomain.FromDomain|default(""), subdomain.AcmePassthrough) }}
    {% 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.
#}
{% macro tls_configuration(
    customCert,
    dnsChallenge,
    clientAuthTrustPool,
    clientAuthMode,
    dnsProvider,
    dnsApiKey,
    dnsSecretApiKey,
    tlsDnsOptionalField1,
    tlsDnsOptionalField2,
    tlsDnsOptionalField3,
    tlsDnsOptionalField4,
    tlsDnsPropagationTimeout,
    tlsDnsPropagationTimeoutPeriod,
    tlsDnsPropagationDelay,
    tlsDnsPropagationResolvers
) %}
    {% if customCert or (dnsChallenge == "1" and dnsProvider) or clientAuthTrustPool %}
        tls {% if customCert %}/var/db/caddy/data/caddy/certificates/temp/{{ customCert }}.pem /var/db/caddy/data/caddy/certificates/temp/{{ customCert }}.key{% endif %} {
            {% if not customCert and (dnsChallenge == "1" and dnsProvider) %}
            issuer acme {
                dns {{ dnsProvider }} {% if dnsProvider not in dnsProviderSpecialConfig %}{{ dnsApiKey }}{% else %}{
                    {% set context_var = 'dnsProviderSpecialLogic' %}
                    {% include "OPNsense/Caddy/includeDnsProvider" %}
                }
                {% endif %}

                {% if tlsDnsPropagationResolvers %}
                resolvers {{ tlsDnsPropagationResolvers }}
                {% endif %}
                {% if tlsDnsPropagationTimeout|default("0") == "1" %}
                propagation_timeout -1
                {% elif tlsDnsPropagationTimeoutPeriod %}
                propagation_timeout {{ tlsDnsPropagationTimeoutPeriod }}s
                {% endif %}
                {% if tlsDnsPropagationDelay %}
                propagation_delay {{ tlsDnsPropagationDelay }}s
                {% endif %}
            }
            {% endif %}

            {% if clientAuthTrustPool %}
                client_auth {
                    {% for ca in clientAuthTrustPool.split(',') %}
                    trust_pool file /var/db/caddy/data/caddy/certificates/temp/{{ ca.strip() }}.pem
                    {% endfor %}
                    {% if clientAuthMode %}
                    mode {{ clientAuthMode }}
                    {% endif %}
                }
            {% endif %}
        }
    {% 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.
#   Parameters:
#   @param handle (object)
#}
{% macro reverse_proxy_configuration(handle) %}
    {{ handle.HandleType }} {{ handle.HandlePath|default("") }} {
        {{ handle_accesslist(handle.accesslist) }}
        {{ render_basic_auth(handle.basicauth) }}
        {# All IPs not matched by accesslist will continue processing #}
        {% if handle.ForwardAuth|default("0") == "1" %}
            {% include "OPNsense/Caddy/includeAuthProvider" %}
        {% endif %}
        {% if handle.HandleDirective == "reverse_proxy" and handle.ToPath|default("") != "" %}
            rewrite * {{ handle.ToPath }}{uri}
        {% endif %}
        {# http:// is the empty default for reverse_proxy #}
        {% set protocol = (
            "http://" if handle.HttpTls == "0" and handle.HandleDirective == "redir" else
            "https://" if handle.HttpTls == "1" else
            "h2c://" if handle.HttpTls == "2" else
            ''
        ) %}
        {% set formatted_domains = [] -%}
        {% for domain in handle.ToDomain.split(',') -%}
            {% set is_ipv6 = (':' in domain and domain.count(':') >= 2) -%}
            {% set formatted_domain =
                ( '[' ~ domain ~ ']' if is_ipv6 else domain ) ~
                ( ':' ~ handle.ToPort if handle.ToPort else '' ) -%}
            {% set _ = formatted_domains.append(protocol ~ formatted_domain) -%}
        {% endfor -%}
        {% if handle.HandleDirective == "reverse_proxy" %}
        {{ handle.HandleDirective }} {{ formatted_domains | join(' ') }} {
            {{ header_manipulation(handle) }}
            {% if handle.lb_policy|default("") %}
                lb_policy {{ handle.lb_policy }}
            {% endif %}
            {% if handle.lb_retries|default("") %}
                lb_retries {{ handle.lb_retries }}
            {% endif %}
            {% if handle.lb_try_duration|default("") %}
                lb_try_duration {{ handle.lb_try_duration }}s
            {% endif %}
            {% if handle.lb_try_interval|default("") %}
                lb_try_interval {{ handle.lb_try_interval }}ms
            {% endif %}
            {% if handle.PassiveHealthFailDuration|default("") %}
                fail_duration {{ handle.PassiveHealthFailDuration }}s
            {% endif %}
            {% if handle.PassiveHealthMaxFails|default("") %}
                max_fails {{ handle.PassiveHealthMaxFails }}
            {% endif %}
            {% if handle.PassiveHealthUnhealthyStatus|default("") %}
                unhealthy_status {{ handle.PassiveHealthUnhealthyStatus }}
            {% endif %}
            {% if handle.PassiveHealthUnhealthyLatency|default("") %}
                unhealthy_latency {{ handle.PassiveHealthUnhealthyLatency }}ms
            {% endif %}
            {% if handle.PassiveHealthUnhealthyRequestCount|default("") %}
                unhealthy_request_count {{ handle.PassiveHealthUnhealthyRequestCount }}
            {% endif %}
            {% set has_transport_options =
                handle.HttpVersion or
                handle.HttpKeepalive or
                handle.HttpTlsInsecureSkipVerify|default("0") == "1" or
                handle.HttpTlsTrustedCaCerts or
                handle.HttpTlsServerName -%}
            {% if has_transport_options %}
                {% if handle.HttpNtlm|default("0") == "1" %}
                transport http_ntlm {
                {% else %}
                transport http {
                {% endif %}
                    {% if handle.HttpVersion %}
                        {% set version_map = {'http1': 1.1, 'http2': 2, 'http3': 3} %}
                        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 == "1" %}
                        {% 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 %}
        }
        {% elif handle.HandleDirective == "redir" %}
            {{ handle.HandleDirective }} {{ formatted_domains[0] }}{{ handle.ToPath|default("{uri}") }}
        {% endif %}
    }
{% endmacro %}

{#
#   Macro: handle_accesslist
#   Purpose: Manages the logic for access lists, used for handlers, domains and subdomains
#   Parameters:
#   @param accesslist (string): The UUID of the access list to be applied.
#   @param unique_suffix (string): A unique string to append to the access list, stripped of non-alphanumeric characters.
#}
{% macro handle_accesslist(accesslist, unique_suffix='') %}
    {# Access list logic for dropping connections based on IP #}
    {% if accesslist %}
        {% set sanitized_suffix = unique_suffix | regex_replace('[^a-zA-Z0-9]', '') %}
        {% set unique_identifier = '@' + accesslist + ('_' + sanitized_suffix if sanitized_suffix else '') %}

        {% set accesslist_obj = helpers.toList('Pischem.caddy.reverseproxy.accesslist') | selectattr('@uuid', 'equalto', accesslist) | first %}
        {% if accesslist_obj %}
            {% set client_ips = accesslist_obj.clientIps.split(',') | join(' ') %}
            {{ unique_identifier }} {
                {# Non-inverted access lists have "not" as default, inverted ones '' #}
                {{ 'not' if accesslist_obj.accesslistInvert|default("0") == "0" else '' }} client_ip {{ client_ips }}
            }
            {# When IP is matched, abort or send response code. This will end processing and drop the request #}
            handle {{ unique_identifier }} {
                {% if accesslist_obj.HttpResponseCode %}
                    respond {{ '"' + accesslist_obj.HttpResponseMessage + '"' if accesslist_obj.HttpResponseMessage else '' }} {{ accesslist_obj.HttpResponseCode }}
                {% else %}
                    abort
                {% endif %}
            }
        {% endif %}
    {% endif %}
{% endmacro %}

{#
#   Macro: render_basic_auth
#   Purpose: Renders the basic authentication configuration.
#   Parameters:
#   @param basicauth_uuids (string): A comma-separated list of UUIDs for basic authentication.
#}
{% macro render_basic_auth(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 %}

{#
#   Macro: render_handles
#   Purpose: Renders the handles in the correct order (path-specific first, then catch-all).
#   Parameters:
#   @param handles (list): A list of handle objects to be rendered.
#}
{% macro render_handles(handles) %}
    {% for handle in handles %}
        {% if handle.enabled|default("0") == "1" and handle.HandlePath %}
            {{ reverse_proxy_configuration(handle) }}
        {% endif %}
    {% endfor %}
    {% for handle in handles %}
        {% if handle.enabled|default("0") == "1" and not handle.HandlePath %}
            {{ reverse_proxy_configuration(handle) }}
        {% endif %}
    {% endfor %}
{% 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: Configures TLS settings for the domain.
#   - handle_accesslist: Manages the logic for access lists, including configuration.
#   - render_handles: Renders the handles in the correct order, including basic authentication.
#   - reverse_proxy_configuration: Sets up the handle with reverse proxy configurations.
#   - handle_response: Manages the response logic for access lists and aborts.
#   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'] }}"
        {% 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 %}
            {{ tls_configuration(
                reverse.CustomCertificate|default(""),
                reverse.DnsChallenge|default("0"),
                reverse.ClientAuthTrustPool|default(""),
                reverse.ClientAuthMode|default(""),
                generalSettings.TlsDnsProvider,
                generalSettings.TlsDnsApiKey,
                generalSettings.TlsDnsSecretApiKey,
                generalSettings.TlsDnsOptionalField1,
                generalSettings.TlsDnsOptionalField2,
                generalSettings.TlsDnsOptionalField3,
                generalSettings.TlsDnsOptionalField4,
                generalSettings.TlsDnsPropagationTimeout,
                generalSettings.TlsDnsPropagationTimeoutPeriod,
                generalSettings.TlsDnsPropagationDelay,
                generalSettings.TlsDnsPropagationResolvers
            ) }}

            {% 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'] }} {
                        {% set subdomain_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('subdomain', 'equalto', subdomain['@uuid']) | list %}
                        {{ handle_accesslist(subdomain.accesslist, subdomain.FromDomain) }}
                        {# All IPs not matched by accesslist will continue processing #}
                        {{ render_basic_auth(subdomain.basicauth) }}
                        {{ render_handles(subdomain_handles) }}
                    }
                {% endif %}
            {% endfor %}

            {% set domain_handles = helpers.toList('Pischem.caddy.reverseproxy.handle') | selectattr('reverse', 'equalto', reverse['@uuid']) | selectattr('subdomain', 'undefined') | list %}
            {{ handle_accesslist(reverse.accesslist, reverse.FromDomain) }}
            {# All IPs not matched by accesslist will continue processing #}
            {{ render_basic_auth(reverse.basicauth) }}
            {{ render_handles(domain_handles) }}
        }
    {% endif %}
{% endfor %}

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