DEBOPS.POSTFIX(5)

NAME
debops.postfix - Configure Postfix SMTP service using Ansible

SYNOPSIS

debops run service/postfix [--limit group,host,...] [--diff] [--check] [--tags tag1,tag2,...] [--skip-tags tag1,tag2,...] [<ansible-playbook options>] ...

DESCRIPTION

The debops.postfix Ansible role can be used to install and manage Postfix <https://en.wikipedia.org/wiki/Postfix_%28software%29>, a SMTP server. It allows configuration of Postfix using Ansible inventory variables, and provides a flexible API to the Postfix configuration for other Ansible roles when it's used as a role dependency.

GETTING STARTED

  Default configuration

The debops.postfix role configures a basic Postfix SMTP server with configuration similar to the "Internet Site" configuration enabled by default by the Debian package. With the default configuration, SMTP service listens for connections on port 25 from all hosts. Mail relay is authorized from localhost, other hosts are deferred. The SMTP server accepts mail addressed for the host's FQDN, but not it's domain. There's no default relayhost, Postfix delivers the mail directly to other hosts. Local mail is enabled by default, support for mail aliases is provided by the debops.etc_aliases Ansible role.

Additional configuration is defined in separate variables and can be easily disabled or modified if necessary. To do that, you can modify the values of the postfix__combined_maincf and postfix__combined_mastercf variables.

The Postfix service will be configured to use TLS connections and strong encryption by default. This might interfere with SMTP service operation for older installations that don't support required features.

  Example inventory

To install and configure Postfix on a host, it needs to be present in the [debops_service_postfix] Ansible inventory group:

[debops_service_postfix]
hostname

  Example playbook

If you are using this role without DebOps, here's an example Ansible playbook that uses the debops.postfix role:

---
- name: Manage Postfix SMTP service
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_postfix' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  pre_tasks:
    - name: Prepare etc_aliases environment
      ansible.builtin.import_role:
        name: 'etc_aliases'
        tasks_from: 'main_env'
      tags: [ 'role::etc_aliases', 'role::secret', 'role::postfix' ]
    - name: Prepare postfix environment
      ansible.builtin.import_role:
        name: 'postfix'
        tasks_from: 'main_env'
      tags: [ 'role::postfix', 'role::secret', 'role::ferm' ]
  roles:
    - role: secret
      tags: [ 'role::secret', 'role::postfix' ]
      secret__directories:
        - '{{ etc_aliases__secret__directories }}'
        - '{{ postfix__secret__directories }}'
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm' ]
      ferm__dependent_rules:
        - '{{ postfix__ferm__dependent_rules }}'
    - role: etc_aliases
      tags: [ 'role::etc_aliases', 'skip::etc_aliases' ]
    - role: postfix
      tags: [ 'role::postfix', 'skip::postfix' ]

  Ansible tags

You can use Ansible --tags or --skip-tags parameters to limit what tasks are performed during Ansible run. This can be used after a host was first configured to speed up playbook execution, when you are sure that most of the configuration is already in the desired state.

Available role tags:

role::postfix
    Main role tag, should be used in the playbook to execute all of the role tasks as well as role dependencies.

POSTFIX CONFIGURATION GUIDES

Here you can find a few guides that can help you configure more advanced Postfix features. Some of these can and are implemented as separate Ansible roles, here you can see the configuration specific to debops.postfix role.

  Postfix SMTP client with SASL authentication

This configuration is based on the SMTP client SASL authentication HOWTO <http://www.postfix.org/SASL_README.html#client_sasl_enable>.

We will configure Postfix to act as an SMTP client and send all mail to a remote relay which requires SMTP authentication. This guide assumes that you already have an e-mail account set up elsewhere and you know the password.

For SASL authentication to work, Postfix requires libsasl2-modules package (there are some custom ones for LDAP, OTP, SQL). You need to tell debops.postfix role to install one for you, via Ansible inventory:

postfix__packages: [ 'libsasl2-modules' ]

It's best to keep the authentication details out of the Ansible inventory, therefore you should create a separate text file in the ansible/secret/ directory of the project directory (see debops.secret for details).

Create the file ansible/secret/postfix/smtp-auth.key. In it, put the e-mail account username and password in the form:

username:password

