diff options
Diffstat (limited to 'roles/apps/nextcloud/instance')
7 files changed, 452 insertions, 0 deletions
diff --git a/roles/apps/nextcloud/instance/defaults/main.yml b/roles/apps/nextcloud/instance/defaults/main.yml new file mode 100644 index 00000000..1a8a6d52 --- /dev/null +++ b/roles/apps/nextcloud/instance/defaults/main.yml @@ -0,0 +1,9 @@ +--- +nextcloud_app_uid: "950" +nextcloud_app_gid: "950" + +nextcloud_db_uid: "951" +nextcloud_db_gid: "951" + +nextcloud_redis_uid: "952" +nextcloud_redis_gid: "952" diff --git a/roles/apps/nextcloud/instance/tasks/custom-image.yml b/roles/apps/nextcloud/instance/tasks/custom-image.yml new file mode 100644 index 00000000..f9e130a2 --- /dev/null +++ b/roles/apps/nextcloud/instance/tasks/custom-image.yml @@ -0,0 +1,31 @@ +--- +- name: create build directory for custom image + file: + path: "{{ nextcloud_instance_basepath }}/build" + state: directory + +- name: generate Dockerfile for custom image + copy: + content: | + FROM {{ nextcloud_instances[nextcloud_instance].custom_image.from | default('nextcloud:' + nextcloud_instances[nextcloud_instance].version) }} + + RUN set -x \ + && addgroup --gid {{ nextcloud_app_gid }} nc-app \ + && adduser --uid {{ nextcloud_app_uid }} --gid {{ nextcloud_app_gid }} --system --no-create-home --home /var/www/html --disabled-login --disabled-password nc-app + {% if 'custom_image' in nextcloud_instances[nextcloud_instance] %} + + {{ nextcloud_instances[nextcloud_instance].custom_image.dockerfile }} + {% endif %} + dest: "{{ nextcloud_instance_basepath }}/build/Dockerfile" + register: nextcloud_custom_image_docker + +- name: build custom image + docker_image: + name: "nextcloud/{{ nextcloud_instance }}:{{ nextcloud_instances[nextcloud_instance].version }}" + state: present + force_source: "{{ nextcloud_custom_image_docker is changed }}" + source: build + build: + path: "{{ nextcloud_instance_basepath }}/build" + network: host + pull: yes diff --git a/roles/apps/nextcloud/instance/tasks/main.yml b/roles/apps/nextcloud/instance/tasks/main.yml new file mode 100644 index 00000000..71a3ee79 --- /dev/null +++ b/roles/apps/nextcloud/instance/tasks/main.yml @@ -0,0 +1,196 @@ +--- +- name: prepare storage volume + vars: + storage_volume: "{{ nextcloud_instances[nextcloud_instance].storage }}" + include_role: + name: "storage/{{ nextcloud_instances[nextcloud_instance].storage.type }}/volume" + +- set_fact: + nextcloud_instance_basepath: "{{ storage_volume_mountpoint }}" + +- name: create nextcloud app subdirectory + file: + path: "{{ nextcloud_instance_basepath }}/nextcloud" + owner: "{{ nextcloud_app_uid }}" + group: "{{ nextcloud_app_gid }}" + state: directory + +- name: create nextcloud database subdirectory + file: + path: "{{ nextcloud_instance_basepath }}/{{ nextcloud_instances[nextcloud_instance].database.type }}" + owner: "{{ nextcloud_db_uid }}" + group: "{{ nextcloud_db_gid }}" + state: directory + +- name: create nextcloud redis subdirectory + file: + path: "{{ nextcloud_instance_basepath }}/redis" + owner: "{{ nextcloud_redis_uid }}" + group: "{{ nextcloud_redis_gid }}" + state: directory + + +- name: create auxiliary config directory + file: + path: "{{ nextcloud_instance_basepath }}/config" + state: directory + +- name: create apache vhost config + template: + src: apache-site.conf.j2 + dest: "{{ nextcloud_instance_basepath }}/config/apache-site.conf" + +- name: configure apache to run on port 8080 only + copy: + content: | + Listen 8080 + dest: "{{ nextcloud_instance_basepath }}/config/ports.conf" + + +- name: create tls directory + file: + path: "{{ nextcloud_instance_basepath }}/tls" + owner: "{{ nextcloud_app_uid }}" + group: "{{ nextcloud_app_gid }}" + mode: 0500 + state: directory + +- name: generate/install TLS certificates for publishment + vars: + x509_certificate_name: "nextcloud-{{ nextcloud_instance }}_publish" + x509_certificate_hostnames: [] + x509_certificate_config: + ca: "{{ nextcloud_instances[nextcloud_instance].publish.zone.certificate_ca_config }}" + cert: + common_name: "nextcloud-{{ nextcloud_instance }}.{{ inventory_hostname }}" + extended_key_usage: + - serverAuth + extended_key_usage_critical: yes + create_subject_key_identifier: yes + not_after: +100w + x509_certificate_renewal: + install: + - dest: "{{ nextcloud_instance_basepath }}/tls/cert.pem" + src: + - cert + owner: "{{ nextcloud_app_uid }}" + mode: "0400" + - dest: "{{ nextcloud_instance_basepath }}/tls/key.pem" + src: + - key + owner: "{{ nextcloud_app_uid }}" + mode: "0400" + include_role: + name: "x509/{{ nextcloud_instances[nextcloud_instance].publish.zone.certificate_provider }}/cert" + + +- name: build custom image + # when: "'custom_image' in nextcloud_instances[nextcloud_instance]" + include_tasks: custom-image.yml + +- name: install pod manifest + vars: + kubernetes_standalone_pod: + name: "nextcloud-{{ nextcloud_instance }}" + spec: "{{ lookup('template', 'pod-spec-with-{{ nextcloud_instances[nextcloud_instance].database.type }}.yml.j2') }}" + mode: "0600" + config_hash_items: + - path: "{{ nextcloud_instance_basepath }}/config/apache-site.conf" + properties: + - checksum + - path: "{{ nextcloud_instance_basepath }}/config/ports.conf" + properties: + - checksum + - path: "{{ nextcloud_instance_basepath }}/build/Dockerfile" + properties: + - checksum + include_role: + name: kubernetes/standalone/pod + + +- name: install upgrade helper script + template: + src: upgrade.sh.j2 + dest: "{{ nextcloud_instance_basepath }}/upgrade.sh" + mode: 0755 + + +- name: install systemd timer unit + template: + src: cron-.timer.j2 + dest: "/etc/systemd/system/nextcloud-cron-{{ nextcloud_instance }}.timer" + +- name: start/enable cron trigger systemd timer + systemd: + daemon_reload: yes + name: "nextcloud-cron-{{ nextcloud_instance }}.timer" + state: started + enabled: yes + + +- name: configure nginx vhost for publishment + vars: + nginx_vhost__yaml: | + name: "nextcloud-{{ nextcloud_instance }}.{{ inventory_hostname }}" + template: generic + {% if 'tls' in nextcloud_instances[nextcloud_instance].publish %} + tls: + {{ nextcloud_instances[nextcloud_instance].publish.tls | to_nice_yaml(indent=2) | indent(2) }} + {% endif %} + hostnames: + {% for hostname in nextcloud_instances[nextcloud_instance].publish.hostnames %} + - {{ hostname }} + {% endfor %} + locations: + '/': + {% if nextcloud_instances[nextcloud_instance].publish.zone.publisher == inventory_hostname %} + proxy_pass: "https://127.0.0.1:{{ nextcloud_instances[nextcloud_instance].port }}" + {% else %} + proxy_pass: "https://{{ ansible_default_ipv4.address }}:{{ nextcloud_instances[nextcloud_instance].port }}" + {% endif %} + proxy_redirect: + - redirect: "https://$host:8080/" + replacement: "https://$host/" + proxy_ssl: + trusted_certificate: "/etc/ssl/apps-publish-{{ nextcloud_instances[nextcloud_instance].publish.zone.name }}/apps-publish-{{ nextcloud_instances[nextcloud_instance].publish.zone.name }}-ca-crt.pem" + verify: "on" + name: "nextcloud-{{ nextcloud_instance }}.{{ inventory_hostname }}" + protocols: "TLSv1.3" + extra_directives: |- + client_max_body_size 0; + types { + text/javascript js mjs; + } + nginx_vhost: "{{ nginx_vhost__yaml | from_yaml }}" + include_role: + name: nginx/vhost + apply: + delegate_to: "{{ nextcloud_instances[nextcloud_instance].publish.zone.publisher }}" + + +# TODO: +# do this automatically! +- name: print info for new instance + when: "'new' in nextcloud_instances[nextcloud_instance] and nextcloud_instances[nextcloud_instance].new" + pause: + seconds: 5 + prompt: | + ************* {{ nextcloud_instance }} is a new instance + ** + ** Go to https://{{ nextcloud_instances[nextcloud_instance].publish.hostnames[0] }} and finalize the + ** installation. After that run the following commands: + ** + ** $ nextcloud-occ {{ nextcloud_instance }} config:system:set default_phone_region --value='at' + ** $ nextcloud-occ {{ nextcloud_instance }} config:system:set memcache.locking --value '\OC\Memcache\Redis' + ** $ nextcloud-occ {{ nextcloud_instance }} config:system:set redis host --value '127.0.0.1' + ** $ nextcloud-occ {{ nextcloud_instance }} config:system:set redis port --type integer --value 6379 + ** $ nextcloud-occ {{ nextcloud_instance }} config:system:set redis timeout --type float --value 0.0 + ** $ nextcloud-occ {{ nextcloud_instance }} config:system:set redis password + ** $ nextcloud-occ {{ nextcloud_instance }} config:system:set maintenance_window_start --type integer --value 23 + ** + ** in case you want to use an exteranl collabora/code server: + ** + ** $ nextcloud-occ {{ nextcloud_instance }} app:disable richdocumentscode + ** $ nextcloud-occ {{ nextcloud_instance }} app:remove richdocumentscode + ** + **************************************** diff --git a/roles/apps/nextcloud/instance/templates/apache-site.conf.j2 b/roles/apps/nextcloud/instance/templates/apache-site.conf.j2 new file mode 100644 index 00000000..8df06113 --- /dev/null +++ b/roles/apps/nextcloud/instance/templates/apache-site.conf.j2 @@ -0,0 +1,20 @@ +IncludeOptional mods-available/socache_shmcb.load +IncludeOptional mods-available/ssl.load +IncludeOptional mods-available/ssl.conf + +<VirtualHost *:8080> + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + UseCanonicalName Off + UseCanonicalPhysicalPort Off + + ServerName nextcloud-{{ nextcloud_instance }}.{{ inventory_hostname }} + SSLEngine on + SSLCertificateFile "/etc/ssl/publish/cert.pem" + SSLCertificateKeyFile "/etc/ssl/publish/key.pem" + SSLProtocol TLSv1.3 + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined +</VirtualHost> diff --git a/roles/apps/nextcloud/instance/templates/cron-.timer.j2 b/roles/apps/nextcloud/instance/templates/cron-.timer.j2 new file mode 100644 index 00000000..eead0fd0 --- /dev/null +++ b/roles/apps/nextcloud/instance/templates/cron-.timer.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=Nextcloud cron.php job timer for {{ nextcloud_instance }} + +[Timer] +OnCalendar=*:{{ 5 | random(seed=nextcloud_instance) }}/5 +Unit=nextcloud-cron@{{ nextcloud_instance }}.service + +[Install] +WantedBy=timers.target diff --git a/roles/apps/nextcloud/instance/templates/pod-spec-with-mariadb.yml.j2 b/roles/apps/nextcloud/instance/templates/pod-spec-with-mariadb.yml.j2 new file mode 100644 index 00000000..c1a4f2ea --- /dev/null +++ b/roles/apps/nextcloud/instance/templates/pod-spec-with-mariadb.yml.j2 @@ -0,0 +1,110 @@ +securityContext: + allowPrivilegeEscalation: false +containers: +- name: nextcloud +{# image: "nextcloud{% if 'custom_image' in nextcloud_instances[nextcloud_instance] %}/{{ nextcloud_instance }}{% endif %}:{{ nextcloud_instances[nextcloud_instance].version }}" #} + image: "nextcloud/{{ nextcloud_instance }}:{{ nextcloud_instances[nextcloud_instance].version }}" + securityContext: + runAsUser: {{ nextcloud_app_uid }} + runAsGroup: {{ nextcloud_app_gid }} + resources: + limits: + memory: "4Gi" +{% if 'new' in nextcloud_instances[nextcloud_instance] and nextcloud_instances[nextcloud_instance].new %} + env: + - name: NEXTCLOUD_TRUSTED_DOMAINS + value: "{{ nextcloud_instances[nextcloud_instance].publish.hostnames | join(' ') }}" + - name: MYSQL_HOST + value: 127.0.0.1 + - name: MYSQL_DATABASE + value: nextcloud + - name: MYSQL_USER + value: nextcloud + - name: MYSQL_PASSWORD + value: "{{ nextcloud_instances[nextcloud_instance].database.password }}" +{% endif %} + volumeMounts: + - name: nextcloud + mountPath: /var/www/html + - name: config + mountPath: /etc/apache2/sites-available/000-default.conf + subPath: apache-site.conf + readOnly: true + - name: config + mountPath: /etc/apache2/ports.conf + subPath: ports.conf + readOnly: true + - name: tls + mountPath: /etc/ssl/publish + readOnly: true + ports: + - containerPort: 8080 + hostPort: {{ nextcloud_instances[nextcloud_instance].port }} + hostIP: 127.0.0.1 +- name: redis + image: "redis:{{ nextcloud_instances[nextcloud_instance].redis.version }}" + args: + - --bind 127.0.0.1 + securityContext: + runAsUser: {{ nextcloud_redis_uid }} + runAsGroup: {{ nextcloud_redis_gid }} + resources: + limits: + memory: "512Mi" + volumeMounts: + - name: redis + mountPath: /data +- name: database + image: "mariadb:{{ nextcloud_instances[nextcloud_instance].database.version }}" + args: + - --transaction-isolation=READ-COMMITTED + - --log-bin=binlog + - --binlog-format=ROW +{% for arg in (nextcloud_instances[nextcloud_instance].database.extra_args | default([])) %} + - {{ arg }} +{% endfor %} + securityContext: + runAsUser: {{ nextcloud_db_uid }} + runAsGroup: {{ nextcloud_db_gid }} + resources: + limits: + memory: "2Gi" + env: + - name: MARIADB_AUTO_UPGRADE + value: "true" + - name: MARIADB_DISABLE_UPGRADE_BACKUP + value: "true" +{% if 'new' in nextcloud_instances[nextcloud_instance] and nextcloud_instances[nextcloud_instance].new %} + - name: MARIADB_RANDOM_ROOT_PASSWORD + value: "true" + - name: MARIADB_DATABASE + value: nextcloud + - name: MARIADB_USER + value: nextcloud + - name: MARIADB_PASSWORD + value: "{{ nextcloud_instances[nextcloud_instance].database.password }}" +{% endif %} + volumeMounts: + - name: database + mountPath: /var/lib/mysql +volumes: +- name: config + hostPath: + path: "{{ nextcloud_instance_basepath }}/config/" + type: Directory +- name: tls + hostPath: + path: "{{ nextcloud_instance_basepath }}/tls/" + type: Directory +- name: nextcloud + hostPath: + path: "{{ nextcloud_instance_basepath }}/nextcloud" + type: Directory +- name: redis + hostPath: + path: "{{ nextcloud_instance_basepath }}/redis" + type: Directory +- name: database + hostPath: + path: "{{ nextcloud_instance_basepath }}/{{ nextcloud_instances[nextcloud_instance].database.type }}" + type: Directory diff --git a/roles/apps/nextcloud/instance/templates/upgrade.sh.j2 b/roles/apps/nextcloud/instance/templates/upgrade.sh.j2 new file mode 100644 index 00000000..62f6641e --- /dev/null +++ b/roles/apps/nextcloud/instance/templates/upgrade.sh.j2 @@ -0,0 +1,77 @@ +#!/bin/bash + +set -e + +if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then + echo "Usage: $0 (preapre|finalize) <old-version> <new-version>" + exit 1 +fi + +COMMAND="$1" +OLD_VERSION="$2" +NEW_VERSION="$3" +POD_NAME="{{ nextcloud_instance }}-$(hostname)" + +maintenance_mode() { + POD_ID=$(crictl pods --name "$POD_NAME" --state ready -q) + CONTAINER_ID=$(crictl ps --pod "$POD_ID" --name nextcloud -q) + crictl exec "$CONTAINER_ID" php -f /var/www/html/occ maintenance:mode "$1" +} + +wait_for_cronjobs() { + POD_ID=$(crictl pods --name "$POD_NAME" --state ready -q) + CONTAINER_ID=$(crictl ps --pod "$POD_ID" --name nextcloud -q) + crictl exec "$CONTAINER_ID" bash -c 'echo -n "waiting for running cron script "; while [ -n "$(pgrep -a php | grep cron.php)" ]; do echo -n "."; sleep 1; done; echo ""' +} + +wait_for_upgrade_complete() { + NEW_VERSION="$1" + + set +e + echo -n "waiting for new version to be ready " + while true; do + POD_ID=$(crictl pods --name "$POD_NAME" --state ready -q) + if [ -z $POD_ID ]; then continue; fi + CONTAINER_ID=$(crictl ps --pod "$POD_ID" --name nextcloud -q) + if [ -z $CONTAINER_ID ]; then continue; fi + STATUS_OUTPUT=$(crictl exec "$CONTAINER_ID" php -f /var/www/html/occ status -n --no-warnings --output plain) + if [ $? -eq 0 ]; then + RUNNING_VERSION=$(echo "$STATUS_OUTPUT" | awk -F : '/versionstring/ { print($2) }' | tr -d ' ') + if [ "$RUNNING_VERSION" = "$NEW_VERSION" ]; then + break + fi + echo -n "." + fi + sleep 1 + done + echo "" + set -e + crictl exec "$CONTAINER_ID" bash -c 'echo -n "waiting for apache to start "; while [ -z "$(pgrep apache2)" ]; do echo -n "."; sleep 1; done; echo ""' +} + +storage_snapshot() { + OLD_VERSION="$1" + NEW_VERSION="$2" + +{% if nextcloud_instances[nextcloud_instance].storage.type == 'zfs' %} + ZFS_VOLUME=$(findmnt -no source -T "{{ nextcloud_instance_basepath }}") + echo "creating snapshot for zfs volume: $ZFS_VOLUME" + zfs snapshot "$ZFS_VOLUME@upgrade_$OLD_VERSION-to-$NEW_VERSION""_$(date '+%Y-%m-%m_%H:%M:%S')" +{% endif %} +} + +case "$COMMAND" in + prepare) + maintenance_mode --on + wait_for_cronjobs + storage_snapshot "$OLD_VERSION" "$NEW_VERSION" + ;; + finalize) + wait_for_upgrade_complete "$NEW_VERSION" + maintenance_mode --off + ;; + *) + echo "unknown command: $COMMAND, must be prepare or finalize" + exit 1 + ;; +esac |