DEBOPS.ROUNDCUBE(5)

NAME
debops.roundcube - Manage Roundcube Web mail with Ansible

SYNOPSIS

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

DESCRIPTION

The debops.roundcube Ansible role manages Roundcube <https://roundcube.net/>, an IMAP Web client written in PHP.

GETTING STARTED

  Default setup

If you don't specify any configuration values, the role will setup a Nginx <https://en.wikipedia.org/wiki/Nginx> HTTP server running a default installation of the latest Roundcube stable release which is then accessible via https://webmail.<your-domain>. SQLite is used as database backend for storing the user settings.

When the LDAP infrastructure is detected on the Roundcube host, the role will install and configure LDAP support in Roundcube. The default address book will be configured to allow only searches in the directory, which is beneficial in larger environments. The password plugin will be enabled and configured to use the LDAP Password Modify Extended Operation (RFC 3062 <https://datatracker.ietf.org/doc/html/rfc3062.html>) driver to allow users to change their passwords.

Roundcube will use the current user credentials to login to the LDAP directory, therefore access to the LDAP entries and attributes depends on the LDAP ACL configuration in the directory itself.

Local spell check support will be configured using the Enchant <https://en.wikipedia.org/wiki/Enchant_(software)> library with aspell spell checker. By default only the English dictionary (aspell-en) is installed, more dictionaries can be added using the roundcube__packages variable.

  Deployment from private or internal git repository

The debops.roundcube role supports deployment of Roundcube from private/internal git repositories over HTTPS. This can be useful when a Roundcube codebase is forked to include custom themes or other changes in the application required for a particular installation.

To do this, you can specify the URL to the git repository using the roundcube__git_repo variable in the form:

https://<username>:<password>@<git-host>/<organization>/<repository>.git

In GitLab, this functionality is called Deploy Tokens <https://docs.gitlab.com/ce/user/project/deploy_tokens/>, while on GitHub users can create Personal Access Tokens <https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line>. The tokens can be generated per-project and allow for read-only access to the git repository.

The access credentials will be stored in the .git/ directory of the cloned Roundcube repository. The role by default puts it in the location specified by the roundcube__git_dir variable, under the /usr/local/src/ subdirectories, with access restricted via UNIX permissions.

The role expects that the checked out commits or tags are signed by a valid GPG key. To include the GPG keys for the staff that creates the modifications of the Roundcube code base, you can include their GPG fingerprints in the roundcube__git_additional_gpg_keys list. They will be imported to the Roundcube UNIX account by the debops.keyring role.

Here are an example Ansible inventory variables that hide the token using the debops.secret role:

roundcube_access_token: '{{ lookup("file", secret + "/roundcube/access_token") }}'
roundcube__git_repo: '{{ "https://" + roundcube_access_token
                         + "@code.example.org/mail-infra/roundcubemail.git" }}'
roundcube__git_additional_gpg_keys: [ 'fingerprint1', 'fingerprint2' ]

Before running the role, make sure to put the credentials in the ansible/secret/roundcube/access_token file inside of the DebOps project directory. The file should contain the credentials in the form of:

<username>:<password>

There should be no new line character at the end.

  IMAP, SMTP and Sieve server detection

The role detects the preferred IMAP, SMTP and Sieve servers by using DNS SRV Records for the following services:

_imaps._tcp.{{ roundcube__domain }} (default port 993)
_submissions._tcp.{{ roundcube__domain }} (default port 465)
_sieve._tcp.{{ roundcube__domain }} (default port 4190)

At the moment, only a single SRV resource record is supported for each service.

If the above SRV resource records are not available, the role will check for the presence of the debops.dovecot and debops.postfix roles using Ansible local facts on the host. If they are found, the respective service (IMAP, SMTP (submission) and/or Sieve) will be configured to be accessed via the host's own FQDN address to support X.509 certificate verification. In this case the services will also use Implicit TLS.

Finally, the role will fall back to using static domain names for the respective services, based on the host domain (roundcube__domain):

IMAP:  imap.example.org:993
SMTP:  smtp.example.org:465
Sieve: sieve.example.org:4190

This allows for deployment of the RoundCube Webmail independent from the respective services, for example on a separate host or VM. The communication with the mail services will be encrypted by default using Implicit TLS, as recommended by RFC 8314 <https://datatracker.ietf.org/doc/html/rfc8314.html>.

  Example inventory

To install and configure Roundcube on a host, it needs to be present in the [debops_service_roundcube] Ansible inventory group. Additional services like memcached, Redis, MariaDB and PostgreSQL can help increase the website performance.

[debops_all_hosts]
webmail
[debops_service_mariadb_server]
webmail
[debops_service_memcached]
webmail
[debops_service_postgresql_server]
webmail
[debops_service_redis_server]
webmail
[debops_service_roundcube]
webmail

  Example playbook

