--- - name: Mailbox password is set ansible.builtin.assert: that: mail_relay.password | length > 0 fail_msg: "mail_relay.password of {{ inventory_hostname }} is empty (host_vars)" quiet: true - name: Postfix state ansible.builtin.stat: path: /usr/sbin/postfix register: mail_relay_postfix - name: Exim state ansible.builtin.stat: path: /usr/sbin/exim4 register: mail_relay_exim # Postfix replaces exim4 as the mail-transport-agent; the package scripts do not stop a running exim4 # while policy-rc.d denies service actions, and its listener would block postfix on port 25. - name: Stop exim4 ansible.builtin.systemd_service: name: exim4 state: stopped enabled: false when: mail_relay_exim.stat.exists and not mail_relay_postfix.stat.exists - name: Preseed postfix ansible.builtin.debconf: name: postfix question: "{{ item.question }}" vtype: "{{ item.vtype }}" value: "{{ item.value }}" loop: - { question: postfix/main_mailer_type, vtype: select, value: Internet Site } - { question: postfix/mailname, vtype: string, value: "{{ mail_relay.myhostname }}" } loop_control: label: "{{ item.question }}" when: not mail_relay_postfix.stat.exists # A third-party repository with a bad signature makes the refresh fail on some hosts; the install # below still fails if the index lacks the packages. - name: Refresh apt cache ansible.builtin.apt: update_cache: true cache_valid_time: 3600 failed_when: false # The package starts postfix with the Debian defaults, which listen on all interfaces; on a host whose # port 25 is taken (tranio: mailcow) that start fails. policy-rc.d keeps the start until the # configuration is in place. - name: Install postfix ansible.builtin.apt: name: - postfix - libsasl2-modules - ca-certificates policy_rc_d: "{{ omit if mail_relay_postfix.stat.exists else 101 }}" - name: Postfix configuration when: not ansible_check_mode or mail_relay_postfix.stat.exists block: - name: Local names of the host ansible.builtin.set_fact: mail_relay_local_names: >- {{ [mail_relay.myhostname, inventory_hostname, inventory_hostname_short, ansible_facts['hostname'], 'localhost', 'localhost.localdomain'] | unique }} - name: Sender rewrite map ansible.builtin.template: src: generic.regexp.j2 dest: /etc/postfix/generic owner: root group: root mode: "0644" notify: Restart postfix - name: Relay settings in main.cf ansible.builtin.lineinfile: path: /etc/postfix/main.cf regexp: "^{{ item.key }}\\s*=" line: "{{ item.key }} = {{ item.value }}" loop: "{{ mail_relay_settings | dict2items }}" vars: mail_relay_settings: myhostname: "{{ mail_relay.myhostname }}" myorigin: $myhostname inet_interfaces: "{{ mail_relay.inet_interfaces }}" relayhost: "[{{ mail_relay.host }}]:{{ mail_relay.port }}" smtp_sasl_auth_enable: "yes" smtp_sasl_password_maps: hash:/etc/postfix/sasl_passwd smtp_sasl_security_options: noanonymous smtp_tls_security_level: verify smtp_tls_CAfile: /etc/ssl/certs/ca-certificates.crt smtp_generic_maps: regexp:/etc/postfix/generic loop_control: label: "{{ item.key }}" notify: Restart postfix - name: SASL credentials ansible.builtin.copy: dest: /etc/postfix/sasl_passwd content: "[{{ mail_relay.host }}]:{{ mail_relay.port }} {{ mail_relay.username }}:{{ mail_relay.password }}\n" owner: root group: root mode: "0600" no_log: true register: mail_relay_sasl - name: SASL database state ansible.builtin.stat: path: /etc/postfix/sasl_passwd.db register: mail_relay_sasl_db # A task instead of a handler: a failed run would drop the notification and leave the database missing. - name: Rebuild sasl_passwd database ansible.builtin.command: cmd: postmap /etc/postfix/sasl_passwd when: mail_relay_sasl.changed or not mail_relay_sasl_db.stat.exists changed_when: true notify: Restart postfix # inet_interfaces cannot switch the listener off ("none" is resolved as a host name): the service # is commented out in master.cf instead. - name: SMTP listener in master.cf ansible.builtin.lineinfile: path: /etc/postfix/master.cf regexp: "^#?smtp\\s+inet\\s+n\\s+-\\s+y\\s+-\\s+-\\s+smtpd\\s*$" line: "{{ '' if mail_relay.smtpd_listener else '#' }}smtp inet n - y - - smtpd" notify: Restart postfix - name: Root alias ansible.builtin.lineinfile: path: /etc/aliases regexp: "^root:" line: "root: {{ mail_relay.root_mail }}" create: true owner: root group: root mode: "0644" - name: Alias file state ansible.builtin.stat: path: /etc/aliases register: mail_relay_aliases - name: Alias database state ansible.builtin.stat: path: /etc/aliases.db register: mail_relay_aliases_db # Postfix reads /etc/aliases through the compiled /etc/aliases.db; without a current db local # delivery defers or misroutes. A task instead of a handler, so a failed run cannot lose the rebuild. - name: Rebuild alias database ansible.builtin.command: cmd: newaliases when: >- not mail_relay_aliases_db.stat.exists or mail_relay_aliases_db.stat.mtime < mail_relay_aliases.stat.mtime changed_when: true - name: Apply pending changes ansible.builtin.meta: flush_handlers - name: SASL database permissions ansible.builtin.file: path: /etc/postfix/sasl_passwd.db owner: root group: root mode: "0600" - name: Postfix running ansible.builtin.service: name: postfix state: started enabled: true - name: Proxmox state ansible.builtin.stat: path: /usr/sbin/pveum register: mail_relay_pve # The built-in mail-to-root notification target mails the email of root@pam. - name: Email of root@pam when: mail_relay_pve.stat.exists block: - name: Read root@pam ansible.builtin.command: cmd: pvesh get /access/users/root@pam --output-format json register: mail_relay_pve_root changed_when: false check_mode: false - name: Set the email of root@pam ansible.builtin.command: cmd: "pveum user modify root@pam --email {{ mail_relay.root_mail }}" when: (mail_relay_pve_root.stdout | from_json).email | default('') != mail_relay.root_mail # Run with --tags mail_relay_test: the mail takes the root alias through the relay. - name: Test mail to root ansible.builtin.command: cmd: sendmail -t stdin: | To: root Subject: mail_relay test from {{ inventory_hostname }} Test mail of the mail_relay role. changed_when: true tags: [never, mail_relay_test]