diff --git a/net/haproxy/pkg-descr b/net/haproxy/pkg-descr index 9ace5e787..a8d97d695 100644 --- a/net/haproxy/pkg-descr +++ b/net/haproxy/pkg-descr @@ -19,6 +19,9 @@ Added: * add basic OCSP stapling support (#1430) * add support for e-mail alerts and mailers (#1669) * add support for custom header checks (#1907) +* add support for server templates (#1975) +* add support for additional resolver options (#1975) +* add support for resolve-prefer option (#1975) Fixed: * fix maintenance page (python error: 'list' object has no attribute 'strip') @@ -35,6 +38,7 @@ Changed: * change default for spreadChecks from 0 to 2 * no longer overwrite live config file when running a syntax check * make restart/reload commands usable in cron jobs +* relax GUI input validation for servers, move validation to jinja template (#1975) 2.26 diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php index 0cd365dc4..e899a083d 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php @@ -132,7 +132,7 @@ class SettingsController extends ApiMutableModelControllerBase public function searchServersAction() { - return $this->searchBase('servers.server', array('enabled', 'name', 'address', 'port', 'description'), 'name'); + return $this->searchBase('servers.server', array('enabled', 'name', 'type', 'address', 'port', 'description'), 'name'); } public function getHealthcheckAction($uuid = null) diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml index a17a99b86..31e0eda98 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml @@ -61,6 +61,22 @@ true + + backend.resolverOpts + + select_multiple + + true + + Type option name or choose from list. + + + backend.resolvePrefer + + dropdown + + true + backend.source diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml index 0eede8c31..1436580cf 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml @@ -7,9 +7,9 @@ server.name - + text - Name to identify this server. + Name to identify a static server. When creating a server template, then this prefix is used for the server names to be built. server.description @@ -17,6 +17,17 @@ text Description for this server. + + server.type + + dropdown + Either configure a static server or a template to initialize multiple servers with shared parameters. + + + + header + + server.address @@ -24,6 +35,42 @@ Enter server address. + + + header + + + + server.serviceName + + text + + + + server.number + + text + + + + server.linkedResolver + + dropdown + + + + server.resolverOpts + + select_multiple + + true + + Type option name or choose from list. + + + + header + server.port @@ -36,6 +83,13 @@ dropdown + + server.resolvePrefer + + dropdown + + true + server.ssl diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml index 211350c1f..e889fb8f9 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -880,6 +880,24 @@ N N + + N + Y + Y + + allow-dup-ip + ignore-weight + prevent-dup-ip + + + + N + N + + prefer IPv4 + prefer IPv6 [default] + + /^((([0-9a-zA-Z._\-\*:]+)))*/u lower @@ -1197,7 +1215,7 @@
/^([0-9a-zA-Z\.,_\-:]){0,1024}$/u Please specify a valid servername or IP address. - Y + N
1 @@ -1213,7 +1231,7 @@ N - Y + N active active [default] @@ -1221,6 +1239,54 @@ disabled + + Y + static + + static + + + + + /^([0-9a-zA-Z\.,_\-:]){0,1024}$/u + Please specify a valid service name. + N + + + /^[0-9]+(-[0-9]+)?/u + Please specify a valid number or range. + N + + + + + + Related resolver not found + N + N + + + N + Y + Y + + allow-dup-ip + ignore-weight + prevent-dup-ip + + + + N + N + + prefer IPv4 + prefer IPv6 [default] + + 0 Y diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M3_0_0.php b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M3_0_0.php new file mode 100644 index 000000000..fff7515d3 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M3_0_0.php @@ -0,0 +1,44 @@ +getNodeByReference('servers.server')->iterateItems() as $server) { + $server->type = 'static'; + } + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt index bbb658680..63ff12973 100644 --- a/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt +++ b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt @@ -230,7 +230,6 @@ POSSIBILITY OF SUCH DAMAGE. $("#acl\\.expression").change(function(){ var service_id = 'table_' + $(this).val(); $(".expression_table").hide(); - // $(".table_"+$(this).val()).show(); $("."+service_id).show(); }); $("#acl\\.expression").change(); @@ -241,7 +240,6 @@ POSSIBILITY OF SUCH DAMAGE. $("#action\\.type").change(function(){ var service_id = 'table_' + $(this).val(); $(".type_table").hide(); - // $(".table_"+$(this).val()).show(); $("."+service_id).show(); }); $("#action\\.type").change(); @@ -308,6 +306,16 @@ POSSIBILITY OF SUCH DAMAGE. $("#healthcheck\\.type").change(); }) + // hook into on-show event for dialog to extend layout. + $('#DialogServer').on('shown.bs.modal', function (e) { + $("#server\\.type").change(function(){ + var service_id = 'table_server_type_' + $(this).val(); + $(".table_server_type").hide(); + $("."+service_id).show(); + }); + $("#server\\.type").change(); + }) + /*********************************************************************** * Commands **********************************************************************/ @@ -647,12 +655,12 @@ POSSIBILITY OF SUCH DAMAGE.

{{ lang._('Real Servers') }}

-

{{ lang._('HAProxy needs to know which servers should be used to serve content. The following minimum information must be provided for each server:') }}

+

{{ lang._('HAProxy needs to know which servers should be used to serve content. Either add a static server configuration or use a template to initialize multiple servers at once. The latter one can also be used to discover the available services via DNS SRV records. The following minimum information must be provided for each server:') }}

    -
  • {{ lang._('%sFQDN or IP:%s The IP address or fully-qualified domain name that should be used when communicating with your server.') | format('', '') }}
  • -
  • {{ lang._('%sPort:%s The TCP or UDP port that should be used. If unset, the same port the client connected to will be used.') | format('', '') }}
  • +
  • {{ lang._('%sStatic Servers:%s The IP address or fully-qualified domain name that should be used when communicating with your server. Additionally the TCP or UDP port that should be used. If unset, the same port the client connected to will be used.') | format('', '') }}
  • +
  • {{ lang._('%sServer Templates:%s A prefix is required to build the server names. Additionally a service name or FQDN is required to identify the servers this template initializes') | format('', '') }}
-

{{ lang._("Please note that advanced mode settings allow you to disable a certain server or to configure it as a backup server in a Backend Pool. Another neat option is the possibility to adjust a server's weight relative to other servers in the same Backend Pool.") }}

+

{{ lang._("Please note that advanced mode settings allow you to adjust a server's weight relative to other servers in the same Backend Pool, in addition to fine-grained health check options.") }}

{{ lang._('Note that it is possible to directly add options to the HAProxy configuration by using the "option pass-through", a setting that is available for several configuration items. It allows you to implement configurations that are currently not officially supported by this plugin. It is strongly discouraged to rely on this feature. Please report missing features on our GitHub page!') | format('', '') }}


@@ -809,6 +817,7 @@ POSSIBILITY OF SUCH DAMAGE. {{ lang._('Enabled') }} {{ lang._('Server ID') }} {{ lang._('Server Name') }} + {{ lang._('Type') }} {{ lang._('Server Address') }} {{ lang._('Server Port') }} {{ lang._('Description') }} diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf index 5dff7a2f7..c0d06021a 100644 --- a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf @@ -1496,135 +1496,169 @@ backend {{backend.name}} {% if server_data == {} %} # ERROR: server data not found ({{server}}) {% else %} -{# # collect optional server parameters #} -{% set server_options = [] %} -{# # check if health check is enabled #} -{% if healthcheck_enabled == '1' %} -{% do server_options.append('check') %} -{# # This can be configured in multiple places. #} -{# # Priority for which value is used: backend > server > health check #} -{% if backend.checkInterval|default("") != "" %} -{% do server_options.append('inter ' ~ backend.checkInterval) %} -{% elif server_data.checkInterval|default("") != "" %} -{% do server_options.append('inter ' ~ server_data.checkInterval) %} -{% elif healthcheck_data.interval|default("") != "" %} -{% do server_options.append('inter ' ~ healthcheck_data.interval) %} -{% endif %} -{# # use a different interval when server is in DOWN state #} -{% if backend.checkDownInterval|default("") != "" %} -{% do server_options.append('downinter ' ~ backend.checkDownInterval) %} -{% elif server_data.checkDownInterval|default("") != "" %} -{% do server_options.append('downinter ' ~ server_data.checkDownInterval) %} -{% endif %} -{# # unhealthy threshold #} -{% if backend.healthCheckFall|default("") != "" %} -{% do server_options.append('fall ' ~ backend.healthCheckFall) %} -{% endif %} -{# # healthy threshold #} -{% if backend.healthCheckRise|default("") != "" %} -{% do server_options.append('rise ' ~ backend.healthCheckRise) %} -{% endif %} -{# # use a different port for health check #} -{% if healthcheck_data.checkport|default("") != "" %} -{# # prefer port from health check template #} -{% do server_options.append('port ' ~ healthcheck_data.checkport) %} -{% elif server_data.checkport|default("") != "" %} -{% do server_options.append('port ' ~ server_data.checkport) %} -{% endif %} -{# # force SSL encryption for health checks #} -{% if healthcheck_data.force_ssl|default('') == '1' %} -{% do server_options.append('check-ssl ') %} -{% endif %} -{# # add all additions from healthchecks here #} -{% do server_options.append(healthcheck_additions|join(' ')) if healthcheck_additions.length != '0' %} -{% endif %} -{# # server weight #} -{% do server_options.append('weight ' ~ server_data.weight) if server_data.weight|default("") != "" %} -{# # server role/mode #} -{% if server_data.mode|default("") != 'active' %} -{% do server_options.append(server_data.mode) %} -{% endif %} -{# # server ssl communication #} -{% if server_data.ssl|default("") == '1' %} -{% do server_options.append('ssl') %} -{# # HTTP/2 #} -{% if backend.http2Enabled|default("") == '1' and backend.ba_advertised_protocols|default("") != "" %} -{# # convert protocols to HAProxy-compatible format #} -{% set alpn_options = backend.ba_advertised_protocols|replace('http10', 'http/1.0')|replace('http11', 'http/1.1') %} -{% do server_options.append('alpn ' ~ alpn_options) %} -{% endif %} -{# # HTTP/2 without TLS #} -{% elif backend.http2Enabled|default("") == '1' and backend.http2Enabled_nontls|default("") == '1' %} -{% do server_options.append('proto h2') %} -{% endif %} -{# # ssl verification can be enabled for two reasons: #} -{# # 1. in server settings: to verify *all* communication to this server #} -{# # 2. in health checks: to verify *only* health check communication to this server #} -{# # When 1. is enabled, health checks are automatically secured. #} -{# # Use-case for 2: when using TCP for server communication, but HTTPS for health checks. #} -{% if server_data.ssl|default("") == '1' or (healthcheck_enabled == '1' and healthcheck_data.force_ssl|default('') == '1') %} -{# # get status of ssl verification #} -{% set ssl_verify_enabled = '0' %} -{% if helpers.exists('OPNsense.HAProxy.general.tuning.sslServerVerify') and OPNsense.HAProxy.general.tuning.sslServerVerify|default("") != 'ignore' %} -{# # NOTE: Global parameter overrides per-server configuration. #} -{% set ssl_verify_enabled = '1' if OPNsense.HAProxy.general.tuning.sslServerVerify|default("") == 'required' %} -{% elif server_data.sslVerify|default("") == '1' %} -{% set ssl_verify_enabled = '1' %} -{% endif %} -{# # configure ssl verification #} -{% if ssl_verify_enabled == '1' %} -{# # enable SSL verification #} -{% do server_options.append('verify required') %} -{# # check for SSL CA #} -{% if server_data.sslCA|default("") != "" %} -{% do server_options.append('ca-file /tmp/haproxy/ssl/' ~ server_data.id ~ '.calist') %} -{% else %} -{# # fallback to system CA Root Certificates #} -{% do server_options.append('ca-file /etc/ssl/cert.pem') %} -{% endif %} -{# # check for SSL CRL #} -{% if server_data.sslCRL|default("") != "" %} -{% do server_options.append('crl-file /tmp/haproxy/ssl/' ~ server_data.sslCRL ~ '.pem') %} -{% endif %} -{# # check for SSL client cert #} -{% if server_data.sslClientCertificate|default("") != "" %} -{% do server_options.append('crt /tmp/haproxy/ssl/' ~ server_data.sslClientCertificate ~ '.pem') %} -{% endif %} +{# # check if all required server parameters are set #} +{% if (server_data.type|default("") == 'static' and server_data.address|default("") == '') or (server_data.type|default("") == 'template' and (server_data.serviceName|default("") == '' or server_data.number|default("") == '')) %} +# ERROR: server is invalid, required parameters not set ({{server_data.name}}) +{% else %} +{# # server type #} +{% set server_basics = [] %} +{% if server_data.type|default("") == 'static' %} +{% do server_basics.append('server ' ~ server_data.name ~ ' ' ~ server_data.address) %} {% else %} -{% do server_options.append('verify none') %} +{% do server_basics.append('server-template ' ~ server_data.name ~ ' ' ~ server_data.number ~ ' ' ~ server_data.serviceName) %} +{% endif %} +{# # collect optional server parameters #} +{% set server_options = [] %} +{# # check if health check is enabled #} +{% if healthcheck_enabled == '1' %} +{% do server_options.append('check') %} +{# # This can be configured in multiple places. #} +{# # Priority for which value is used: backend > server > health check #} +{% if backend.checkInterval|default("") != "" %} +{% do server_options.append('inter ' ~ backend.checkInterval) %} +{% elif server_data.checkInterval|default("") != "" %} +{% do server_options.append('inter ' ~ server_data.checkInterval) %} +{% elif healthcheck_data.interval|default("") != "" %} +{% do server_options.append('inter ' ~ healthcheck_data.interval) %} +{% endif %} +{# # use a different interval when server is in DOWN state #} +{% if backend.checkDownInterval|default("") != "" %} +{% do server_options.append('downinter ' ~ backend.checkDownInterval) %} +{% elif server_data.checkDownInterval|default("") != "" %} +{% do server_options.append('downinter ' ~ server_data.checkDownInterval) %} +{% endif %} +{# # unhealthy threshold #} +{% if backend.healthCheckFall|default("") != "" %} +{% do server_options.append('fall ' ~ backend.healthCheckFall) %} +{% endif %} +{# # healthy threshold #} +{% if backend.healthCheckRise|default("") != "" %} +{% do server_options.append('rise ' ~ backend.healthCheckRise) %} +{% endif %} +{# # use a different port for health check #} +{% if healthcheck_data.checkport|default("") != "" %} +{# # prefer port from health check template #} +{% do server_options.append('port ' ~ healthcheck_data.checkport) %} +{% elif server_data.checkport|default("") != "" %} +{% do server_options.append('port ' ~ server_data.checkport) %} +{% endif %} +{# # force SSL encryption for health checks #} +{% if healthcheck_data.force_ssl|default('') == '1' %} +{% do server_options.append('check-ssl ') %} +{% endif %} +{# # add all additions from healthchecks here #} +{% do server_options.append(healthcheck_additions|join(' ')) if healthcheck_additions.length != '0' %} +{% endif %} +{# # server weight #} +{% do server_options.append('weight ' ~ server_data.weight) if server_data.weight|default("") != "" %} +{# # server role/mode #} +{% if server_data.mode|default("") != 'active' %} +{% do server_options.append(server_data.mode) %} +{% endif %} +{# # server ssl communication #} +{% if server_data.ssl|default("") == '1' %} +{% do server_options.append('ssl') %} +{# # HTTP/2 #} +{% if backend.http2Enabled|default("") == '1' and backend.ba_advertised_protocols|default("") != "" %} +{# # convert protocols to HAProxy-compatible format #} +{% set alpn_options = backend.ba_advertised_protocols|replace('http10', 'http/1.0')|replace('http11', 'http/1.1') %} +{% do server_options.append('alpn ' ~ alpn_options) %} +{% endif %} +{# # HTTP/2 without TLS #} +{% elif backend.http2Enabled|default("") == '1' and backend.http2Enabled_nontls|default("") == '1' %} +{% do server_options.append('proto h2') %} +{% endif %} +{# # ssl verification can be enabled for two reasons: #} +{# # 1. in server settings: to verify *all* communication to this server #} +{# # 2. in health checks: to verify *only* health check communication to this server #} +{# # When 1. is enabled, health checks are automatically secured. #} +{# # Use-case for 2: when using TCP for server communication, but HTTPS for health checks. #} +{% if server_data.ssl|default("") == '1' or (healthcheck_enabled == '1' and healthcheck_data.force_ssl|default('') == '1') %} +{# # get status of ssl verification #} +{% set ssl_verify_enabled = '0' %} +{% if helpers.exists('OPNsense.HAProxy.general.tuning.sslServerVerify') and OPNsense.HAProxy.general.tuning.sslServerVerify|default("") != 'ignore' %} +{# # NOTE: Global parameter overrides per-server configuration. #} +{% set ssl_verify_enabled = '1' if OPNsense.HAProxy.general.tuning.sslServerVerify|default("") == 'required' %} +{% elif server_data.sslVerify|default("") == '1' %} +{% set ssl_verify_enabled = '1' %} +{% endif %} +{# # configure ssl verification #} +{% if ssl_verify_enabled == '1' %} +{# # enable SSL verification #} +{% do server_options.append('verify required') %} +{# # check for SSL CA #} +{% if server_data.sslCA|default("") != "" %} +{% do server_options.append('ca-file /tmp/haproxy/ssl/' ~ server_data.id ~ '.calist') %} +{% else %} +{# # fallback to system CA Root Certificates #} +{% do server_options.append('ca-file /etc/ssl/cert.pem') %} +{% endif %} +{# # check for SSL CRL #} +{% if server_data.sslCRL|default("") != "" %} +{% do server_options.append('crl-file /tmp/haproxy/ssl/' ~ server_data.sslCRL ~ '.pem') %} +{% endif %} +{# # check for SSL client cert #} +{% if server_data.sslClientCertificate|default("") != "" %} +{% do server_options.append('crt /tmp/haproxy/ssl/' ~ server_data.sslClientCertificate ~ '.pem') %} +{% endif %} +{% else %} +{% do server_options.append('verify none') %} +{% endif %} +{% endif %} +{# # resolver #} +{% set resolver_id = '' %} +{% set resolver_opts = '' %} +{% if backend.linkedResolver|default("") != "" %} +{# # prefer backend configuration #} +{% set resolver_id = backend.linkedResolver %} +{% set resolver_opts = backend.resolverOpts %} +{% elif server_data.linkedResolver|default("") != "" and server_data.type|default("") == 'template' %} +{# # use resolver for server template #} +{% set resolver_id = server_data.linkedResolver %} +{% set resolver_opts = server_data.resolverOpts %} +{% endif %} +{% if resolver_id != '' %} +{% set resolver_data = helpers.getUUID(resolver_id) %} +{% do server_options.append('resolvers ' ~ resolver_data.id) %} +{# # additional resolver options #} +{% if resolver_opts != '' %} +{% do server_options.append('resolve-opts ' ~ resolver_opts) %} +{% endif %} +{% endif %} +{# # prefer selected IP family for DNS resolution #} +{% if backend.resolvePrefer|default("") != "" %} +{# # prefer backend configuration #} +{% do server_options.append('resolve-prefer ' ~ backend.resolvePrefer) %} +{% elif server_data.linkedResolver|default("") != "" %} +{% do server_options.append('resolve-prefer ' ~ server_data.resolvePrefer) %} +{% endif %} +{# # source address #} +{% if backend.source|default("") != "" %} +{# # prefer backend configuration #} +{% do server_options.append('source ' ~ backend.source) %} +{% elif server_data.source|default("") != "" %} +{% do server_options.append('source ' ~ server_data.source) %} +{% endif %} +{# # PROXY protocol #} +{% if backend.proxyProtocol|default("") == "v1" %} +{% do server_options.append('send-proxy') %} +{% do server_options.append('check-send-proxy') %} +{% elif backend.proxyProtocol|default("") == "v2" %} +{% do server_options.append('send-proxy-v2') %} +{% do server_options.append('check-send-proxy') %} +{% endif %} +{# # cookie-based persistence #} +{% if backend.persistence|default("") == "cookie" %} +{% do server_options.append('cookie ' ~ server_data.id|replace(".", "")) %} +{% endif %} +{# # server advanced options #} +{% if server_data.advanced|default("") != "" %} +{% do server_options.append(server_data.advanced) %} +{% endif %} +{# # server enabled? #} +{% if server_data.enabled == '1' %} + {{server_basics|join(' ')}}{% if backend.tuning_noport != '1' %}{% if server_data.port|default("") != "" %}:{{server_data.port}}{% endif %}{% endif %} {{server_options|join(' ')}} {% endif %} -{% endif %} -{# # resolver #} -{% if backend.linkedResolver|default("") != "" %} -{% set resolver_data = helpers.getUUID(backend.linkedResolver) %} -{% do server_options.append('resolvers ' ~ resolver_data.id) %} -{% endif %} -{# # source address #} -{% if backend.source|default("") != "" %} -{# # prefer backend configuration #} -{% do server_options.append('source ' ~ backend.source) %} -{% elif server_data.source|default("") != "" %} -{% do server_options.append('source ' ~ server_data.source) %} -{% endif %} -{# # PROXY protocol #} -{% if backend.proxyProtocol|default("") == "v1" %} -{% do server_options.append('send-proxy') %} -{% do server_options.append('check-send-proxy') %} -{% elif backend.proxyProtocol|default("") == "v2" %} -{% do server_options.append('send-proxy-v2') %} -{% do server_options.append('check-send-proxy') %} -{% endif %} -{# # cookie-based persistence #} -{% if backend.persistence|default("") == "cookie" %} -{% do server_options.append('cookie ' ~ server_data.id|replace(".", "")) %} -{% endif %} -{# # server advanced options #} -{% if server_data.advanced|default("") != "" %} -{% do server_options.append(server_data.advanced) %} -{% endif %} -{# # server enabled? #} -{% if server_data.enabled == '1' %} - server {{server_data.name}} {{server_data.address}}:{% if backend.tuning_noport != '1' %}{% if server_data.port|default("") != "" %}{{server_data.port}}{% endif %}{% endif %} {{server_options|join(' ')}} {% endif %} {% endif %} {% endfor %}