The following playbook can be used with DebOps. If you are using these role without DebOps you might need to adapt them to make them work in your setup.

---
- name: Install and manage Roundcube Web mail
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_roundcube' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  pre_tasks:
    - name: Apply keyring configuration for php environment
      ansible.builtin.import_role:
        name: 'keyring'
      vars:
        keyring__dependent_apt_keys:
          - '{{ php__keyring__dependent_apt_keys }}'
          - '{{ nodejs__keyring__dependent_apt_keys }}'
          - '{{ nginx__keyring__dependent_apt_keys }}'
          - '{{ mariadb__keyring__dependent_apt_keys }}'
        keyring__dependent_gpg_user: '{{ roundcube__keyring__dependent_gpg_user }}'
        keyring__dependent_gpg_keys:
          - '{{ roundcube__keyring__dependent_gpg_keys }}'
      tags: [ 'role::keyring', 'skip::keyring',
              'role::php', 'role::nodejs', 'role::nginx', 'role::mariadb',
              'role::roundcube' ]
    - name: Prepare php environment
      ansible.builtin.import_role:
        name: 'php'
        tasks_from: 'main_env'
      tags: [ 'role::php', 'role::php:env', 'role::logrotate' ]
  roles:
    - role: apt_preferences
      tags: [ 'role::apt_preferences', 'skip::apt_preferences',
              'role::nginx', 'role::php', 'role::nodejs' ]
      apt_preferences__dependent_list:
        - '{{ nginx__apt_preferences__dependent_list }}'
        - '{{ php__apt_preferences__dependent_list }}'
        - '{{ nodejs__apt_preferences__dependent_list }}'
    - role: cron
      tags: [ 'role::cron', 'skip::cron' ]
    - role: logrotate
      tags: [ 'role::logrotate', 'skip::logrotate' ]
      logrotate__dependent_config:
        - '{{ php__logrotate__dependent_config }}'
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm', 'role::nginx' ]
      ferm__dependent_rules:
        - '{{ nginx__ferm__dependent_rules }}'
    - role: python
      tags: [ 'role::python', 'skip::python', 'role::mariadb', 'role::postgresql' ]
      python__dependent_packages3:
        - '{{ ldap__python__dependent_packages3 }}'
        - '{{ mariadb__python__dependent_packages3 if roundcube__database_map[roundcube__database].dbtype == "mysql" else [] }}'
        - '{{ nginx__python__dependent_packages3 }}'
        - '{{ postgresql__python__dependent_packages3 if roundcube__database_map[roundcube__database].dbtype == "postgresql" else [] }}'
      python__dependent_packages2:
        - '{{ ldap__python__dependent_packages2 }}'
        - '{{ mariadb__python__dependent_packages2 if roundcube__database_map[roundcube__database].dbtype == "mysql" else [] }}'
        - '{{ nginx__python__dependent_packages2 }}'
        - '{{ postgresql__python__dependent_packages2 if roundcube__database_map[roundcube__database].dbtype == "postgresql" else [] }}'
    - role: ldap
      tags: [ 'role::ldap', 'skip::ldap' ]
      ldap__dependent_tasks:
        - '{{ roundcube__ldap__dependent_tasks }}'
    - role: php
      tags: [ 'role::php', 'skip::php' ]
      php__dependent_packages:
        - '{{ roundcube__php__dependent_packages }}'
      php__dependent_pools:
        - '{{ roundcube__php__dependent_pools }}'
    - role: nodejs
      tags: [ 'role::nodejs', 'skip::nodejs' ]
      nodejs__npm_dependent_packages:
        - '{{ roundcube__nodejs__npm_dependent_packages }}'
    - role: nginx
      tags: [ 'role::nginx', 'skip::nginx' ]
      nginx__dependent_servers:
        - '{{ roundcube__nginx__dependent_servers }}'
      nginx__dependent_upstreams:
        - '{{ roundcube__nginx__dependent_upstreams }}'
    - role: mariadb
      tags: [ 'role::mariadb', 'skip::mariadb' ]
      mariadb__dependent_users:
        - database: '{{ roundcube__database_map[roundcube__database].dbname }}'
          user: '{{ roundcube__database_map[roundcube__database].dbuser }}'
          password: '{{ roundcube__database_map[roundcube__database].dbpass }}'
          owner: '{{ roundcube__user }}'
          group: '{{ roundcube__group }}'
          home: '{{ roundcube__home }}'
          system: True
          priv_aux: False
      mariadb__server: '{{ roundcube__database_map[roundcube__database].dbhost }}'
      when: roundcube__database_map[roundcube__database].dbtype == 'mysql'
    - role: postgresql
      tags: [ 'role::postgresql', 'skip::postgresql' ]
      postgresql__dependent_roles:
        - db: '{{ roundcube__database_map[roundcube__database].dbname }}'
          role: '{{ roundcube__database_map[roundcube__database].dbuser }}'
          password: '{{ roundcube__database_map[roundcube__database].dbpass }}'
      postgresql__server: '{{ roundcube__database_map[roundcube__database].dbhost
                              if roundcube__database_map[roundcube__database].dbhost != "localhost"
                              else "" }}'
      when: roundcube__database_map[roundcube__database].dbtype == 'postgresql'
    - role: roundcube
      tags: [ 'role::roundcube', 'skip::roundcube' ]