You now need to create a lookup table with the authentication credentials for Postfix to consume. You can do this using Ansible inventory:

postfix__lookup_tables:
  - name: 'smtp_sasl_password_maps.in'
    mode: '0600'
    options:
      - name: '[smtp.example.org]'
        value: '{{ lookup("file", secret + "/postfix/smtp-auth.key") }}'

The .in filename suffix tells Postfix to generate a hash table with the file contents. The files should be secured with the 0600 permissions, so only root will be able to read it.

The last piece of the puzzle is the Postfix configuration in the /etc/postfix/main.cf. You can set it via Ansible inventory:

postfix__maincf:
  - name: 'smtp_sasl_auth_enable'
    value: True
    state: present
  - name: 'smtp_tls_security_level'
    value: 'encrypt'
    state: present
  - name: 'smtp_sasl_tls_security_options'
    value: 'noanonymous'
    state: present
  - name: 'relayhost'
    value: '[smtp.example.org]'
    state: present
  - name: 'smtp_sasl_password_maps'
    value: [ 'hash:${config_directory}/smtp_sasl_password_maps' ]
    state: present

When you run the debops.postfix role with the above configuration, Postfix should now send all e-mails to the smtp.example.org relayhost with SMTP client authentication. You can send an e-mail and check the logs in /var/log/mail.log to see if they are relayed correctly.

USAGE AS A ROLE DEPENDENCY

The debops.postfix role can be used as a dependency by other Ansible roles to manage contents of the /etc/postfix/main.cf and /etc/postfix/master.cf configuration files idempotently. Configuration options from multiple roles can be merged together and included in the Postfix configuration, or removed conditionally.

  Dependent role variable

The role exposes the postfix__dependent_maincf and postfix__dependent_mastercf variables which can be used to define Postfix configuration options and services by other Ansible roles through the role dependent variables.

The variables are an YAML lists with YAML dictionaries as entries. A short format of the configuration uses the dictionary key as a name of the dependent role and dictionary value as that role's configuration, in the format defined by Default variable details: postfix__maincf and Default variable details: postfix__mastercf variables, respectively (see playbook excerpt below):

roles:
  - role: debops.postfix
    postfix__dependent_maincf:
      - role_name: '{{ role_name__postfix__dependent_maincf }}'
    postfix__dependent_mastercf:
      - role_name: '{{ role_name__postfix__dependent_mastercf }}'

The extended version of the configuration uses YAML dictionaries with specific parameters:

role
    Required. Name of the role, used to save its configuration in a YAML dictionary on the Ansible Controller. Shouldn't be changed once selected, otherwise the configuration will be desynchronized.

config
    Required. YAML list with configuration of the Postfix options and services in the same format defined by Default variable details: postfix__maincf and Default variable details: postfix__mastercf variables.

state
    Optional. If not specified or present, the configuration will be included in the generated configuration files. If absent, the configuration will be removed from the configuration files. If ignore, a given configuration entries will be skipped during data evaluation and won't affect any existing entries.

    An example extended configuration (playbook excerpt):

    roles:
      - role: debops.postfix
        postfix__dependent_maincf:
          - role: 'role_name'
            config: '{{ role_name__postfix__dependent_maincf }}'
        postfix__dependent_mastercf:
          - role: 'role_name'
            config: '{{ role_name__postfix__dependent_mastercf }}'

    The above configuration layout allows for use of the multiple role dependencies in one playbook by providing configuration of each role in a separate configuration entry.

  Dependent configuration storage and retrieval

The dependent configuration from other roles is stored in the secret/ directory on the Ansible Controller (see debops.secret for more details) in a JSON file (one for each variable), with each role configuration in a separate dictionary. The debops.postfix role reads these files when Ansible local facts indicate that the Postfix is installed, otherwise empty files are created. This ensures that the stale configuration is not present on a new or re-installed host.

The YAML dictionaries from different roles are merged with the main configuration in the postfix__combined_maincf and postfix__combined_mastercf variables that are used to generate the final configuration. The merge order of the different postfix__*_maincf and postfix__*_mastercf variables allows to further affect the dependent configuration through Ansible inventory if necessary, therefore the Ansible roles that use this method don't need to provide additional variables for this purpose themselves.

  Example role default variables

