Getting started

Default setup

If you don't specify any configuration values, the role will setup a 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 benefical in larger environments. The password plugin will be enabled and configured to use the LDAP Password Modify Extended Operation (RFC 3062) 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 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, while on GitHub users can create Personal Access Tokens. 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 checking the DNS SRV resource records (as defined by the RFC 6186 and RFC 5804), looking for the IMAPS and SMTPS (submission) service recommended by the RFC 8314 using Implicit TLS. The example DNS resource records checked by the role:

_imaps._tcp          SRV 0 1 993  imap.example.org.
_submissions._tcp    SRV 0 1 465  smtp.example.org.
_sieve._tcp          SRV 0 1 4190 sieve.example.org.

At the moment only a single SRV resource record is supported by the role.

If the above SRV resource records are not available, the debops.roundcube role will check for the presence of the debops.dovecot and the debops.postfix role 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 (ports 993 and 465 respectively).

If both SRV resource records and local Ansible facts are not available, the debops.roundcube role will fall back to using static subdomains for the respective services, based on the host domain:

IMAP:  imap.example.org
SMTP:  smtp.example.org
Sieve: sieve.example.org

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.

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:

    - 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' ]

    - 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.