- name: Allow SMTP traffic ufw: rule: allow port: 25 proto: tcp - name: Allow mail submission traffic ufw: rule: allow port: 587 proto: tcp - name: Allow IMAP over TLS traffic ufw: rule: allow port: 993 proto: tcp - name: Create docker-mailserver directory file: path: "/srv/mail" state: directory owner: root group: docker # to allow access to the compose file mode: '0755' - name: Create docker-mailserver directories file: path: "/srv/mail/{{ item }}" state: directory owner: root group: docker mode: '0750' loop: - env - config/rspamd/override.d - name: Create maillogs directory file: path: /srv/mail/maillogs state: directory mode: '0755' # container startup script needs to traverse and chown subdirs # stat+chown: avoids UID/GID lookup warnings for container-internal UIDs not present on host - name: Stat maillogs directory stat: path: /srv/mail/maillogs register: maillogs_stat - name: Set maillogs directory ownership command: chown 113:0 /srv/mail/maillogs when: maillogs_stat.stat.uid != 113 or maillogs_stat.stat.gid != 0 - name: Create mailstate directory file: path: /srv/mail/mailstate state: directory owner: root group: root mode: '0755' # container startup script needs to traverse and chown subdirs - name: Create maildata directory file: path: /srv/mail/maildata state: directory mode: '0751' # container startup script needs to traverse and chown subdirs - name: Create config directory file: path: /srv/mail/config state: directory mode: '0751' # container startup script needs to traverse and chown subdirs - name: Create rainloop data directory file: path: /srv/mail/rainloop/data state: directory mode: '0755' # stat+chown: avoids UID/GID lookup warnings for container-internal UIDs not present on host - name: Stat rainloop data directory stat: path: /srv/mail/rainloop/data register: rainloop_data_stat - name: Set rainloop data directory ownership command: chown 991:991 /srv/mail/rainloop/data when: rainloop_data_stat.stat.uid != 991 or rainloop_data_stat.stat.gid != 991 - name: Ensure certbot is installed apt: name: certbot state: present - name: Check if mail TLS certificate already exists ansible.builtin.stat: path: /etc/letsencrypt/live/{{ mail_hostname }}/fullchain.pem register: mail_cert - name: Stop Caddy to free port 80 for certbot community.docker.docker_compose_v2: project_src: /srv/caddy state: stopped when: not mail_cert.stat.exists - name: Obtain a Let's Encrypt certificate for {{ mail_hostname }} command: > certbot certonly --standalone -d {{ mail_hostname }} --non-interactive --agree-tos -m postmaster@{{ domain }} when: not mail_cert.stat.exists tags: config - name: Restart Caddy after certbot community.docker.docker_compose_v2: project_src: /srv/caddy state: present build: never when: not mail_cert.stat.exists - name: Deploy mail compose file template: src: compose.yml.j2 dest: /srv/mail/compose.yml notify: Restart mail stack tags: config - name: Deploy mailserver environment file template: src: mailserver.env.j2 dest: /srv/mail/env/mailserver.env mode: '0640' owner: root group: docker notify: Restart mailserver tags: config - name: Deploy rspamd web UI config template: src: worker-controller.inc.j2 dest: /srv/mail/config/rspamd/override.d/worker-controller.inc mode: '0644' notify: Restart mailserver tags: config - name: Seed mail accounts into postfix-accounts.cf before first start ansible.builtin.shell: | grep -qF "{{ item.address }}" /srv/mail/config/postfix-accounts.cf 2>/dev/null && exit 0 hash=$(openssl passwd -6 {{ item.password | quote }}) printf '%s|{SHA512-CRYPT}%s\n' "{{ item.address }}" "${hash}" >> /srv/mail/config/postfix-accounts.cf loop: "{{ mail_users }}" no_log: true args: executable: /bin/bash tags: users - name: Start mailserver community.docker.docker_compose_v2: project_src: /srv/mail state: present build: never tags: config - import_tasks: users.yml - import_tasks: aliases.yml # webmail interface - import_tasks: rainloop.yml