---
# This is a set of default variables in an example 'application' role that uses
# dependent variables to pass configuration to 'debops.postfix' role.
# Additional APT packages to install for Postfix
application__postfix__dependent_packages:
  - 'postfix-pgsql'
# Postfix main.cf configuration
application__postfix__dependent_maincf:
  - name: 'application_destination_recipient_limit'
    value: 1
# Postfix master.cf configuration
application__postfix__dependent_mastercf:
  - name: 'application'
    type: 'unix'
    unpriv: False
    chroot: False
    command: 'pipe'
    args: |
      flags=FR user=application argv=/usr/local/lib/application/bin/in-pipe
      ${nexthop} ${user}

  Example role playbook

---
# This is a playbook for an example 'application' role which uses
# 'debops.postfix' as a dependency and passes its own set of
# configuration options to it.
- name: Manage application
  collections: [ 'debops.debops' ]
  hosts: [ 'debops_service_application' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  pre_tasks:
    # This role along with 'debops.etc_aliases' can be used to maintain the
    # /etc/aliases database.
    #
    #- name: Prepare etc_aliases environment
    #  import_role:
    #    name: 'etc_aliases'
    #    tasks_from: 'main_env'
    #  tags: [ 'role::etc_aliases', 'role::secret', 'role::postfix' ]
    - name: Prepare postfix environment
      ansible.builtin.import_role:
        name: 'postfix'
        tasks_from: 'main_env'
      tags: [ 'role::postfix', 'role::secret', 'role::ferm' ]
  roles:
    - role: secret
      tags: [ 'role::secret', 'role::postfix' ]
      secret__directories:
        - '{{ postfix__secret__directories }}'
    # Normally a 'debops.ferm' role would be here for 'debops.postfix'
    # to manage the firewall. You don't need it if you run the main
    # 'debops.postfix' playbook before yours.
    #
    #- role: ferm
    #  tags: [ 'role::ferm', 'skip::ferm' ]
    #  ferm__dependent_rules:
    #    - '{{ etc_aliases__secret__directories }}'
    #    - '{{ postfix__ferm__dependent_rules }}'
    #- role: etc_aliases
    #  tags: [ 'role::etc_aliases' ]
    - role: postfix
      tags: [ 'role::postfix' ]
      postfix__dependent_packages:
        - '{{ application__postfix__dependent_packages }}'
      postfix__dependent_maincf:
        # Short form of dependent configuration
        - application: '{{ application__postfix__dependent_maincf }}'
      postfix__dependent_mastercf:
        # Expanded form of dependent configuration
        - role: 'application'
          config: '{{ application__postfix__dependent_mastercf }}'
          state: 'present'
    - role: application
      tags: [ 'role::application' ]

DEFAULT VARIABLE DETAILS

some of debops.postfix default variables have more extensive configuration than simple strings or lists, here you can find documentation and examples for them.

  postfix__maincf

Configuration of the postfix__*_maincf variables is described in a separate document, Default variable details: postfix__maincf.

  postfix__mastercf

Configuration of the postfix__*_mastercf variables is described in a separate document, Default variable details: postfix__mastercf.

  postfix__maincf_sections

The /etc/postfix/main.cf configuration file is managed using informal "sections", each section groups the common Postfix options.

The postfix__maincf_sections variable contains a list of sections defined by YAML dictionaries with specific parameters:

name
    Required. Short name of the section, used in the configuration parameters to put a given option in a particular section.

title
    Optional. A short description of the section included as its header.

state
    Optional. If not specified or present, the section will be added in the configuration file. If absent, the section will not be included in the file.

  Examples

Define a set of configuration sections:

postfix__maincf_sections:
  - name: 'base'
  - name: 'admin'
    title: 'Administrator options'
  - name: 'unknown'
    title: 'Other options'

  postfix__lookup_tables

The postfix__*_lookup_tables variables can be used to manage Postfix lookup tables <http://www.postfix.org/DATABASE_README.html>. Each lookup table is a separate file located in the /etc/postfix/ directory. The entries in the variables are merged together, therefore by using the same name key in multiple entries you can modify existing configuration, for example through Ansible inventory.

The lookup tables can be defined by other roles via role dependent variables, however the state of each dependent role is not tracked. Because of that it's best to use separate lookup tables for each Ansible role and join them together at the Postfix configuration level, via options defined in the main.cf or master.cf configuration files.

Each entry that manages a lookup table is a YAML dictionary with specific parameters:

name
    Required. Name of the lookup table to manage, it will be a file in the /etc/postfix/ directory. This parameter is used as an anchor to merge separate entries together.

    Files which names end with the *.in extension are assumed to be hashed tables, and will be processed automatically by make when any changes are detected during role execution.

state
    Optional. If not specified or present, the lookup table will be generated. If absent, the lookup table will be removed (hashed table files are not removed automatically). If ignore, a given configuration entry will not be evaluated by Ansible.

owner
    Optional. The UNIX account which will be the owner of the generated file. If not specified, root will be used by default.

group
    Optional. The UNIX group which will be the primary group of the generated file. If not specified, postfix will be used by default.

mode
    Optional. The attributes set on the generated file. If not specified, 0640 will be set by default.

    If you specify 0600 or 0640 file attributes, the task which manages the file will automatically set the no_log Ansible parameter to True, so that the contents of the file are not logged or displayed during Ansible execution.

no_log
    Optional, boolean. If not specified or False, the task will be processed normally. If True, the task execution will not be logged and any file contents will not be displayed in the Ansible output.

    The parameters below are related to the contents of the lookup table file:

comment
    Optional. String or YAML text block with a comment added at the beginning of the lookup table file.

raw Optional. String or YAML text block with the file contents which will be stored "as-is" in the lookup table file.

config
    Optional. An YAML dictionary which defines an external Postfix lookup table, for example in a SQL database. Each dictionary key is an option name, and dictionary value is the option value. Values can be either strings or YAML lists. See the manpage of specific lookup tables for the supported options.

connection
    Optional. An YAML dictionary which uses the same syntax as the config parameter. The connection parameter can be used to define connection details for a particular database in a separate YAML dictionary, which then can be referenced in multiple lookup tables at once with different query configuration. See the examples below for an example usage.

options
    Optional. An YAML list with lookup table entries. Each entry is a YAML dictionary. If the dictionary has a name key, it will be interpreted as an extended entry with specific parameters:

name
    The lookup key used by Postfix to find the specific entry in the table.

value
    The value or action returned by the lookup table.

state
    Optional. If not specified or present, a given lookup table entry will be added in the file. If absent, a given entry will be removed from the file. If ignore, a given configuration will not be parsed by Ansible. If comment, a given lookup table entry will be added but commented out.

comment
    Optional. A string or YAML text block with a comment related to a given lookup table entry.

    If the name parameter is not found, first entry in a YAML dictionary is parsed as a key/value lookup table entry.

    When a given lookup table is defined by multiple entries, the options parameters are merged together.

content
    Optional. An YAML list with lookup table entries. Each entry can be a string that defines a lookup table key, its value will be defined by the default_action parameter. Otherwise you can specify parameters similar to those supported by the options list. Contents of the content parameter are appended to the options contents. The content parameters from multiple entries are not merged together.

default_action
    Optional. The default action defined for the lookup table entries that don't specify one themselves.

    If the connection or config parameters are specified, for convenience you can specify the options that control the lookup table configuration from the ldap_table(5) <https://manpages.debian.org/ldap_table(5)>, mysql_table(5) <https://manpages.debian.org/mysql_table(5)>, sqlite_table(5) <https://manpages.debian.org/sqlite_table(5)> and pgsql_table(5) <https://manpages.debian.org/pgsql_table(5)> as the lookup table parameters, on the same level as the name parameter.

  Examples

Define a set of virtual mail aliases using a raw YAML text block, stored in a hashed lookup table:

postfix__lookup_tables:
  - name: 'virtual_alias_maps.in'
    raw: |
      name.surname@example.org     user1@example.org
      name.othername@example.org   user2@example.org
postfix__maincf:
  - virtual_alias_maps: [ 'hash:${config_directory}/virtual_alias_maps' ]

Define virtual mailbox table stored in a MySQL database. Lookup table file will be only readable by the root account to secure the password for the database:

postfix__lookup_tables:
  - name: 'virtual_mailbox_maps.cf'
    config:
      hosts:    [ 'db1.example.net', 'db2.example.net' ]
      user:     'mailuser'
      password: 'mailpassword'
      dbname:   'mail'
      query:    "SELECT maildir FROM mailbox WHERE local_part='%u' AND domain='%d' AND active='1'"
postfix__maincf:
  - virtual_mailbox_maps: [ 'proxy:mysql:${config_directory}/virtual_mailbox_maps.cf' ]

The same example with connection details defined in a separate variable which can be reused in multiple lookup tables:

db_connection:
  hosts:    [ 'db1.example.net', 'db2.example.net' ]
  user:     'mailuser'
  password: 'mailpassword'
  dbname:   'mail'
postfix__lookup_tables:
  - name: 'virtual_mailbox_maps.cf'
    connection: '{{ db_connection }}'
    query:      "SELECT maildir FROM mailbox WHERE local_part='%u' AND domain='%d' AND active='1'"
postfix__maincf:
  - virtual_mailbox_maps: [ 'proxy:mysql:${config_directory}/virtual_mailbox_maps.cf' ]

Note that the parameters of a particular table can be defined on the same level as the name parameter, for ease of use.

Create a list of banned HELO/EHLO names which contains the host's IP addresses and FQDN hostname, stored in a hashed lookup table:

postfix__lookup_tables:
  - name: 'banned_helo_names.in'
    content: '{{ ansible_all_ipv4_addresses + ansible_all_ipv6_addresses
                 + [ ansible_fqdn, "localhost", "127.0.0.1" ] }}'
    default_action: 'REJECT You are not me'
