diff --git a/CHANGELOG.md b/CHANGELOG.md index 33998f64..b83ea994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ The **patch** part changes incrementally at each release. ### Added +* nextcloud: New role to setup a nextcloud instance + ### Changed ### Fixed diff --git a/webapps/nextcloud/defaults/main.yml b/webapps/nextcloud/defaults/main.yml new file mode 100644 index 00000000..0048ca69 --- /dev/null +++ b/webapps/nextcloud/defaults/main.yml @@ -0,0 +1,19 @@ +--- +nextcloud_webserver: 'nginx' +nextcloud_version: "19.0.0" +nextcloud_archive_name: "nextcloud-{{ nextcloud_version }}.tar.bz2" +nextcloud_releases_baseurl: "https://download.nextcloud.com/server/releases/" + +nextcloud_instance_name: "nextcloud" +nextcloud_user: "{{ nextcloud_instance_name }}" +nextcloud_domains: [] + +nextcloud_home: "/home/{{ nextcloud_user }}" +nextcloud_webroot: "{{ nextcloud_home }}/nextcloud" +nextcloud_data: "{{ nextcloud_webroot }}/data" + +nextcloud_db_user: "{{ nextcloud_user }}" +nextcloud_db_name: "{{ nextcloud_instance_name }}" + +nextcloud_admin_login: "admin" +nextcloud_admin_password: "" diff --git a/webapps/nextcloud/handlers/main.yml b/webapps/nextcloud/handlers/main.yml new file mode 100644 index 00000000..2db4770d --- /dev/null +++ b/webapps/nextcloud/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: reload php-fpm + service: + name: php7.3-fpm + state: reloaded + +- name: reload nginx + service: + name: nginx + state: reloaded diff --git a/webapps/nextcloud/meta/main.yml b/webapps/nextcloud/meta/main.yml new file mode 100644 index 00000000..d5852e32 --- /dev/null +++ b/webapps/nextcloud/meta/main.yml @@ -0,0 +1,4 @@ +--- +# dependencies: + # - { role: nginx, when: nextcloud_webserver == 'nginx' } + # - { role: php, php_fpm_enable: True } diff --git a/webapps/nextcloud/tasks/archive.yml b/webapps/nextcloud/tasks/archive.yml new file mode 100644 index 00000000..d59bd582 --- /dev/null +++ b/webapps/nextcloud/tasks/archive.yml @@ -0,0 +1,37 @@ +--- + +- name: Retrieve Nextcloud archive + get_url: + url: "{{ nextcloud_releases_baseurl }}{{ nextcloud_archive_name }}" + dest: "{{ nextcloud_home }}/{{ nextcloud_archive_name }}" + force: no + tags: + - nextcloud + +- name: Retrieve Nextcloud sha256 checksum + get_url: + url: "{{ nextcloud_releases_baseurl }}{{ nextcloud_archive_name }}.sha256" + dest: "{{ nextcloud_home }}/{{ nextcloud_archive_name }}.sha256" + force: no + tags: + - nextcloud + +- name: Verify Nextcloud sha256 checksum + command: "sha256sum -c {{ nextcloud_archive_name }}.sha256" + changed_when: "False" + args: + chdir: "{{ nextcloud_home }}" + tags: + - nextcloud + +- name: Extract Nextcloud archive + unarchive: + src: "{{ nextcloud_home }}/{{ nextcloud_archive_name }}" + dest: "{{ nextcloud_home }}" + creates: "{{ nextcloud_home }}/nextcloud" + remote_src: True + mode: "0750" + owner: "{{ nextcloud_user }}" + group: "{{ nextcloud_user }}" + tags: + - nextcloud diff --git a/webapps/nextcloud/tasks/config.yml b/webapps/nextcloud/tasks/config.yml new file mode 100644 index 00000000..a4e3a3e7 --- /dev/null +++ b/webapps/nextcloud/tasks/config.yml @@ -0,0 +1,81 @@ +--- + +- block: + - name: Generate admin password + command: 'apg -n 1 -m 16 -M lcN' + register: nextcloud_admin_password_apg + check_mode: no + changed_when: False + + - debug: + var: nextcloud_admin_password_apg + + - set_fact: + nextcloud_admin_password: "{{ nextcloud_admin_password_apg.stdout }}" + + tags: + - nextcloud + when: nextcloud_admin_password == "" + +- name: Get Nextcloud Status + shell: "php ./occ status --output json | grep -v 'Nextcloud is not installed'" + args: + chdir: "{{ nextcloud_webroot }}" + become_user: "{{ nextcloud_user }}" + register: nc_status + check_mode: no + tags: + - nextcloud + +- name: Install Nextcloud + command: "php ./occ maintenance:install --database mysql --database-name {{ nextcloud_db_name | mandatory }} --database-user {{ nextcloud_db_user | mandatory }} --database-pass {{ nextcloud_db_pass | mandatory }} --admin-user {{ nextcloud_admin_login | mandatory }} --admin-pass {{ nextcloud_admin_password | mandatory }} --data-dir {{ nextcloud_data | mandatory }}" + args: + chdir: "{{ nextcloud_webroot }}" + creates: "{{ nextcloud_home }}/config/config.php" + become_user: "{{ nextcloud_user }}" + when: (nc_status.stdout | from_json).installed == false + tags: + - nextcloud + +- name: Configure Nextcloud Mysql password + replace: + dest: "{{ nextcloud_home }}/nextcloud/config/config.php" + regexp: "'dbpassword' => '([^']*)'," + replace: "'dbpassword' => '{{ nextcloud_db_pass }}'," + tags: + - nextcloud + +- name: Configure Nextcloud cron + cron: + name: 'Nextcloud' + minute: "*/5" + job: "php -f {{ nextcloud_webroot }}/cron.php" + user: "{{ nextcloud_user }}" + tags: + - nextcloud + +- name: Erase previously trusted domains config + command: "php ./occ config:system:set trusted_domains" + args: + chdir: "{{ nextcloud_webroot }}" + become_user: "{{ nextcloud_user }}" + tags: + - nextcloud + +- name: Configure trusted domains + command: "php ./occ config:system:set trusted_domains {{ item.0 }} --value {{ item.1 }}" + args: + chdir: "{{ nextcloud_webroot }}" + with_indexed_items: + - "{{ nextcloud_domains }}" + become_user: "{{ nextcloud_user }}" + tags: + - nextcloud + +#- name: Configure memcache local to APCu +# command: "php ./occ config:system:set memcache.local --value '\\OC\\Memcache\\APCu'" +# args: +# chdir: "{{ nextcloud_webroot }}" +# become_user: "{{ nextcloud_user }}" +# tags: +# - nextcloud diff --git a/webapps/nextcloud/tasks/main.yml b/webapps/nextcloud/tasks/main.yml new file mode 100644 index 00000000..2c525114 --- /dev/null +++ b/webapps/nextcloud/tasks/main.yml @@ -0,0 +1,31 @@ +--- +- name: Install dependencies + apt: + state: present + name: + - bzip2 + - php-gd + - php-json + - php-xml + - php-mbstring + - php-zip + - php-curl + - php-bz2 + - php-intl + - php-gmp + - php-apcu + - php-redis + - php-bcmath + - python-mysqldb + tags: + - nextcloud + +- include: user.yml + +- include: archive.yml + +- include: vhost.yml + +- include: mysql.yml + +- include: config.yml diff --git a/webapps/nextcloud/tasks/mysql.yml b/webapps/nextcloud/tasks/mysql.yml new file mode 100644 index 00000000..f2fcee32 --- /dev/null +++ b/webapps/nextcloud/tasks/mysql.yml @@ -0,0 +1,62 @@ +--- +- name: Get actual Mysql password + shell: "grep password {{ nextcloud_home }}/.my.cnf | awk '{ print $3 }'" + register: nextcloud_db_pass_grep + check_mode: no + changed_when: False + failed_when: False + tags: + - nextcloud + +- name: Generate Mysql password + command: 'apg -n 1 -m 16 -M lcN' + register: nextcloud_db_pass_apg + check_mode: no + changed_when: False + tags: + - nextcloud + +- name: Set Mysql password + set_fact: + nextcloud_db_pass: "{{ nextcloud_db_pass_grep.stdout | default(nextcloud_db_pass_apg.stdout, True) }}" + tags: + - nextcloud + +- debug: + var: nextcloud_db_pass + verbosity: 1 + +- name: Create Mysql database + mysql_db: + name: "{{ nextcloud_db_name }}" + config_file: "/root/.my.cnf" + state: present + tags: + - nextcloud + +- name: Create Mysql user + mysql_user: + name: "{{ nextcloud_db_user }}" + password: '{{ nextcloud_db_pass }}' + priv: "{{ nextcloud_db_name }}.*:ALL" + config_file: "/root/.my.cnf" + update_password: always + state: present + tags: + - nextcloud + +- name: Store credentials in my.cnf + ini_file: + dest: "{{ nextcloud_home }}/.my.cnf" + owner: "{{ nextcloud_user }}" + group: "{{ nextcloud_user }}" + mode: "0600" + section: client + option: "{{ item.option }}" + value: "{{ item.value }}" + with_items: + - { option: "user", value: "{{ nextcloud_db_user }}" } + - { option: "database", value: "{{ nextcloud_db_name }}" } + - { option: "password", value: "{{ nextcloud_db_pass }}" } + tags: + - nextcloud diff --git a/webapps/nextcloud/tasks/user.yml b/webapps/nextcloud/tasks/user.yml new file mode 100644 index 00000000..07d5a31a --- /dev/null +++ b/webapps/nextcloud/tasks/user.yml @@ -0,0 +1,38 @@ +--- +- name: Create Nextcloud group + group: + name: "{{ nextcloud_instance_name | mandatory }}" + state: present + tags: + - nextcloud + +- name: Create Nextcloud user + user: + name: "{{ nextcloud_user | mandatory }}" + group: "{{ nextcloud_user }}" + home: "{{ nextcloud_home | mandatory }}" + shell: '/bin/bash' + createhome: True + state: present + tags: + - nextcloud + +- name: Add the user 'www-data' to Nextcloud group + user: + name: www-data + groups: "{{ nextcloud_user | mandatory }}" + append: yes + +- name: Create top-level directories + file: + dest: "{{ item }}" + state: directory + mode: "0770" + owner: "{{ nextcloud_user }}" + group: "{{ nextcloud_user }}" + with_items: + - "{{ nextcloud_home }}/log" + - "{{ nextcloud_home }}/tmp" + - "{{ nextcloud_home }}/data" + tags: + - nextcloud diff --git a/webapps/nextcloud/tasks/vhost.yml b/webapps/nextcloud/tasks/vhost.yml new file mode 100644 index 00000000..1f1592cc --- /dev/null +++ b/webapps/nextcloud/tasks/vhost.yml @@ -0,0 +1,34 @@ +--- +- block: + - name: Copy Nginx vhost + template: + src: nginx.conf.j2 + dest: "/etc/nginx/sites-available/{{ nextcloud_instance_name }}.conf" + mode: "0640" + notify: reload nginx + tags: + - nextcloud + + - name: Enable Nginx vhost + file: + src: "/etc/nginx/sites-available/{{ nextcloud_instance_name }}.conf" + dest: "/etc/nginx/sites-enabled/{{ nextcloud_instance_name }}.conf" + state: link + notify: reload nginx + tags: + - nextcloud + + - name: Generate ssl config + shell: + cmd: "/usr/local/sbin/vhost-domains {{ nextcloud_instance_name }} | /usr/local/sbin/make-csr {{ nextcloud_instance_name }}" + creates: "/etc/nginx/ssl/{{ nextcloud_instance_name }}.conf" + + - name: Copy PHP-FPM pool + template: + src: php-fpm.conf.j2 + dest: "/etc/php/7.3/fpm/pool.d/{{ nextcloud_instance_name }}.conf" + mode: "0640" + notify: reload php-fpm + tags: + - nextcloud + when: nextcloud_webserver == 'nginx' diff --git a/webapps/nextcloud/templates/nginx.conf.j2 b/webapps/nextcloud/templates/nginx.conf.j2 new file mode 100644 index 00000000..ffb72f01 --- /dev/null +++ b/webapps/nextcloud/templates/nginx.conf.j2 @@ -0,0 +1,134 @@ +upstream php-handler-{{ nextcloud_instance_name }} { + server unix:/var/run/php/php-fpm-{{ nextcloud_instance_name }}.sock; +} + +server { + listen 80; + listen [::]:80; + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name {{ nextcloud_domains | join(' ') }}; + + access_log {{ nextcloud_home }}/log/access.log; + error_log {{ nextcloud_home }}/log/error.log; + + include /etc/nginx/snippets/letsencrypt.conf; + include /etc/nginx/ssl/{{ nextcloud_instance_name }}.conf; + + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Remove X-Powered-By, which is an information leak + fastcgi_hide_header X-Powered-By; + + root {{ nextcloud_webroot }}; + + location = /robots.txt { + allow all; + 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; + + # The following rule is only needed for the Social app. + # Uncomment it if you're planning to use this app. + rewrite ^/.well-known/webfinger /public.php?service=webfinger last; + + location = /.well-known/carddav { + return 301 $scheme://$host:$server_port/remote.php/dav; + } + location = /.well-known/caldav { + return 301 $scheme://$host:$server_port/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; + + + location / { + rewrite ^ /index.php; + } + + 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\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy)\.php(?:$|\/) { + fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; + set $path_info $fastcgi_path_info; + try_files $fastcgi_script_name =404; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $path_info; + fastcgi_param HTTPS on; + # Avoid sending the security headers twice + fastcgi_param modHeadersAvailable true; + # Enable pretty urls + fastcgi_param front_controller_active true; + fastcgi_pass php-handler-{{ nextcloud_instance_name }}; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + } + + location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) { + try_files $uri/ =404; + index index.php; + } + + # Adding the cache control header for js, css and map files + # Make sure it is BELOW the PHP block + location ~ \.(?:css|js|woff2?|svg|gif|map)$ { + try_files $uri /index.php$request_uri; + 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;" always; + # + # 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 Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Optional: Don't log access to assets + access_log off; + } + + location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ { + try_files $uri /index.php$request_uri; + # Optional: Don't log access to other assets + access_log off; + } +} diff --git a/webapps/nextcloud/templates/php-fpm.conf.j2 b/webapps/nextcloud/templates/php-fpm.conf.j2 new file mode 100644 index 00000000..1b4c7861 --- /dev/null +++ b/webapps/nextcloud/templates/php-fpm.conf.j2 @@ -0,0 +1,17 @@ +[{{ nextcloud_instance_name }}] +user = {{ nextcloud_user }} +group = {{ nextcloud_user }} +listen = /run/php/php-fpm-{{ nextcloud_instance_name }}.sock +listen.owner = {{ nextcloud_user }} +listen.group = {{ nextcloud_user }} + +pm = ondemand +pm.max_children = 50 +pm.process_idle_timeout = 120s +pm.status_path = /fpm_status + +env[HOSTNAME] = $HOSTNAME +env[PATH] = /usr/local/bin:/usr/bin:/bin +env[TMP] = {{ nextcloud_home }}/tmp +env[TMPDIR] = {{ nextcloud_home }}/tmp +env[TEMP] = {{ nextcloud_home }}/tmp