This playbook is also shipped with DebOps at ansible/playbooks/service/roundcube.yml.

  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::roundcube
    Main role tag, should be used in the playbook to execute all of the role tasks as well as role dependencies.

role::roundcube:pkg
    Run tasks related to system package installation.

role::roundcube:deployment
    Run tasks related to the application deployment and update.

role::roundcube:config
    Run tasks related to the Roundcube application configuration.

role::roundcube:database
    Run tasks related to setup or update the database user and schema.

DEFAULT VARIABLE DETAILS

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

  roundcube__configuration

The roundcube__*_configuration variables define the contents of the config/config.php.inc configuration file located in the Roundcube installation directory. The contents are defined using YAML data structures and converted to PHP via the role template.

  Examples

Define a few configuration options with simple syntax:

roundcube__configuration:
  - string_option: 'string value'
  - number_option: 1234
  - bool_true_option: True
  - bool_false_option: False
  - null_option: null
  - empty_array: []
  - empty_string: ''
  - simple_list: [ 'item1', 'item2', 'item3' ]

Define a few configuration options with more advanced syntax which allows for conditions and better control over values:

roundcube__configuration:
  - name: 'string_option'
    value: 'string value'
  - name: 'number_option'
    value: 1234
  - name: 'bool_true_option'
    value: True
  - name: 'bool_false_option'
    value: False
  - name: 'null_option'
    value: null
  - name: 'empty_array'
    value: []
  - name: 'empty_string'
    value: ''
  - name: 'simple_list'
    value: [ 'item1', 'item2, 'item3' ]
  - name: 'option_with_constant'
    value: "'string' . CONSTANT . 'other-string'"
    quotes: False

Insert raw PHP code in the configuration file:

roundcube__configuration:
  - name: 'custom_code'
    raw: |
      if( isset( $_SERVER['MACHINE_NAME'] )) {
          $config['syslog_facility'] = LOG_USER;
      }

Add a multi-level option in the configuration (in a "sub-array"):

roundcube__configuration:
  - name: 'multi_level_option'
    option: [ 'firstlevel', 'secondlevel' ]
    value: True

Create complex PHP array() definitions parsed directly in the template:

roundcube__configuration:
  - name: 'spellcheck_languages'
    array:
      - de: 'Deutsch'
      - en: 'English'
      - pl: 'Polish'
  - name: 'compose_responses_static'
    array:
      - [ 'name': 'Canned Response 1', 'text': 'Static Response One' ]
      - [ 'name': 'Canned Response 2', 'text': 'Static Response Two' ]

You can see more examples in the roundcube__original_configuration and the roundcube__default_configuration variables.

  Syntax

The Roundcube configuration options can be defined using a simple or expanded syntax. Simple syntax uses YAML dictionary keys as the configuration option names (the name equivalent), and dictionary values as the option values (the value equivalent). In this case, only one YAML dictionary key/value pair should be defined at a time.

The expanded definition is enabled when a given configuration entry contains the name parameter and uses a set of parameters for better control over the final output:

name
    Required. Roundcube configuration option name. Configuration entries with the same name parameter are merged in order of appearance; this can be used to change configuration options conditionally.

    If the option parameter is specified, the name parameter is not used as the configuration option name.

value
    Optional. The value of the Roundcube configuration option. It can be specified as a string, a YAML list, True or False boolean, a null value, a positive or negative number. if the value parameter is not specified, the result will be an empty list (array() in PHP).

    The value parameters from multiple configuration entries override each other, with exception of YAML lists - the lists are additive and the result will contain only unique values. Only strings are supported in lists.

array
    Optional. Define a PHP array() <https://www.php.net/manual/en/language.types.array.php> data structure using YAML. The array parameter is used only when the value or raw parameters are not present. The array parameters from multiple configuration entries override each other. See various examples in the role default variables for better idea on how to define the data structures.

raw Optional. String or YAML text block with PHP code, which will be included in the generated configuration file "as is". If the raw parameter is defined, it takes precedence over value or array parameters.

option
    Optional. It can be a string or a YAML list of strings. If defined, the value will be used instead of the name parameter as the Roundcube configuration option name. If a list is defined, each list element will be used as a "subkey", for example [ 'one', 'two' ] value would become $config['one']['two'] in the generated configuration file.

quotes
    Optional, bollean. If defined and False, the quotes around the string value will not be included in the generated configuration file. This can be used to create values which contain PHP constants; the text strings in the values need to be additionally quoted in this case.

state
    Optional. If not specified or present, a given Roundcube option will be present in the configuration file. If absent, a given option will be removed from the configuration file (or not included if not present). If init, the configuration option will be prepared, but will not be active and won't show up on the generated configuration file - this can be used to prepare configuration that will be activated conditionally in another configuration entry. If ignore, a given configuration entry will not be evaluated during role execution. If comment, a given Roundcube configuration option will be present in the generated file, but commented out.

comment
    Optional. String or YAML text block with comments about a given configuration option.

separator
    Optional, boolean. If defined and True, the role will add an empty line before a given configuration option, to allow for better readability.

section
    Optional. Specify the configuration file section name to put a given configuration option into. Section names are defined using the roundcube__configuration_sections variables. If not defined, the configuration option will be put into the unknown section.

copy_id_from
    Optional. Copy the internal "id" of a configuration option specified by the name parameter to the current configuration option. This parameter can be used to reorder configuration options relative to a specific option.

weight
    Optional. Positive or negative number which defines the additional "weight" of an option. Smaller or negative weight will move the option higher in the configuration file, Bigger weight will move the configuration option lower in the configuration file.

value_cast
    Optional. Specify the type of a given value to use in the configuration file. Supported types: int/integer, str/string, float, null/none, bool/boolean. This parameter is onlu useful when the value is defined using another variable, in which case the type information is not preserved by Jinja templating.

  roundcube__configuration_sections

The roundcube__*_configuration_sections variables define what sections are present in the config/config.inc.php configuration file. Using these variables, the sections can be reordered and modified as needed.

  Examples

See the roundcube__default_configuration_sections variable for the list of the sections defined by default.

  Syntax

Configuration sections are defined using a list of YAML dictionaries, each dictionary uses specific parameters:

name
    Required. Name of a given section, used also as its identificator in the main configuration section parameter. Multiple configuration entries with the same name are merged together.

title
    Optional. Set a custom title for a given section. If not specified, the name parameter will be used as the title.

state
    Optional. If not specified or present, a given section will be present in the generated configuration file. If absent, a given section will be removed from the configuration file. if hidden, the section will be present, but the title will not be included in the generated configuration file. if ignore, a given configuration entry will not be evaluated during role execution.

  roundcube__plugins

The roundcube__*_plugins lists define what plugins will be enabled in Roundcube and optionally installed from the Roundcube Plugins repository <https://plugins.roundcube.net> using PHP Composer <https://getcomposer.org>. The composer command is assumed to be installed by the debops.php role.

  Examples

Override the default value in the cloud_button plugin configuration file:

roundcube__plugins:
  - name: 'cloud_button'
    state: 'append'
    options:
      - cloud_button_url: 'https://cloud.example.org/'

See the roundcube__default_plugins for a list of Roundcube plugin definitions which are enabled by the role.

  Syntax

The plugins are defined using YAML dictionaries with specific parameters:

name
    Required. The name of the plugin, also the directory name in the plugins/ subdirectory where the plugin is located. The name parameter is used in the $config['plugins'] configuration option to enable the plugin, only if the state parameter is set to enabled. Multiple configuration entries with the same name parameter are merged together in the order of appearance.

state
    Optional. If not defined or present, the plugin will be installed (if the package parameter is also defined), and its config.inc.php configuration file will be generated, but the plugin itself will not be active in Roundcube. If enabled, the plugin will be installed if needed, and will be activated in the Roundcube configuration file.

    If absent, the plugin will be deactivated, but it will not be uninstalled from the host. If ignore, a given configuration entry won't be evaluated during role execution. If init, a given configuration entry will be prepared but will not be activated - this can be used to prepare configuration for plugins and activate them later conditionally if needed. If append, a given configuration entry is evaluated by the role only if an entry with the same name is already present in the configuration (was defined previously).

package
    Optional. If specified, a given plugin will be installed using PHP Composer from the Roundcube Plugins <https://plugins.roundcube.net/> repository. You need to specify the plugin name using the namespace/plugin format, plugin names can be found on the repository page.

    This parameter is passed to the composer Ansible module as the arguments parameter. You can use any valid value, for example by setting a specific version of a plugin to use by defining it as namespace/plugin:version.

options
    Optional. List of configuration options for a specific plugin which will be stored in the plugins/<plugin_name>/config.inc.php configuration file. The list format is the same as the Roundcube global configuration defined in the roundcube__configuration variables.

AUTHOR
Reto Gantenbein

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