postfix__maincf:
  - name: 'smtpd_helo_restrictions'
    value:
      - name: 'check_helo_access hash:${config_directory}/banned_helo_names'
        weight: -100

Create a CIDR lookup table that contains a custom blacklist/whitelist of networks that can talk to the SMTP 'submission' service:

postfix__lookup_tables:
  - name: 'submission_client_access.cidr'
    options:
      - name: '192.0.2.0/24'
        value: 'REJECT Connections not allowed from TEST-NET-1 network'
      - '10.10.0.0/16': 'OK'
postfix__maincf:
  - name: 'submission_smtpd_client_restrictions'
    value:
      - 'check_client_access cidr:${config_directory}/submission_client_access.cidr'
      - 'reject'
postfix__mastercf:
  - name: 'submission'
    options:
      - name: 'smtpd_client_restrictions'
        value: '${submission_smtpd_client_restrictions}'

DEFAULT VARIABLE DETAILS: POSTFIX__MAINCF

The postfix__*_maincf variables are used to define the contents of the /etc/postfix/main.cf configuration file. The variables are YAML lists, concatenated together into postfix__combined_maincf variable, which is passed to the configuration template.

Each list entry is a YAML dictionary, which can be written in a simple or complex form. Entries that control Postfix parameters of the same name will be combined together in order of appearance.

  Simple form of the configuration parameters

