commit 696846031c7f881f384b1e46585142f4ce474423 Author: thiuda Date: Thu Nov 11 13:25:47 2021 +0100 init diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..bcb5ef3 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,28 @@ +--- + +# container +nc_app_host: nextcloud +nc_nginx_host: nextcloud-nginx + +# commands +nextcloud_occ_cmd: "docker-compose run --user www-data --rm {{ nextcloud_container_hostname }} php occ" +nextcloud_app_install: "{{ nextcloud_occ_cmd }} app:install " +nextcloud_app_list: "{{ nextcloud_occ_cmd }} app:list" +nextcloud_config_set: "{{ nextcloud_occ_cmd }} config:system:set " +nextcloud_config_get: "{{ nextcloud_occ_cmd }} config:system:get " +nextcloud_config_import: "{{ nextcloud_occ_cmd }} config:import " + +nextcloud_settings: + - {param: "settings default_phone_region", value: "DE", options: "--no-interaction"} + - {param: "trusted_domains 1", value: "localhost", options: ""} + - {param: "trusted_domains 2", value: "{{ nextcloud_prefix }}.{{ domain }}", options: ""} + - {param: "overwriteprotocol", value: "https", options: ""} + - {param: "overwritehost", value: "{{ nextcloud_prefix }}.{{ domain }}", options: ""} + - {param: "overwrite.cli.url", value: "https:\/\/{{ nextcloud_prefix }}.{{ domain }}", options: ""} + - {param: "trusted_proxies", value: "{{ nc_app_host }}", options: ""} + +# apps +nextcloud_apps: + - onlyoffice + - end_to_end_encryption + - deck diff --git a/files/web/Dockerfile b/files/web/Dockerfile new file mode 100755 index 0000000..c5429e8 --- /dev/null +++ b/files/web/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:stable-alpine + +COPY nginx.conf /etc/nginx/nginx.conf diff --git a/files/web/nginx.conf b/files/web/nginx.conf new file mode 100755 index 0000000..b8149ef --- /dev/null +++ b/files/web/nginx.conf @@ -0,0 +1,170 @@ +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '[$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + upstream php-handler { + server nextcloud:9000; + } + + server { + listen 80; + + # 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;" 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; + + # Remove X-Powered-By, which is an information leak + fastcgi_hide_header X-Powered-By; + + # Path to the root of your installation + root /var/www/html; + + 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; + rewrite ^/.well-known/nodeinfo /public.php?service=nodeinfo 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 10G; + 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; + } + + 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\/.+)\.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; + 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/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..35a584f --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,25 @@ +--- +- name: Add missing indices + become: yes + command: + chdir: "{{ compose_dir }}/nextcloud" + cmd: "{{ nextcloud_occ_cmd }} db:add-missing-indices" + listen: Checking database + register: output + +- name: Add missing primary keys + become: yes + command: + chdir: "{{ compose_dir }}/nextcloud" + cmd: "{{ nextcloud_occ_cmd }} db:add-missing-primary-keys" + listen: Checking database + register: output + ignore_errors: yes # TODO this is a production hotfix + +- name: Convert filecache bigint + become: yes + command: + chdir: "{{ compose_dir }}/nextcloud" + cmd: "{{ nextcloud_occ_cmd }} db:convert-filecache-bigint --no-interaction" + listen: Checking database + register: output diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..91eeac9 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,12 @@ +galaxy_info: + author: thiuda + description: Role to deploy Nextcloud with OnlyOffice behind Reverse Proxy + company: progressivwerk + license: MIT + min_ansible_version: 2.1 + galaxy_tags: [] + platforms: + - name: Debian + versions: + - 10 +dependencies: [] \ No newline at end of file diff --git a/tasks/config_php.yml b/tasks/config_php.yml new file mode 100644 index 0000000..f6c8dae --- /dev/null +++ b/tasks/config_php.yml @@ -0,0 +1,18 @@ +--- + +- name: "Get configuration" + become: yes + command: + chdir: "{{ compose_dir }}/nextcloud" + cmd: "{{ nextcloud_config_get + _param.param }}" + register: _current_param_value + ignore_errors: true + changed_when: false + +# TODO: generated config.php +- name: "Set configuration" + become: yes + command: + chdir: "{{ compose_dir }}/nextcloud" + cmd: "{{ nextcloud_config_set + _param.param + ' --value=' + _param.value + ' ' + _param.options }}" + when: '_param.value | string not in _current_param_value.stdout' diff --git a/tasks/install_apps.yml b/tasks/install_apps.yml new file mode 100644 index 0000000..a902296 --- /dev/null +++ b/tasks/install_apps.yml @@ -0,0 +1,17 @@ +--- + +- name: "Get list of current apps" + become: yes + command: + chdir: "{{ compose_dir }}/nextcloud" + cmd: "{{ nextcloud_app_list }}" + register: _apps_installed + changed_when: false + +- name: Install apps + become: yes + command: + chdir: "{{ compose_dir }}/nextcloud" + cmd: "{{ nextcloud_app_install + item }}" + with_items: "{{ nextcloud_apps }}" + when: 'item | string not in _apps_installed.stdout' \ No newline at end of file diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..41077ee --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,94 @@ +--- +- name: Create directory if it does not exists + become: yes + file: + path: "{{ compose_dir }}/nextcloud" + state: directory + mode: 0755 + +- name: Place docker-compose file + become: yes + template: + src: templates/docker-compose.yml.j2 + dest: "{{ compose_dir }}/nextcloud/docker-compose.yml" + mode: 0700 + +- name: Place nextcloud env file + become: yes + template: + src: templates/.nextcloud.env.j2 + dest: "{{ compose_dir }}/nextcloud/nextcloud.env" + mode: 0600 + +- name: Place postgres env file + become: yes + template: + src: templates/.postgres.env.j2 + dest: "{{ compose_dir }}/nextcloud/postgres.env" + mode: 0600 + +- name: Place redis env file + become: yes + template: + src: templates/.redis.env.j2 + dest: "{{ compose_dir }}/nextcloud/redis.env" + mode: 0600 + +- name: Create nginx directory for nextcloud + become: yes + file: + path: "{{ compose_dir }}/nextcloud/web" + state: directory + mode: 0755 + +- name: Place docker nginx Dockerfile + become: yes + copy: + src: files/web/Dockerfile + dest: "{{ compose_dir }}/nextcloud/web/Dockerfile" + mode: 0700 + +- name: Copy docker nginx config + become: yes + copy: + src: files/web/nginx.conf + dest: "{{ compose_dir }}/nextcloud/web/nginx.conf" + mode: 0600 + +- name: Update and start services + become: yes + docker_compose: + project_src: "{{ compose_dir }}/nextcloud" + pull: yes + state: present + remove_orphans: yes + notify: + - Add missing indices + - Add missing primary keys + - Convert filecache bigint + register: output + +- name: Check all containers are running + ansible.builtin.assert: + that: + - "output.ansible_facts.nextcloud.nextcloud.state.running": true + - "output.ansible_facts.nextcloud-nginx.nextcloud-nginx.state.running": true + - "output.ansible_facts.nextcloud-cron.nextcloud-cron.state.running": true + - "output.ansible_facts.nextcloud-redis.nextcloud-redis.state.running": true + - "output.ansible_facts.nextcloud-postgres.nextcloud-postgres.state.running": true + +- import_tasks: install_apps.yml + +- include_tasks: config_php.yml + vars: + _param: "{{ item }}" + with_items: "{{ nextcloud_settings }}" + + +- name: Place reverse proxy conf + become: yes + template: + src: templates/reverse_proxy.conf.j2 + dest: "/etc/nginx/conf.d/nextcloud.conf" + mode: 0600 + notify: Check and Reload nginx diff --git a/templates/.nextcloud.env.j2 b/templates/.nextcloud.env.j2 new file mode 100755 index 0000000..713baf9 --- /dev/null +++ b/templates/.nextcloud.env.j2 @@ -0,0 +1,5 @@ +NEXTCLOUD_TRUSTED_DOMAINS={{ nextcloud_prefix }}.{{ domain }} +NEXTCLOUD_ADMIN_USER=admin +NEXTCLOUD_ADMIN_PASSWORD={{ nextcloud_admin_pw }} +NEXTCLOUD_DATA_DIR=/var/www/html/data +REDIS_HOST_PASSWORD={{ nextcloud_redis_host_pw }} diff --git a/templates/.postgres.env.j2 b/templates/.postgres.env.j2 new file mode 100755 index 0000000..bf8f167 --- /dev/null +++ b/templates/.postgres.env.j2 @@ -0,0 +1,3 @@ +POSTGRES_DB=nextcloud +POSTGRES_USER=nextcloud +POSTGRES_PASSWORD={{ nextcloud_postgres_pw }} diff --git a/templates/.redis.env.j2 b/templates/.redis.env.j2 new file mode 100755 index 0000000..43bef6b --- /dev/null +++ b/templates/.redis.env.j2 @@ -0,0 +1 @@ +REDIS_PASSWORD={{ nextcloud_redis_host_pw }} diff --git a/templates/docker-compose.yml.j2 b/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..8119eaa --- /dev/null +++ b/templates/docker-compose.yml.j2 @@ -0,0 +1,90 @@ +# {{ ansible_managed }} +# commit: {{ lookup('pipe', 'git rev-parse --short HEAD') }} + +version: "3.4" + +services: + nextcloud: + image: nextcloud:{{ nextcloud_version }}-fpm-alpine + container_name: {{ nc_app_host }} + hostname: {{ nc_app_host }} + restart: unless-stopped + depends_on: + - nextcloud-postgres + - nextcloud-redis + volumes: + - data:/var/www/html + networks: + - internal + - external # TODO: remove this when it's fixed + env_file: + - postgres.env + - nextcloud.env + environment: + POSTGRES_HOST: "nextcloud-postgres:5432" + REDIS_HOST: "nextcloud-redis" + + nextcloud-nginx: + build: ./web + container_name: {{ nc_nginx_host }} + hostname: {{ nc_nginx_host }} + restart: always + ports: + - "127.0.0.1:{{ nextcloud_port }}:80" + volumes: + - data:/var/www/html:ro + networks: + - external + - internal + depends_on: + - nextcloud + + nextcloud-cron: + image: nextcloud:{{ nextcloud_version }}-fpm-alpine + container_name: nextcloud-cron + hostname: nextcloud-cron + restart: unless-stopped + volumes: + - data:/var/www/html + networks: + - internal + - external + entrypoint: /cron.sh + depends_on: + - nextcloud-postgres + - nextcloud-redis + + nextcloud-redis: + image: redis:{{ nextcloud_redis_version }}-alpine + container_name: nextcloud-redis + hostname: nextcloud-redis + restart: unless-stopped + volumes: + - redis:/bitnami/redis/data + networks: + - internal + env_file: + - redis.env + command: redis-server --requirepass {{ nextcloud_redis_host_pw }} # unfortunately, this is necessary + + nextcloud-postgres: + image: postgres:{{nextcloud_postgres_version}}-alpine + container_name: nextcloud-postgres + hostname: nextcloud-postgres + restart: unless-stopped + volumes: + - postgres:/var/lib/postgresql/data + networks: + - internal + env_file: + - postgres.env + +networks: + external: + internal: + internal: true + +volumes: + data: + postgres: + redis: diff --git a/templates/reverse_proxy.conf.j2 b/templates/reverse_proxy.conf.j2 new file mode 100644 index 0000000..a5b1419 --- /dev/null +++ b/templates/reverse_proxy.conf.j2 @@ -0,0 +1,81 @@ +# {{ ansible_managed }} +# commit: {{ lookup('pipe', 'git rev-parse --short HEAD') }} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name {{ nextcloud_prefix }}.{{ domain }}; + + ## + # SSL + ## + ssl_session_cache builtin:1000 shared:SSL:10m; + ssl_protocols TLSv1.2 TLSv1.1 TLSv1; + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; + ssl_prefer_server_ciphers on; + ssl_dhparam {{ ssl_dir }}/dhparams.pem; + ssl_ecdh_curve secp384r1; + ssl_certificate {{ certs_dir }}/{{ domain | get_tld }}/cert.pem; + ssl_certificate_key {{ certs_dir }}/{{ domain | get_tld }}/key.pem; + + ## + # OCSP Stapling + ## + ssl_stapling on; + ssl_stapling_verify on; + resolver {{ dns_resolvers }} valid=300s; + ssl_trusted_certificate {{ certs_dir }}/{{ domain | get_tld }}/cert.pem; + + ## + # HSTS + ## + add_header Strict-Transport-Security "max-age=15552000 includeSubDomains" always; + + location / { + proxy_set_header Host $http_host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://localhost:{{ nextcloud_port }}; + + client_max_body_size 500M; + } + + error_page 404 /404.html; + location = /40x.html { + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + } + + location /.well-known/carddav { + return 301 $scheme://$host/remote.php/dav; + } + + location /.well-known/caldav { + return 301 $scheme://$host/remote.php/dav; + } + + rewrite ^/.well-known/webfinger /index.php$uri redirect; + rewrite ^/.well-known/nodeinfo /index.php$uri redirect; +} + +server { + if ($host = {{ nextcloud_prefix }}.{{ domain }}) { + return 301 https://$host$request_uri; + } + + listen 80; + listen [::]:80; + server_name {{ nextcloud_prefix }}.{{ domain }}; + + location / { + return 301 https://$host$request_uri; + } +}