mail_relay: postfix relays all mail through the mailcow submission port with a mailbox per host, replaces mail_aliases
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
# Entries of /etc/aliases managed here: name -> recipient.
|
||||
# Root mail goes to the admin mailbox (doc/host-mail-relay.md in the kubernetes repo).
|
||||
mail_aliases:
|
||||
root: admin@ruekov.eu
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
- name: Rebuild alias database
|
||||
ansible.builtin.command:
|
||||
cmd: newaliases
|
||||
changed_when: true
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
# Postfix reads /etc/aliases through the compiled /etc/aliases.db. Without the db, local delivery
|
||||
# defers every mail with "alias database unavailable".
|
||||
- name: Alias entries
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/aliases
|
||||
regexp: "^{{ item.key }}:"
|
||||
line: "{{ item.key }}: {{ item.value }}"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop: "{{ mail_aliases | dict2items }}"
|
||||
notify: Rebuild alias database
|
||||
|
||||
- name: Alias database exists
|
||||
ansible.builtin.command:
|
||||
cmd: newaliases
|
||||
creates: /etc/aliases.db
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
# Every host sends its mail through the mailcow submission port with its own mailbox
|
||||
# (doc/host-mail-relay.md in the kubernetes repo). The password is set in host_vars.
|
||||
mail_relay:
|
||||
host: tranio.ruekov.eu
|
||||
port: 587
|
||||
# SASL login of the mailcow mailbox; the password is an SMTP-only app password of it.
|
||||
username: "{{ inventory_hostname_short }}.system@mail.ruekov.eu"
|
||||
# Sender address every local address is rewritten to; mailcow accepts only addresses of the login.
|
||||
sender: "{{ mail_relay.username }}"
|
||||
password: ""
|
||||
# Root mail goes to this address: /etc/aliases and, on Proxmox hosts, the email of root@pam.
|
||||
root_mail: admin@ruekov.eu
|
||||
myhostname: "{{ inventory_hostname }}"
|
||||
# The local postfix only takes mail from the host itself.
|
||||
inet_interfaces: loopback-only
|
||||
# The smtp inet service of master.cf; off where another daemon holds port 25 (tranio: mailcow).
|
||||
smtpd_listener: true
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Restart postfix
|
||||
ansible.builtin.service:
|
||||
name: postfix
|
||||
state: restarted
|
||||
@@ -0,0 +1,208 @@
|
||||
---
|
||||
- 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]
|
||||
@@ -0,0 +1,4 @@
|
||||
# mailcow rejects a sender that does not belong to the login of the submission, so every address of
|
||||
# this host is rewritten to the mailbox. The map also applies to recipient addresses: only the names
|
||||
# of this host match, never a recipient such as admin@ruekov.eu.
|
||||
/^[^@]+@({{ mail_relay_local_names | map('regex_escape') | join('|') }})$/ {{ mail_relay.sender }}
|
||||
Reference in New Issue
Block a user