Simple form of the Postfix main.cf configuration uses the dictionary key as a option name, and its value as that option's parameters:

postfix__maincf:
  # Option with boolean value
  - append_dot_mydomain: False
  # Option with integer value
  - mailbox_size_limit: 0
  # Option with string value
  - myorigin: '/etc/mailname'
  # Option with multiple values in a list
  - mydestination: [ 'example.org', 'localhost' ]
  # Option with an empty value
  - relayhost: ''
  # Option with multiline value
  - debugger_command: |
      PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
      ddd $daemon_directory/$process_name $process_id & sleep 5

The result of the above configuration in /etc/postfix/main.cf:

append_dot_mydomain = no
mailbox_size_limit = 0
myorigin = /etc/mailname
mydestination = example.org, localhost
relayhost =
debugger_command =
    PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
    ddd $daemon_directory/$process_name $process_id & sleep 5

The parameters in the configuration file will be present in the order they were first defined in the variables.

  Complex form of the configuration parameters

Complex form of the Postfix main.cf configuration is detected when a dictionary key contains a name parameter. In that case, the role will interpret the entry using specific parameters:

name
    The name of the configuration option to manage. This parameter is used as an identifier during the variable parsing.

value
    Required. A value which should be set for a given option. Values can be YAML strings, text blocks, integers, booleans and lists (not dictionaries). Lists can contain simple strings, numbers, or YAML dictionaries that describe each value in greater detail. See Configuration values and their interactions for more details.

