From 5ccb60bd2a37ca868a3d5be8d7e5eab1c0fd4df1 Mon Sep 17 00:00:00 2001 From: il Date: Fri, 26 Jun 2026 16:07:59 +0900 Subject: [PATCH] feat(snappymail): update snappymail --- ansible/inventory/group_vars/all.yaml | 16 +- ansible/playbooks/app/site.yaml | 8 + ansible/roles/app/handlers/main.yaml | 11 ++ .../roles/app/tasks/services/set_homebox.yaml | 2 - .../app/tasks/services/set_snappymail.yaml | 90 +++++++++ .../common/tasks/services/set_kopia.yaml | 24 ++- .../infra/tasks/services/set_ca_server.yaml | 10 +- .../app/homepage/config/settings.yaml.j2 | 2 +- .../app/snappymail/configs/application.ini.j2 | 174 ++++++++++++++++++ .../app/snappymail/domains/domain.ini.j2 | 17 ++ .../app/snappymail/snappymail.container.j2 | 34 ++++ .../auth/authelia/authelia.container.j2 | 4 + .../auth/authelia/config/authelia.yaml.j2 | 17 +- .../containers/infra/ca/ca.container.j2 | 2 +- .../containers/infra/maddy/maddy.container.j2 | 2 +- 15 files changed, 393 insertions(+), 20 deletions(-) create mode 100644 ansible/roles/app/tasks/services/set_snappymail.yaml create mode 100644 config/services/containers/app/snappymail/configs/application.ini.j2 create mode 100644 config/services/containers/app/snappymail/domains/domain.ini.j2 create mode 100644 config/services/containers/app/snappymail/snappymail.container.j2 diff --git a/ansible/inventory/group_vars/all.yaml b/ansible/inventory/group_vars/all.yaml index 229e485..ef81d49 100644 --- a/ansible/inventory/group_vars/all.yaml +++ b/ansible/inventory/group_vars/all.yaml @@ -25,7 +25,7 @@ services: domain: public: "vpn" ports: - upd: "11290" + udp: "11290" crowdsec: domain: internal: "crowdsec" @@ -144,6 +144,15 @@ services: http: "3000" # app dependency: web: "app-public" + snappymail: + domain: + public: "webmail" + internal: "webmail.app" + ports: + http: "8888" + subuid: "100081" + dependency: + web: "app-public" vaultwarden: domain: public: "vault" @@ -270,7 +279,7 @@ version: # common caddy: "2.11.4" # infra - step: "0.30.2" + ca: "0.30.2" ## Postgresql postgresql: "18.4" ### For immich - https://github.com/immich-app/base-images/blob/main/postgres/versions.yaml @@ -292,7 +301,7 @@ version: redis: "8.8.0" immich: "v2.7.5" paperless: "2.20.15" - nextcloud: "34.0.0" + nextcloud: "34.0.1" collabora: "26.04.1.4.1" sure: "0.7.1-hotfix.1" outline: "1.8.1" @@ -300,3 +309,4 @@ version: mealie: "v3.19.2" homebox: "0.26.2" homepage: "v1.13.2" + snappymail: "v2.38.2" diff --git a/ansible/playbooks/app/site.yaml b/ansible/playbooks/app/site.yaml index fdc50c7..81f9ec1 100644 --- a/ansible/playbooks/app/site.yaml +++ b/ansible/playbooks/app/site.yaml @@ -178,6 +178,14 @@ tags: ["site", "vaultwarden"] tags: ["site", "vaultwarden"] + - name: Set snappymail + ansible.builtin.include_role: + name: "app" + tasks_from: "services/set_snappymail" + apply: + tags: ["site", "snappymail"] + tags: ["site", "snappymail"] + - name: Set gitea ansible.builtin.include_role: name: "app" diff --git a/ansible/roles/app/handlers/main.yaml b/ansible/roles/app/handlers/main.yaml index 4dfab56..63a2618 100644 --- a/ansible/roles/app/handlers/main.yaml +++ b/ansible/roles/app/handlers/main.yaml @@ -146,3 +146,14 @@ changed_when: false listen: "notification_restart_homepage" ignore_errors: true # noqa: ignore-errors + +- name: Restart snappymail + ansible.builtin.systemd: + name: "snappymail.service" + state: "restarted" + enabled: true + scope: "user" + daemon_reload: true + changed_when: false + listen: "notification_restart_snappymail" + ignore_errors: true # noqa: ignore-errors diff --git a/ansible/roles/app/tasks/services/set_homebox.yaml b/ansible/roles/app/tasks/services/set_homebox.yaml index 213a2b3..d081af0 100644 --- a/ansible/roles/app/tasks/services/set_homebox.yaml +++ b/ansible/roles/app/tasks/services/set_homebox.yaml @@ -11,7 +11,6 @@ - "data/containers/homebox/data" - "containers/homebox" - "containers/homebox/ssl" - become: true - name: Deploy root certificate ansible.builtin.copy: @@ -21,7 +20,6 @@ owner: "{{ ansible_user }}" group: "{{ node['group'] }}" mode: "0440" - become: true notify: "notification_restart_homebox" no_log: true diff --git a/ansible/roles/app/tasks/services/set_snappymail.yaml b/ansible/roles/app/tasks/services/set_snappymail.yaml new file mode 100644 index 0000000..03cad23 --- /dev/null +++ b/ansible/roles/app/tasks/services/set_snappymail.yaml @@ -0,0 +1,90 @@ +--- +- name: Create snappymail directory + ansible.builtin.file: + path: "{{ node['home_path'] }}/{{ item }}" + state: "directory" + owner: "{{ services['snappymail']['subuid'] }}" + group: "{{ node['group'] }}" + mode: "0770" + loop: + - "data/containers/snappymail" + - "data/containers/snappymail/data" + - "containers/snappymail" + - "containers/snappymail/ssl" + - "containers/snappymail/configs" + - "containers/snappymail/domains" + become: true + +- name: Deploy root certificate + ansible.builtin.copy: + content: | + {{ hostvars['console']['ca']['root']['crt'] }} + dest: "{{ node['home_path'] }}/containers/snappymail/ssl/{{ root_cert_filename }}" + owner: "{{ services['snappymail']['subuid'] }}" + group: "{{ node['group'] }}" + mode: "0440" + become: true + notify: "notification_restart_snappymail" + no_log: true + +- name: Pull image + containers.podman.podman_image: + name: "ghcr.io/the-djmaze/snappymail" + tag: "{{ version['containers']['snappymail'] }}" + state: "present" + pull: true + +- name: Deploy config files + ansible.builtin.template: + src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/snappymail/{{ item.src }}.j2" + dest: "{{ node['home_path'] }}/containers/snappymail/{{ item.dest }}" + owner: "{{ services['snappymail']['subuid'] }}" + group: "{{ node['group'] }}" + mode: "0644" + loop: + - src: "configs/application.ini" + dest: "configs/application.ini" + - src: "domains/domain.ini" + dest: "domains/{{ domain['internal'] }}.ini" + loop_control: + label: "{{ item.src | basename }}" + become: true + notify: "notification_restart_snappymail" + +- name: Deploy snappymail.container file + ansible.builtin.template: + src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/snappymail/snappymail.container.j2" + dest: "{{ node['home_path'] }}/.config/containers/systemd/snappymail.container" + owner: "{{ services['snappymail']['subuid'] }}" + group: "{{ node['group'] }}" + mode: "0644" + become: true + notify: "notification_restart_snappymail" + +- name: Enable snappymail.service + ansible.builtin.systemd: + name: "snappymail.service" + state: "started" + enabled: true + daemon_reload: true + scope: "user" + +- name: Check container archive images + ansible.builtin.stat: + path: "{{ node['home_path'] }}/archives/containers/ghcr.io_the-djmaze_snappymail_{{ version['containers']['snappymail'] }}.tar" + register: container_archive_images + +- name: Save container archive images + containers.podman.podman_save: + image: + - "ghcr.io/the-djmaze/snappymail:{{ version['containers']['snappymail'] }}" + dest: "{{ node['home_path'] }}/archives/containers/ghcr.io_the-djmaze_snappymail_{{ version['containers']['snappymail'] }}.tar" + format: "oci-archive" + force: false + when: not container_archive_images.stat.exists + +- name: Fetch container archive images + ansible.builtin.fetch: + src: "{{ node['home_path'] }}/archives/containers/ghcr.io_the-djmaze_snappymail_{{ version['containers']['snappymail'] }}.tar" + dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/" + flat: true diff --git a/ansible/roles/common/tasks/services/set_kopia.yaml b/ansible/roles/common/tasks/services/set_kopia.yaml index 215c37b..9a64c7a 100644 --- a/ansible/roles/common/tasks/services/set_kopia.yaml +++ b/ansible/roles/common/tasks/services/set_kopia.yaml @@ -120,7 +120,7 @@ /usr/bin/kopia repository connect filesystem \ --path="{{ node['home_path'] }}/data/kopia/repository" \ --override-username="il" \ - --override-hostname="{{ node['name'] }}.{{ domain['internal'] }}" \ + --override-hostname="{{ node['name'] }}.{{ domain['internal'] }}" when: - not is_kopia_repository.stat.exists - is_repository_created.rc == 0 @@ -267,7 +267,7 @@ changed_when: true no_log: true - - name: Deploy kopia servcer service file + - name: Deploy kopia server service file ansible.builtin.template: src: "{{ hostvars['console']['node']['config_path'] }}/services/systemd/common/kopia/kopia-server.service.j2" dest: "{{ node['home_path'] }}/.config/systemd/user/kopia-server.service" @@ -276,6 +276,7 @@ mode: "0644" validate: "/usr/bin/systemd-analyze verify %s" become: true + notify: "notification_restart_kopia_server" - name: Enable kopia server service ansible.builtin.systemd: @@ -365,6 +366,18 @@ become: true no_log: true + - name: Check data directory existance + ansible.builtin.stat: + path: "{{ node['home_path'] }}/data/" + register: "is_data_dir" + + - name: Delete lost+found directory on data directory + ansible.builtin.file: + path: "{{ node['home_path'] }}/data/lost+found/" + state: "absent" + become: true + when: is_data_dir.stat.exists + - name: Check data directory empty ansible.builtin.find: path: "{{ node['home_path'] }}/data/" @@ -372,6 +385,7 @@ hidden: true recurse: false register: "is_data_dir_empty" + when: is_data_dir.stat.exists - name: Connect kopia server environment: @@ -382,7 +396,7 @@ --override-username={{ node['name'] }} \ --override-hostname={{ node['name'] }}.{{ domain['internal'] }} become: true - when: is_data_dir_empty.matched == 0 + when: (is_data_dir_empty.matched | default(1)) == 0 changed_when: false failed_when: is_kopia_connected.rc != 0 register: "is_kopia_connected" @@ -413,7 +427,7 @@ {{ node['home_path'] }}/data become: true when: - - is_data_dir_empty.matched == 0 + - (is_data_dir_empty.matched | default(1)) == 0 - (is_kopia_connected.rc | default(1)) == 0 - (is_snapshot_exist.stdout | default('[]', true) | @@ -431,7 +445,7 @@ /usr/bin/kopia repository disconnect become: true when: - - is_data_dir_empty.matched == 0 + - (is_data_dir_empty.matched | default(1)) == 0 - (is_kopia_connected.rc | default(1)) == 0 changed_when: false failed_when: false diff --git a/ansible/roles/infra/tasks/services/set_ca_server.yaml b/ansible/roles/infra/tasks/services/set_ca_server.yaml index 69f0916..6df75e4 100644 --- a/ansible/roles/infra/tasks/services/set_ca_server.yaml +++ b/ansible/roles/infra/tasks/services/set_ca_server.yaml @@ -27,7 +27,7 @@ - name: Pull image containers.podman.podman_image: name: "docker.io/smallstep/step-ca" - tag: "{{ version['containers']['step'] }}" + tag: "{{ version['containers']['ca'] }}" state: "present" pull: true @@ -85,20 +85,20 @@ - name: Check container archive images ansible.builtin.stat: - path: "{{ node['home_path'] }}/archives/containers/docker.io_smallstep_step-ca_{{ version['containers']['step'] }}.tar" + path: "{{ node['home_path'] }}/archives/containers/docker.io_smallstep_step-ca_{{ version['containers']['ca'] }}.tar" register: container_archive_images - name: Save container archive images containers.podman.podman_save: image: - - "docker.io/smallstep/step-ca:{{ version['containers']['step'] }}" - dest: "{{ node['home_path'] }}/archives/containers/docker.io_smallstep_step-ca_{{ version['containers']['step'] }}.tar" + - "docker.io/smallstep/step-ca:{{ version['containers']['ca'] }}" + dest: "{{ node['home_path'] }}/archives/containers/docker.io_smallstep_step-ca_{{ version['containers']['ca'] }}.tar" format: "oci-archive" force: false when: not container_archive_images.stat.exists - name: Fetch container archive images ansible.builtin.fetch: - src: "{{ node['home_path'] }}/archives/containers/docker.io_smallstep_step-ca_{{ version['containers']['step'] }}.tar" + src: "{{ node['home_path'] }}/archives/containers/docker.io_smallstep_step-ca_{{ version['containers']['ca'] }}.tar" dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/" flat: true diff --git a/config/services/containers/app/homepage/config/settings.yaml.j2 b/config/services/containers/app/homepage/config/settings.yaml.j2 index 32eb370..72e9c68 100644 --- a/config/services/containers/app/homepage/config/settings.yaml.j2 +++ b/config/services/containers/app/homepage/config/settings.yaml.j2 @@ -1,4 +1,4 @@ -title: {{ domain['org'] }} homepage +title: {{ domain['org'] }} target: _blank hideVersion: true diff --git a/config/services/containers/app/snappymail/configs/application.ini.j2 b/config/services/containers/app/snappymail/configs/application.ini.j2 new file mode 100644 index 0000000..7d60bda --- /dev/null +++ b/config/services/containers/app/snappymail/configs/application.ini.j2 @@ -0,0 +1,174 @@ +; SnappyMail configuration file +; Managed by Ansible + +[webmail] +title = "SnappyMail" +loading_description = "SnappyMail" +favicon_url = "" +app_path = "" + +theme = "Default" +allow_themes = On +allow_user_background = Off + +language = "en" +allow_languages_on_settings = On +allow_additional_accounts = Off +allow_additional_identities = Off +popup_identity = Off + +messages_per_page = 20 +message_read_delay = 5 +min_refresh_interval = 5 +attachment_size_limit = 25 +compress_output = Off + +[interface] +show_attachment_thumbnail = On + +[contacts] +enable = Off +allow_sync = Off +sync_interval = 20 +type = "sqlite" +pdo_dsn = "host=127.0.0.1;port=3306;dbname=snappymail" +pdo_user = "root" +pdo_password = "" +mysql_ssl_ca = "" +mysql_ssl_verify = On +mysql_ssl_ciphers = "" +sqlite_global = Off +suggestions_limit = 20 + +[security] +custom_server_signature = "SnappyMail" +x_xss_protection_header = "1; mode=block" +gnupg = Off +openpgp = Off +auto_verify_signatures = Off + +allow_admin_panel = Off +admin_login = "" +admin_password = "" +admin_totp = "" + +insecure_cryptkey = Off +force_https = Off +hide_x_mailer_header = On +max_sys_getloadavg = 0 + +content_security_policy = "" +csp_report = Off +encrypt_cipher = "aes-256-cbc-hmac-sha1" +cookie_samesite = "Strict" +secfetch_allow = "" + +[admin_panel] +host = "" +key = "admin" +allow_update = Off +language = "en" + +[ssl] +verify_certificate = On +allow_self_signed = Off +security_level = 1 +cafile = "/etc/ssl/snappymail/{{ root_cert_filename }}" +capath = "" +local_cert = "" +disable_compression = On + +[capa] +dangerous_actions = Off +attachments_actions = On + +[login] +default_domain = "{{ domain['internal'] }}" +allow_languages_on_login = On +determine_user_language = On +determine_user_domain = Off +sign_me_auto = "DefaultOff" +fault_delay = 5 + +[plugins] +enable = Off +enabled_list = "" + +[defaults] +view_editor_type = "Html" +view_layout = 1 +view_use_checkboxes = On +view_show_next_message = On +autologout = 30 +view_html = On +show_images = Off +view_images = "ask" +contacts_autosave = On +mail_list_grouped = Off +mail_use_threads = Off +allow_draft_autosave = On +mail_reply_same_folder = Off +msg_default_action = 1 +collapse_blockquotes = On +allow_spellcheck = Off + +[logs] +enable = On +path = "" +level = 4 +hide_passwords = On +time_zone = "{{ timezone }}" +filename = "stderr" + +auth_logging = On +auth_logging_filename = "auth.log" +auth_logging_format = "[{date:Y-m-d H:i:s}] Auth failed: ip={request:ip} user={imap:login} host={imap:host} port={imap:port}" +auth_syslog = Off +json_response_write_limit = 300 + +[debug] +enable = Off +javascript = Off +css = Off + +[cache] +enable = On +path = "" +index = "v1" +fast_cache_index = "v1" +http = On +http_expires = 3600 +server_uids = On +system_data = On + +[imap] +use_force_selection = Off +use_expunge_all_on_delete = Off +message_list_fast_simple_search = On +message_list_permanent_filter = "" +message_all_headers = Off +show_login_alert = On +fetch_new_messages = On + +[labs] +allow_message_append = Off +smtp_show_server_errors = Off +mail_func_clear_headers = On +mail_func_additional_parameters = Off +folders_spec_limit = 50 +curl_proxy = "" +curl_proxy_auth = "" +custom_login_link = "" +custom_logout_link = "" +http_client_ip_check_proxy = Off +use_local_proxy_for_external_images = On +image_exif_auto_rotate = Off +cookie_default_path = "" +cookie_default_secure = Off +replace_env_in_configuration = "" +boundary_prefix = "" +dev_email = "" +dev_password = "" + +[version] +current = "{{ version['containers']['snappymail'] | regex_replace('^v', '') }}" \ No newline at end of file diff --git a/config/services/containers/app/snappymail/domains/domain.ini.j2 b/config/services/containers/app/snappymail/domains/domain.ini.j2 new file mode 100644 index 0000000..936b6ed --- /dev/null +++ b/config/services/containers/app/snappymail/domains/domain.ini.j2 @@ -0,0 +1,17 @@ +imap_host = "{{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }}" +imap_port = 993 +imap_secure = "SSL" +imap_short_login = On +imap_verify_ssl = On +imap_allow_self_signed = Off + +smtp_host = "{{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }}" +smtp_port = 587 +smtp_secure = "TLS" +smtp_auth = On +smtp_short_login = On +smtp_verify_ssl = On +smtp_allow_self_signed = Off + +sieve_use = Off +white_list = "" diff --git a/config/services/containers/app/snappymail/snappymail.container.j2 b/config/services/containers/app/snappymail/snappymail.container.j2 new file mode 100644 index 0000000..f98456f --- /dev/null +++ b/config/services/containers/app/snappymail/snappymail.container.j2 @@ -0,0 +1,34 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=Snappymail + +After=network-online.target +Wants=network-online.target + +[Container] +Image=ghcr.io/the-djmaze/snappymail:{{ version['containers']['snappymail'] }} +ContainerName=snappymail +HostName=snappymail + +PublishPort={{ services['snappymail']['ports']['http'] }}:8888/tcp + +Volume=%h/data/containers/snappymail/data:/var/lib/snappymail:rw +Volume=%h/containers/snappymail/ssl:/etc/ssl/snappymail:ro +Volume=%h/containers/snappymail/configs:/var/lib/snappymail/_data_/_default_/configs:rw +Volume=%h/containers/snappymail/domains:/var/lib/snappymail/_data_/_default_/domains:rw + +# General +Environment="TZ={{ timezone }}" + +[Service] +# 587: SMTP submission, 993: imaps +ExecStartPre=/usr/bin/nc -zv {{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }} 587 +ExecStartPre=/usr/bin/nc -zv {{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }} 993 +Restart=always +RestartSec=10s +TimeoutStopSec=120 + +[Install] +WantedBy=default.target diff --git a/config/services/containers/auth/authelia/authelia.container.j2 b/config/services/containers/auth/authelia/authelia.container.j2 index c9a6a07..4a325c9 100644 --- a/config/services/containers/auth/authelia/authelia.container.j2 +++ b/config/services/containers/auth/authelia/authelia.container.j2 @@ -49,6 +49,9 @@ Secret=AUTHELIA_LDAP_PASSWORD,target=/run/secrets/AUTHELIA_LDAP_PASSWORD # Database Environment="AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE=/run/secrets/POSTGRES_AUTHELIA_PASSWORD" Secret=POSTGRES_AUTHELIA_PASSWORD,target=/run/secrets/POSTGRES_AUTHELIA_PASSWORD +# SMTP +# SMTP shares LDAP password +Environment="AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/run/secrets/AUTHELIA_LDAP_PASSWORD" Exec=--config /config/authelia.yaml @@ -59,6 +62,7 @@ Exec=--config /config/authelia.yaml ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }} # services['ldap']['ports']['ldaps'] is 6360, but nftables works on 636 the original port ExecStartPre=/usr/bin/nc -zv {{ services['ldap']['domain']['internal'] }}.{{ domain['internal'] }} 636 +ExecStartPre=/usr/bin/nc -zv {{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }} 587 ExecStartPre=sleep 5 Restart=always RestartSec=10s diff --git a/config/services/containers/auth/authelia/config/authelia.yaml.j2 b/config/services/containers/auth/authelia/config/authelia.yaml.j2 index b1a8983..d88f9b4 100644 --- a/config/services/containers/auth/authelia/config/authelia.yaml.j2 +++ b/config/services/containers/auth/authelia/config/authelia.yaml.j2 @@ -87,8 +87,21 @@ storage: # Notification provider notifier: - filesystem: - filename: '/config/notification.txt' + smtp: + address: 'submission://{{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }}:587' + timeout: '5 seconds' + username: 'authelia' + password: '' # $AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE option is designated in container file + sender: 'Authelia ' + identifier: '{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}' + subject: '[Authelia] {title}' + startup_check_address: 'authelia@{{ domain['internal'] }}' + disable_require_tls: false + disable_starttls: false + disable_html_emails: false + tls: + server_name: '{{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }}' + skip_verify: false # This part needs the clients to integrate with Authelia in order for it to activate. identity_providers: diff --git a/config/services/containers/infra/ca/ca.container.j2 b/config/services/containers/infra/ca/ca.container.j2 index 08ee236..4592f2b 100644 --- a/config/services/containers/infra/ca/ca.container.j2 +++ b/config/services/containers/infra/ca/ca.container.j2 @@ -8,7 +8,7 @@ After=network-online.target Wants=network-online.target [Container] -Image=docker.io/smallstep/step-ca:{{ version['containers']['step'] }} +Image=docker.io/smallstep/step-ca:{{ version['containers']['ca'] }} ContainerName=ca HostName=ca diff --git a/config/services/containers/infra/maddy/maddy.container.j2 b/config/services/containers/infra/maddy/maddy.container.j2 index 82d36d7..b43f35d 100644 --- a/config/services/containers/infra/maddy/maddy.container.j2 +++ b/config/services/containers/infra/maddy/maddy.container.j2 @@ -29,7 +29,7 @@ Secret=MADDY_POSTGRES_URL,type=env [Service] ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }} -ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }} 636 +ExecStartPre=/usr/bin/nc -zv {{ services['ldap']['domain']['internal'] }}.{{ domain['internal'] }} 636 Restart=always RestartSec=10s TimeoutStopSec=120