diff --git a/library/roles/roundcube/defaults/main.yml b/library/roles/roundcube/defaults/main.yml new file mode 100644 index 0000000..4f1ecb0 --- /dev/null +++ b/library/roles/roundcube/defaults/main.yml @@ -0,0 +1,30 @@ +--- +roundcube_version: 1.4.1 +roundcube_dist_filename: 'roundcubemail-{{ roundcube_version }}-complete.tar.gz' +roundcube_download_url: 'https://github.com/roundcube/roundcubemail/releases/download/{{ roundcube_version }}/{{ roundcube_dist_filename }}' +roundcube_use_redis: True +roundcube_use_memcache: False +roundcube_web_basedir: /var/www/html +roundcube_web_root: '{{ roundcube_web_basedir }}/roundcube' +roundcube_data_base_dir: /srv +roundcube_data_dir: '{{ roundcube_data_base_dir }}/roundcube/data' +roundcube_oc_dir: '{{ roundcube_data_base_dir }}/roundcube/oc_keys' + +roundcube_servername: '{{ ansible_fqdn }}' +roundcube_servernames: + - { webroot: '{{ roundcube_web_root }}', id: 1, name: '{{ roundcube_servername }}' } + +roundcube_user: roundcube + +roundcube_db: pgsql +roundcube_db_host: localhost +roundcube_db_name: roundcubemail +roundcube_db_user: roundcube_u +#roundcube_db_pwd: 'Use a vault file' + +roundcube_admin_user: nc_admin +#roundcube_admin_u_pwd: 'Use a vault file' + +roundcube_encryption_enabled: True +roundcube_ldap_auth: False + diff --git a/library/roles/roundcube/meta/main.yml b/library/roles/roundcube/meta/main.yml new file mode 100644 index 0000000..4be987f --- /dev/null +++ b/library/roles/roundcube/meta/main.yml @@ -0,0 +1,7 @@ +--- +dependencies: + - { role: '../../library/roles/postgresql' } + - { role: '../../library/roles/php-fpm' } + - { role: '../../library/roles/nginx' } + - { role: '../../library/roles/redis', when nextcloud_use_redis } + - { role: '../../library/roles/memcached', when nextcloud_use_memcache } diff --git a/library/roles/roundcube/tasks/main.yml b/library/roles/roundcube/tasks/main.yml new file mode 100644 index 0000000..5d8f035 --- /dev/null +++ b/library/roles/roundcube/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- import_tasks: roundcube-nginx.yml +- import_tasks: roundcube-install.yml diff --git a/library/roles/roundcube/tasks/roundcube-install.yml b/library/roles/roundcube/tasks/roundcube-install.yml new file mode 100644 index 0000000..f5d2fd6 --- /dev/null +++ b/library/roles/roundcube/tasks/roundcube-install.yml @@ -0,0 +1,83 @@ +--- +- block: + - name: Create the roundcube webroot + file: dest={{ roundcube_web_basedir }} owner=root group=root state=directory + + - name: Create the roundcube data directory + file: dest={{ nextcloud_data_dir }} state=directory owner={{ item.user }} group={{ item.user }} + with_items: '{{ phpfpm_pools }}' + + - name: Get roundcube + get_url: url={{ roundcube_download_url }} dest=/srv/{{ roundcube_dist_filename }} + + - name: Unpack the roundcube archive + unarchive: remote_src=yes src=/srv/{{ roundcube_dist_filename }} dest={{ roundcube_web_basedir }} owner={{ item.user }} group={{ item.user }} + args: + creates: '{{ item.doc_root }}/index.php' + with_items: '{{ phpfpm_pools }}' + + tags: [ 'roundcube' ] + +- block: + - name: Configure the roundcube instance + shell: cd {{ item.doc_root }} ; php occ maintenance:install --database="{{ roundcube_db }}" --database-host "{{ roundcube_db_host }}" --database-name "{{ roundcube_db_name }}" --database-user "{{ roundcube_db_user }}" --database-pass "{{ nextcloud_db_pwd }}" --admin-user "{{ nextcloud_admin_user }}" --admin-pass "{{ nextcloud_admin_u_pwd }}" --data-dir={{ roundcube_data_dir }} ; touch {{ roundcube_data_dir }}/.ht_roundcube_setup + args: + creates: '{{ roundcube_data_dir }}/.ht_roundcube_setup' + with_items: '{{ phpfpm_pools }}' + + - name: Set the trusted domains list + shell: cd {{ item.webroot }} ; php occ config:system:set trusted_domains {{ item.id }} --value={{ item.name }} ; touch {{ roundcube_data_dir }}/.ht_roundcube_trusted_domains + args: + creates: '{{ roundcube_data_dir }}/.ht_roundcube_trusted_domains' + with_items: '{{ roundcube_servernames }}' + + - name: Setup the cron configuration + shell: cd {{ item.webroot }} ; php occ background:cron ; touch {{ roundcube_data_dir }}/.ht_roundcube_cron + args: + creates: 'touch {{ nextcloud_data_dir }}/.ht_nextcloud_cron' + with_items: '{{ nextcloud_servernames }}' + tags: [ 'nextcloud', 'nextcloud_config_cron' ] + + - name: Install the nextcloud cron job + cron: user={{ item.user }} minute="*/15" job="php -f {{ item.doc_root }}/cron.php" name="NextCloud cron job" + with_items: '{{ phpfpm_pools }}' + tags: [ 'nextcloud', 'nextcloud_config_cron' ] + + become: True + become_user: '{{ nextcloud_user }}' + tags: [ 'nextcloud', 'nextcloud_config' ] + +- block: + - name: Create the nextcloud encryption keys directory + become_user: root + file: dest={{ nextcloud_oc_dir }} state=directory owner={{ item.user }} group={{ item.user }} + with_items: '{{ phpfpm_pools }}' + + - name: Activate global encryption + shell: cd {{ item.doc_root }} ; php occ app:enable encryption ; php occ encryption:enable ; php occ encryption:enable-master-key ; php occ encryption:change-key-storage-root {{ nextcloud_oc_dir }} ; touch {{ nextcloud_oc_dir }}/.ht_nextcloud_oc + args: + creates: '{{ nextcloud_oc_dir }}/.ht_nextcloud_oc' + with_items: '{{ phpfpm_pools }}' + + become: True + become_user: '{{ nextcloud_user }}' + when: nextcloud_encryption_enabled + tags: [ 'nextcloud', 'nextcloud_config', 'nextcloud_config_oc' ] + +- block: + - name: Enable ldap + shell: cd {{ item.doc_root }} ; php occ app:enable user_ldap ; touch {{ item.doc_root }}/.ht_nextcloud_ldap_enabled + args: + creates: '{{ item.doc_root }}/.ht_nextcloud_ldap_enabled' + with_items: '{{ phpfpm_pools }}' + + - name: Configure ldap + shell: cd {{ item.doc_root }} ; php occ ldap:create-empty-config ; touch {{ item.doc_root }}/.ht_nextcloud_ldap_configured + args: + creates: '{{ item.doc_root }}/.ht_nextcloud_ldap_configured' + with_items: '{{ phpfpm_pools }}' + + become: True + become_user: '{{ nextcloud_user }}' + when: nextcloud_ldap_auth + tags: [ 'nextcloud', 'nextcloud_config', 'nextcloud_config_ldap' ] diff --git a/library/roles/roundcube/tasks/roundcube-nginx.yml b/library/roles/roundcube/tasks/roundcube-nginx.yml new file mode 100644 index 0000000..4c43aa9 --- /dev/null +++ b/library/roles/roundcube/tasks/roundcube-nginx.yml @@ -0,0 +1,17 @@ +--- +- block: + - name: Install the nginx virtualhosts + template: src=nginx-virthost.conf dest=/etc/nginx/sites-available/{{ item.virthost }} owner=root group=root mode=0444 + with_items: '{{ phpfpm_pools }}' + notify: Reload nginx + + - name: Enable the nginx virtualhosts + file: src=/etc/nginx/sites-available/{{ item.virthost }} dest=/etc/nginx/sites-enabled/{{ item.virthost }} state=link + with_items: '{{ phpfpm_pools }}' + notify: Reload nginx + + - name: Create the nginx body temp directory + file: dest={{ nginx_client_body_temp_dir }} state=directory owner=www-data group=www-data mode=1700 + when: nginx_client_body_temp_dir is defined + + tags: [ 'nginx', 'virtualhost', 'nextcloud' ] diff --git a/library/roles/roundcube/templates/nginx-virthost.conf b/library/roles/roundcube/templates/nginx-virthost.conf new file mode 100644 index 0000000..d39f3f7 --- /dev/null +++ b/library/roles/roundcube/templates/nginx-virthost.conf @@ -0,0 +1,151 @@ +upstream php-handler { + server {{ item.listen }}; + #server unix:/var/run/php5-fpm.sock; +} + +server { + listen 80; + listen [::]:80; + server_name {{ item.nginx_servername }}; + # enforce https + location ~ /\.(?!well-known).* { + deny all; + access_log off; + log_not_found off; + return 404; + } + include /etc/nginx/snippets/letsencrypt-proxy.conf; + location / { + return 301 https://$server_name$request_uri; + } +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name {{ item.nginx_servername }}; + + include /etc/nginx/snippets/nginx-server-ssl.conf; + server_tokens off; + + # Add headers to serve security related headers + # Before enabling Strict-Transport-Security headers please read into this + # topic first. + # add_header Strict-Transport-Security "max-age=15768000; + # includeSubDomains; preload;"; + # + # WARNING: Only add the preload option once you read about + # the consequences in https://hstspreload.org/. This option + # will add the domain to a hardcoded list that is shipped + # in all major browsers and getting removed from this list + # could take several months. + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag none; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + + # Path to the root of your installation + root {{ item.doc_root }}; + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + location = /favicon.ico { + log_not_found off; + access_log off; + } + # The following 2 rules are only needed for the user_webfinger app. + # Uncomment it if you're planning to use this app. + #rewrite ^/.well-known/host-meta /public.php?service=host-meta last; + #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json + # last; + + location = /.well-known/carddav { + return 301 $scheme://$host/remote.php/dav; + } + location = /.well-known/caldav { + return 301 $scheme://$host/remote.php/dav; + } + + # set max upload size + client_max_body_size 512M; + fastcgi_buffers 64 4K; + + # Enable gzip but do not remove ETag headers + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; + + # Uncomment if your server is build with the ngx_pagespeed module + # This module is currently not supported. + #pagespeed off; + + location / { + rewrite ^ /index.php$uri; + } + + location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ { + deny all; + } + location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { + deny all; + } + + location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) { + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTPS on; + #Avoid sending the security headers twice + fastcgi_param modHeadersAvailable true; + fastcgi_param front_controller_active true; + fastcgi_pass php-handler; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + } + + location ~ ^/(?:updater|ocs-provider)(?:$|/) { + try_files $uri/ =404; + index index.php; + } + + # Adding the cache control header for js and css files + # Make sure it is BELOW the PHP block + location ~ \.(?:css|js|woff|svg|gif)$ { + try_files $uri /index.php$uri$is_args$args; + add_header Cache-Control "public, max-age=15778463"; + # Add headers to serve security related headers (It is intended to + # have those duplicated to the ones above) + # Before enabling Strict-Transport-Security headers please read into + # this topic first. + # add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;"; + # + # WARNING: Only add the preload option once you read about + # the consequences in https://hstspreload.org/. This option + # will add the domain to a hardcoded list that is shipped + # in all major browsers and getting removed from this list + # could take several months. + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag none; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + # Optional: Don't log access to assets + access_log off; + } + + location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ { + try_files $uri /index.php$uri$is_args$args; + # Optional: Don't log access to other assets + access_log off; + } +} + diff --git a/library/roles/roundcube/vars/main.yml b/library/roles/roundcube/vars/main.yml new file mode 100644 index 0000000..797adb8 --- /dev/null +++ b/library/roles/roundcube/vars/main.yml @@ -0,0 +1,57 @@ +--- +nginx_use_common_virthost: False +phpfpm_default_user: '{{ roundcube_user }}' +phpfpm_default_pool_name: roundcube +redis_install: True +http_port: 80 +https_port: 443 + +php_version: 7.2 + +php_additional_packages: + - 'php{{ php_version }}-gd' + - 'php{{ php_version }}-json' + - 'php{{ php_version }}-ldap' + - 'php{{ php_version }}-{{ roundcube_db }}' + - 'php{{ php_version }}-xml' + - 'php{{ php_version }}-mbstring' + - 'php{{ php_version }}-intl' + - 'php{{ php_version }}-curl' + - 'php{{ php_version }}-zip' + - 'php{{ php_version }}-bz2' + - 'php{{ php_version }}-gmp' + - 'php-imagick' + - 'php-redis' + - 'php-apcu' + +phpfpm_default_memory_limit: "64M" + +php_global_settings: + - { option: 'zlib.output_compression', value: 'Off' } + - { option: 'post_max_size', value: '6M' } + - { option: 'upload_max_filesize', value: '5M' } + - { option: 'session.auto_start', value: 'Off' } + - { option: 'suhosin.session.encrypt', value: 'Off' } + - { option: 'session.gc_maxlifetime', value: '21600' } + - { option: 'session.gc_divisor', value: '500' } + - { option: 'session.gc_probability', value: '1' } + + - { option: 'always_populate_raw_post_data', value: '-1' } + - { option: 'allow_url_fopen', value: 'on' } + - { option: 'max_execution_time', value: '240' } + - { option: 'memory_limit', value: '{{ phpfpm_default_memory_limit }}' } + - { option: 'max_input_vars', value: '1400' } + - { option: 'opcache.enable', value: '1' } + - { option: 'opcache.enable_cli', value: '1' } + - { option: 'opcache.interned_strings_buffer', value: '8' } + - { option: 'opcache.max_accelerated_files', value: '10000' } + - { option: 'opcache.memory_consumption', value: '128' } + - { option: 'opcache.save_comments', value: '1' } + - { option: 'opcache.revalidate_freq', value: '1' } + +php_cli_global_settings: '{{ php_global_settings }}' + + +phpfpm_pools: + - { pool_name: '{{ phpfpm_default_pool_name }}', app_context: '{{ phpfpm_default_context }}', user: '{{ phpfpm_default_user }}', group: '{{ phpfpm_default_group }}', listen: '{{ phpfpm_default_listen }}', allowed_clients: '{{ phpfpm_default_allowed_clients }}', pm: '{{ phpfpm_default_pm }}', pm_max_children: '{{ phpfpm_default_pm_max_children }}', pm_start_servers: '{{ phpfpm_default_pm_start_servers }}', pm_min_spare: '{{ phpfpm_default_pm_min_spare_servers }}', pm_max_spare: '{{ phpfpm_default_pm_max_spare_servers }}', pm_max_requests: '{{ phpfpm_default_pm_max_requests }}', pm_status_enabled: '{{ phpfpm_default_pm_status_enabled }}', pm_status_path: '{{ phpfpm_default_pm_status_path }}', ping_enabled: '{{ phpfpm_default_ping_enabled }}', ping_path: '{{ phpfpm_default_ping_path }}', ping_response: '{{ phpfpm_default_ping_response }}', display_errors: '{{ phpfpm_default_display_errors }}', log_errors: '{{ phpfpm_default_log_errors }}', memory_limit: '{{ phpfpm_default_memory_limit }}', slowlog_timeout: '{{ phpfpm_default_slowlog_timeout }}', rlimit_files: '{{ phpfpm_default_rlimit_files }}', php_extensions: '{{ phpfpm_default_extensions }}', define_custom_variables: '{{ phpfpm_default_define_custom_variables }}', doc_root: '{{ roundcube_web_root }}', req_term_timeout: '240s', virthost: '{{ roundcube_servername }}', nginx_servername: '{{ roundcube_servername }}' } +