option
    Optional. If specified, the option will use this string as the "name" instead of the name value. This is useful to create examples in the configuration file that have the same name as existing configuration options.

comment
    Optional. String or a YAML dictionary with additional comments for a given configuration option.

separator
    Optional, boolean. if True, an empty line will be added above a given option, useful for readability.

state
    Optional. If not specified or present, the option will be present in the finished configuration file.

    If absent, the option will not be included in the configuration file.

    If ignore, the given entry will not be evaluated by the role, and no changes will be done to the preceding parameters with the same name. This can be used to conditionally activate entries with different configuration.

    If hidden, the option will not be displayed in the configuration file, but any comments will be present. This can be used to add free-form comments in the Postfix configuration file.

    If comment, the option will be present, but it will be commented out. This can be used to add examples in the configuration file.

    If append, the given entry will be evaluated only if an entry with the same name already exists. The current state will not be changed.

section
    Optional. Name of the section of the /etc/postfix/main.cf configuration file in which a given option should be placed. If it's no specified, unknown section is used. See postfix__maincf_sections for more details.

weight
    Optional. A positive or negative number which affects the position of a given option in the configuration file, within the selected section. The higher the number, the more a given option "weighs" and the lower it will be placed in the finished configuration file. Negative numbers make the option "lighter" and it will be placed higher.

copy_id_from
    Optional. This is an internal role parameter which can be used to change the relative position of a given option in the configuration file. If you specify a name of an option, it's internal "id" number (used for sorting) will be copied to the current option. This can be used to move options around to different configuration file sections.

  Examples

Define the previous example using complex form:

postfix__maincf:
  - name: 'append_dot_mydomain'
    comment: 'appending .domain is the MUA's job.'
    value: False
    state: 'comment'
  - name: 'mailbox_size_limit'
    value: 0
  - name: 'myorigin'
    value: '/etc/mailname'
  - name: 'mydestination'
    value: [ 'example.org', 'localhost' ]
    weight: 100
  - name: 'relayhost'
    value: ''
  - name: 'debugger_command'
    value: |
      PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
      ddd $daemon_directory/$process_name $process_id & sleep 5

The result of the above configuration in /etc/postfix/main.cf:

# appending .domain is the MUA's job.
#append_dot_mydomain = no
mailbox_size_limit = 0
myorigin = /etc/mailname
relayhost =
debugger_command =
    PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
    ddd $daemon_directory/$process_name $process_id & sleep 5
mydestination = example.org, localhost

The parameters in the configuration file will be present in the order they were first defined in the variables, unless the weight parameter is added, which will change the order.

  Configuration values and their interactions

The Postfix main.cf configuration <http://www.postfix.org/postconf.5.html> uses key-value format, with values being either strings, numbers, booleans or lists. The first three types are handled by the debops.postfix role as normal.

List values are by default concatenated to allow easy extension of existing values. The values in a list are either YAML strings, numbers, or can be defined as YAML dictionaries with specific parameters:

name or param
    Required. The value itself, usually a string.

state
    Optional. If not defined or present, the value will be included in the list.

    If absent, the value will be removed from the list.

    If ignore, the given entry will not be evaluated by the role, and will not change the state of the value. This can be used to enable or disable values conditionally.

weight
    Optional. A positive or negative number which affects the position of a given value in the list. The higher the number, the more a given value "weighs" and the lower it will be placed in the finished list. Negative numbers make the value "lighter" and it will be placed higher.

  Example list

Define a list with conditional values:

postfix__maincf:
  - name: 'mydestination'
    value:
      - '{{ ansible_fqdn }}'
      - name: '{{ ansible_domain }}'
        state: '{{ "present"
                   if (ansible_domain.split(".")|count > 1)
                   else "ignore" }}'
      - name: 'localhost'
        weight: 100

  Base value replacement

Repeating the string, number or boolean option will result in the latter entry replacing the former entry:

postfix__maincf:
  # Old value
  - myorigin: '/dev/null'
  # New, active value
  - myorigin: '/etc/mailname'

The result of the above configuration in /etc/postfix/main.cf:

myorigin = /etc/mailname

  Lists are merged together

The list parameters behave differently. Specifying the same option multiple times, if the preceding option was a list, will add the specified parameters to the list:

postfix__maincf:
  - mydestination: [ 'example.org', 'localhost' ]
  - mydestination: [ 'example.com' ]

The result of the above configuration in /etc/postfix/main.cf:

mydestination = example.org, localhost, example.com

  How to reset a list

If the option was a list, and subsequent option specified a boolean, string or a number, the value will replace the previous one, instead of adding to a list. This can be used to reset the list instead of appending to it.

postfix__maincf:
  - inet_interfaces: [ '127.0.0.1', '::1' ]
  - inet_interfaces: 'all'

The result of the above configuration in /etc/postfix/main.cf:

inet_interfaces = all

  Lists don't add duplicates

The role checks if a given list element is already present, and it won't add a duplicate value to the list:

postfix__maincf:
  - mydestination: [ 'example.org', 'localhost' ]
  - mydestination: [ 'example.org' ]

The result of the above configuration in /etc/postfix/main.cf:

mydestination = example.org, localhost

DEFAULT VARIABLE DETAILS: POSTFIX__MASTERCF

The postfix__*_mastercf variables are used to define the contents of the /etc/postfix/master.cf configuration file. The variables are YAML lists, concatenated together into postfix__combined_mastercf variable, which is passed to the configuration template.

Each list entry is a YAML dictionary. Entries that control Postfix parameters of the same name will be combined together in order of appearance.

  Configuration variable format

The master.cf configuration entries are defined using specific parameters:

name
    The name of the Postfix service to manage. This parameter is used as an identifier during the variable parsing. If command parameter is not specified, the service will use its name (or service parameter, if present) as the command to execute.

service
    Optional. If specified, the service will use this string as the "name" instead of the name value. This is useful to create examples in the configuration file that have the same name as existing configuration options.

type
    Required. Specify the service type (inet, unix, fifo, pass).

private
    Optional, boolean. Specify the service "private" status.

unpriv
    Optional, boolean. Specify the service "unprivileged" status.

chroot
    Optional, boolean. Specify the service "chroot" status.

wakeup
    Optional. Time in seconds after which the Postfix master process will connect to the service and send a wake up signal.

maxproc
    Optional. Maximum number of processes that can run at the same time for a given service.

command
    Optional. The Postfix command to execute for a given service. If not specified, service and name parameters are used in that order of appearance.

args
    Optional. String or an YAML text block with custom arguments to pass to a given service.

options
    Optional. YAML list of main.cf configuration options to override for a given service. The syntax is the same as the postfix__*_maincf configuration variables. See Default variable details: postfix__maincf for more details.

comment
    Optional. String or a YAML dictionary with additional comments for a given service.

separator
    Optional, boolean. if True, an empty line will be added above a given service, useful for readability.

state
    Optional. If not specified or present, the service will be present in the finished configuration file.

    If absent, the service will not be included in the configuration file.

    If ignore, the given entry will not be evaluated by the role, and no changes will be done to the preceding parameters with the same name. This can be used to conditionally activate entries with different configuration.

    If hidden, the service will not be displayed in the configuration file, but any comments will be present. This can be used to add free-form comments in the Postfix configuration file.

    If comment, the service will be present, but it will be commented out. This can be used to add examples in the configuration file.

    If append, the given entry will be evaluated only if an entry with the same name already exists. The current state will not be changed.

weight
    Optional. A positive or negative number which affects the position of a given service in the configuration file. The higher the number, the more a given service "weighs" and the lower it will be placed in the finished configuration file. Negative numbers make the service "lighter" and it will be placed higher.

copy_id_from
    Optional. This is an internal role parameter which can be used to change the relative position of a given option in the configuration file. If you specify a name of an option, it's internal "id" number (used for sorting) will be copied to the current option. This can be used to move options around to different configuration file sections.

  Examples

Define a SMTP Postfix service

postfix__mastercf:
  - name: 'smtp'
    type: 'inet'
    private: False
    chroot: True
    command: 'smtpd'

The result of the above configuration in /etc/postfix/master.cf:

smtp      inet  n       -       y       -       -       smtpd

The parameters in the configuration file will be present in the order they were first defined in the variables, unless the weight parameter is added, which will change the order.

AUTHOR
Maciej Delmanowski

COPYRIGHT
2014-2024, Maciej Delmanowski, Nick Janetakis, Robin Schneider and others