From dcc01ecd2001e9f6382a6666e1ac0d65e4154389 Mon Sep 17 00:00:00 2001 From: Danyal Berchtold Date: Wed, 20 May 2026 10:09:16 +0200 Subject: [PATCH 01/14] refactor(roles/nextcloud): parameterize OS-specific names for multi-OS support Move the hardcoded RHEL-specific web server user/group (apache), the PHP-FPM service name (php-fpm) and the base package list out of the role logic into vars/RedHat.yml, loaded via shared/platform-variables.yml. Expose nextcloud__webserver_user, nextcloud__webserver_group and nextcloud__php_fpm_service_name as overridable variables and use them throughout the tasks, the deployed systemd services and the notify_push unit, as well as the nextcloud-update script. Guard the SELinux restorecon tasks with the selinux status fact and switch the update script's SELinux blocks to ansible_facts["os_family"]. Only vars/RedHat.yml ships, so the role still runs on RHEL only; adding a tested vars/Debian.yml is all that is needed to extend support. --- CHANGELOG.md | 1 + roles/nextcloud/README.md | 18 +++++ roles/nextcloud/defaults/main.yml | 8 +- roles/nextcloud/tasks/create-user.yml | 4 +- roles/nextcloud/tasks/main.yml | 73 +++++++++++-------- .../system/nextcloud-app-update.service.j2 | 4 +- .../systemd/system/nextcloud-jobs.service.j2 | 4 +- .../nextcloud-ldap-show-remnants.service.j2 | 4 +- .../system/nextcloud-scan-files.service.j2 | 4 +- .../usr/local/bin/nextcloud-update.j2 | 14 ++-- roles/nextcloud/vars/RedHat.yml | 8 ++ 11 files changed, 93 insertions(+), 49 deletions(-) create mode 100644 roles/nextcloud/vars/RedHat.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index b961ce274..8bb2c2103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* **role:nextcloud**: Lay the groundwork for non-RHEL platforms (Debian/Ubuntu) by removing hardcoded RHEL-specific names from the role logic. The web server user/group (previously `apache`), the PHP-FPM service name (previously `php-fpm`) and the base package list (previously the RHEL names `openldap-clients`/`samba-client`) are now sourced from OS-specific `vars/` via `shared/platform-variables.yml`. The web server user/group and PHP-FPM service name are exposed as the overridable variables `nextcloud__webserver_user`, `nextcloud__webserver_group` and `nextcloud__php_fpm_service_name` and are now used throughout the tasks, the deployed systemd services and the notify_push unit (not just the `/usr/local/bin/nextcloud-update` script). The SELinux `restorecon` tasks are now guarded by `ansible_facts["selinux"]["status"] != "disabled"`, and the SELinux blocks in the update script use `ansible_facts["os_family"]` instead of `ansible_os_family`. Only `vars/RedHat.yml` ships, so the role still runs on RHEL only (see `COMPATIBILITY.md`); adding a tested `vars/Debian.yml` is all that is needed to extend support. * **playbooks**: Enable the CRB repository on Rocky 10 too, not just Rocky 9. Previously Rocky 10 hosts silently skipped this step, which could leave dependencies such as `python3-virtualenv` uninstallable. * **role:grafana**: Apply the systemd/chkconfig workaround on RHEL 10 as well, not just RHEL 9. * **role:tools**: Install the German locale package (`glibc-langpack-de`) on RHEL 10 as well. diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index c00f3741a..c73f8dee2 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -237,6 +237,12 @@ nextcloud__users: * Type: String. * Default: `'*:50:15'` +`nextcloud__php_fpm_service_name` + +* Name of the PHP-FPM systemd service that the role restarts (and that the `/usr/local/bin/nextcloud-update` script restarts). Defaults to the OS-specific value (`php-fpm` on RHEL, `php-fpm` on Debian). +* Type: String. +* Default: Have a look at [vars/](https://github.com/Linuxfabrik/lfops/blob/main/roles/nextcloud/vars/) + `nextcloud__skip_apps` * Completely skips the management of Nextcloud apps. Set this to prevent changes via the WebGUI from being overwritten. @@ -332,6 +338,18 @@ nextcloud__users: * Type: Number. * Default: `80` +`nextcloud__webserver_group` + +* Group of the web server, used for file ownership of the Nextcloud installation. Defaults to the OS-specific value (`apache` on RHEL, `www-data` on Debian). +* Type: String. +* Default: Have a look at [vars/](https://github.com/Linuxfabrik/lfops/blob/main/roles/nextcloud/vars/) + +`nextcloud__webserver_user` + +* User of the web server, used for file ownership, to run the `occ` commands and as the `User=` of the deployed systemd services. Defaults to the OS-specific value (`apache` on RHEL, `www-data` on Debian). +* Type: String. +* Default: Have a look at [vars/](https://github.com/Linuxfabrik/lfops/blob/main/roles/nextcloud/vars/) + Example: ```yaml # optional diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index 9e491ac61..67b539830 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -177,6 +177,9 @@ nextcloud__mariadb_login: '{{ mariadb_server__admin_user }}' nextcloud__on_calendar_app_update: '06,18,23:{{ 59 | random(seed=inventory_hostname) }}' nextcloud__on_calendar_jobs: '*:0/5' # every 5 minutes nextcloud__on_calendar_scan_files: '*:50:15' # every hour at hh:50:15 + +nextcloud__php_fpm_service_name: '{{ __nextcloud__php_fpm_service_name }}' + nextcloud__skip_apps: false nextcloud__skip_notify_push: false @@ -310,6 +313,9 @@ nextcloud__timer_scan_files_enabled: true # 'latest', 'latest-XX' or 'nextcloud-XX.X.XX' nextcloud__version: 'latest' +nextcloud__webserver_group: '{{ __nextcloud__webserver_group }}' +nextcloud__webserver_user: '{{ __nextcloud__webserver_user }}' + # ----------------------------------------------------------------------------- nextcloud__apache_httpd__mods__dependent_var: @@ -546,6 +552,6 @@ nextcloud__systemd_unit__services__dependent_var: Environment=PORT=7867 ExecStartPre=-/bin/chcon --type bin_t /var/www/html/nextcloud/apps/notify_push/bin/x86_64/notify_push ExecStart=/var/www/html/nextcloud/apps/notify_push/bin/x86_64/notify_push /var/www/html/nextcloud/config/config.php - User=apache + User={{ nextcloud__webserver_user }} enabled: true state: 'present' diff --git a/roles/nextcloud/tasks/create-user.yml b/roles/nextcloud/tasks/create-user.yml index c6cfb78fd..4f58d50ab 100644 --- a/roles/nextcloud/tasks/create-user.yml +++ b/roles/nextcloud/tasks/create-user.yml @@ -1,7 +1,7 @@ - name: 'Create Nextcloud user {{ ncuser["username"] }}' ansible.builtin.shell: >- export OC_PASS={{ ncuser["password"] | quote }}; - sudo -E -u apache php occ user:add + sudo -E -u {{ nextcloud__webserver_user }} php occ user:add --password-from-env --group {{ ncuser["group"] | d('""') | quote }} {{ ncuser["username"] | quote }} @@ -15,7 +15,7 @@ - name: 'Update Nextcloud settings for user {{ ncuser["username"] }}' ansible.builtin.command: | - sudo -u apache php occ user:setting {{ ncuser["username"] }} {{ item }} + sudo -u {{ nextcloud__webserver_user }} php occ user:setting {{ ncuser["username"] }} {{ item }} args: chdir: '/var/www/html/nextcloud/' # changed_when: there is no easy way to check for changes diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index 72f673a21..b03756433 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -1,12 +1,19 @@ - block: - - name: 'Install bzip2 jq openldap-clients samba-client' + - name: 'Set platform/version specific variables' + ansible.builtin.import_role: + name: 'shared' + tasks_from: 'platform-variables.yml' + + tags: + - 'always' + + +- block: + + - name: 'Install required packages' ansible.builtin.package: - name: - - 'bzip2' - - 'jq' - - 'openldap-clients' - - 'samba-client' + name: '{{ __nextcloud__packages }}' state: 'present' - name: 'wget https://download.nextcloud.com/server/releases/{{ nextcloud__version }}.tar.bz2' @@ -26,25 +33,25 @@ backup: true src: 'var/www/html/nextcloud/config/objectstore.config.php.j2' dest: '/var/www/html/nextcloud/config/objectstore.config.php' - owner: 'apache' - group: 'apache' + owner: '{{ nextcloud__webserver_user }}' + group: '{{ nextcloud__webserver_group }}' mode: 0o644 when: '(nextcloud__storage_backend_s3["bucket"] is defined and nextcloud__storage_backend_s3["bucket"] | length > 0) or (nextcloud__storage_backend_swift["bucket"] is defined and nextcloud__storage_backend_swift["bucket"] | length > 0)' - - name: 'chown -R apache:apache /var/www/html/nextcloud' + - name: 'chown -R {{ nextcloud__webserver_user }}:{{ nextcloud__webserver_group }} /var/www/html/nextcloud' ansible.builtin.file: path: '/var/www/html/nextcloud' - owner: 'apache' - group: 'apache' + owner: '{{ nextcloud__webserver_user }}' + group: '{{ nextcloud__webserver_group }}' recurse: true - - name: 'mkdir path/to/data; chown -R apache:apache path/to/data; chmod 0750 -R path/to/data' + - name: 'mkdir path/to/data; chown -R {{ nextcloud__webserver_user }}:{{ nextcloud__webserver_group }} path/to/data; chmod 0750 -R path/to/data' ansible.builtin.file: path: '{{ item }}' state: 'directory' - owner: 'apache' - group: 'apache' + owner: '{{ nextcloud__webserver_user }}' + group: '{{ nextcloud__webserver_group }}' mode: 0o750 loop: - '/data' @@ -59,6 +66,8 @@ ansible.builtin.command: 'restorecon -Fvr /data /var/www/html/nextcloud' register: 'nextcloud__restorecon_nextcloud_result' changed_when: 'nextcloud__restorecon_nextcloud_result["stdout"] | length > 0' + when: + - 'ansible_facts["selinux"]["status"] != "disabled"' - name: 'Run the Nextcloud installer' # installation hangs without "--admin-user" and "--admin-pass" @@ -76,7 +85,7 @@ chdir: '/var/www/html/nextcloud/' creates: '/var/www/html/nextcloud/config/config.php' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' - name: 'Convert some database columns to big int' ansible.builtin.command: | @@ -84,7 +93,7 @@ args: chdir: '/var/www/html/nextcloud/' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' register: 'nextcloud__convert_filecache_bigint_result' changed_when: '"All tables already up to date" not in nextcloud__convert_filecache_bigint_result["stdout"]' # changed_when: there is no easy way to check for changes @@ -98,7 +107,7 @@ - name: 'Get Nextcloud config list' ansible.builtin.command: 'php /var/www/html/nextcloud/occ --no-interaction --output=json config:list --private' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' changed_when: false check_mode: false register: '__nextcloud__config_list_result' @@ -111,14 +120,14 @@ state: '{{ item["state"] | d("present") }}' installed_config_json: '{{ __nextcloud__config_list_result["stdout"] | from_json }}' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' loop: '{{ nextcloud__sysconfig__combined_var }}' # do this straight after the installation to get NC up and running # otherwise subsequent occ commands might fail - - name: 'restart php-fpm' + - name: 'restart {{ nextcloud__php_fpm_service_name }}' ansible.builtin.service: - name: 'php-fpm' + name: '{{ nextcloud__php_fpm_service_name }}' state: 'restarted' tags: @@ -131,7 +140,7 @@ - name: 'Get Nextcloud app list' ansible.builtin.command: 'php /var/www/html/nextcloud/occ --no-interaction --output=json app:list' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' changed_when: false check_mode: false register: '__nextcloud__app_list_result' @@ -143,13 +152,13 @@ force: '{{ item["force"] | d(false) }}' installed_apps_json: '{{ __nextcloud__app_list_result["stdout"] | from_json }}' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' loop: '{{ nextcloud__apps__combined_var }}' - name: 'Get Nextcloud config list' ansible.builtin.command: 'php /var/www/html/nextcloud/occ --no-interaction --output=json config:list --private' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' changed_when: false check_mode: false register: '__nextcloud__config_list_result' @@ -163,12 +172,12 @@ state: '{{ item["state"] | d("present") }}' installed_config_json: '{{ __nextcloud__config_list_result["stdout"] | from_json }}' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' loop: '{{ nextcloud__app_configs__combined_var }}' - - name: 'restart php-fpm' + - name: 'restart {{ nextcloud__php_fpm_service_name }}' ansible.builtin.service: - name: 'php-fpm' + name: '{{ nextcloud__php_fpm_service_name }}' state: 'restarted' when: @@ -184,6 +193,8 @@ ansible.builtin.command: 'restorecon -Fvr /var/www/html/nextcloud/apps/notify_push/' register: 'nextcloud__restorecon_notify_push_result' changed_when: 'nextcloud__restorecon_notify_push_result["stdout"] | length > 0' + when: + - 'ansible_facts["selinux"]["status"] != "disabled"' - name: 'systemctl restart notify_push.service' ansible.builtin.systemd_service: @@ -196,7 +207,7 @@ args: chdir: '/var/www/html/nextcloud/' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' when: - 'not nextcloud__skip_notify_push' @@ -221,11 +232,11 @@ - block: - - name: 'chown -R apache:apache /var/www/html/nextcloud' + - name: 'chown -R {{ nextcloud__webserver_user }}:{{ nextcloud__webserver_group }} /var/www/html/nextcloud' ansible.builtin.file: path: '/var/www/html/nextcloud' - owner: 'apache' - group: 'apache' + owner: '{{ nextcloud__webserver_user }}' + group: '{{ nextcloud__webserver_group }}' recurse: true tags: @@ -260,7 +271,7 @@ args: chdir: '/var/www/html/nextcloud/' become: true - become_user: 'apache' + become_user: '{{ nextcloud__webserver_user }}' # changed_when: there is no easy way to check for changes - name: 'Deploy /etc/systemd/system/nextcloud-app-update.service' diff --git a/roles/nextcloud/templates/etc/systemd/system/nextcloud-app-update.service.j2 b/roles/nextcloud/templates/etc/systemd/system/nextcloud-app-update.service.j2 index 06ef0f523..a5f42bd4e 100644 --- a/roles/nextcloud/templates/etc/systemd/system/nextcloud-app-update.service.j2 +++ b/roles/nextcloud/templates/etc/systemd/system/nextcloud-app-update.service.j2 @@ -1,5 +1,5 @@ # {{ ansible_managed }} -# 2022100401 +# 2026052001 [Unit] Description=Nextcloud App Update Service @@ -8,4 +8,4 @@ Description=Nextcloud App Update Service ExecStart=/usr/bin/php occ app:update --all --no-interaction --quiet WorkingDirectory=/var/www/html/nextcloud Type=oneshot -User=apache +User={{ nextcloud__webserver_user }} diff --git a/roles/nextcloud/templates/etc/systemd/system/nextcloud-jobs.service.j2 b/roles/nextcloud/templates/etc/systemd/system/nextcloud-jobs.service.j2 index 668db63b9..4a8423ddd 100644 --- a/roles/nextcloud/templates/etc/systemd/system/nextcloud-jobs.service.j2 +++ b/roles/nextcloud/templates/etc/systemd/system/nextcloud-jobs.service.j2 @@ -1,5 +1,5 @@ # {{ ansible_managed }} -# 2023041802 +# 2026052001 [Unit] Description=Nextcloud Background Jobs Service @@ -7,6 +7,6 @@ Description=Nextcloud Background Jobs Service [Service] ExecStart=/usr/bin/php --file /var/www/html/nextcloud/cron.php Type=oneshot -User=apache +User={{ nextcloud__webserver_user }} KillMode=process TimeoutStartSec=10m diff --git a/roles/nextcloud/templates/etc/systemd/system/nextcloud-ldap-show-remnants.service.j2 b/roles/nextcloud/templates/etc/systemd/system/nextcloud-ldap-show-remnants.service.j2 index 3e69a47d1..3418eeeba 100644 --- a/roles/nextcloud/templates/etc/systemd/system/nextcloud-ldap-show-remnants.service.j2 +++ b/roles/nextcloud/templates/etc/systemd/system/nextcloud-ldap-show-remnants.service.j2 @@ -1,5 +1,5 @@ # {{ ansible_managed }} -# 2024062101 +# 2026052001 [Unit] Description=Nextcloud LDAP Show Remnants Service @@ -8,4 +8,4 @@ Description=Nextcloud LDAP Show Remnants Service # need the help of /bin/sh here, since systemd units don't understand pipes directly ExecStart=/usr/local/bin/nextcloud-ldap-show-remnants Type=oneshot -User=apache +User={{ nextcloud__webserver_user }} diff --git a/roles/nextcloud/templates/etc/systemd/system/nextcloud-scan-files.service.j2 b/roles/nextcloud/templates/etc/systemd/system/nextcloud-scan-files.service.j2 index f5b66f9eb..c45fad609 100644 --- a/roles/nextcloud/templates/etc/systemd/system/nextcloud-scan-files.service.j2 +++ b/roles/nextcloud/templates/etc/systemd/system/nextcloud-scan-files.service.j2 @@ -1,5 +1,5 @@ # {{ ansible_managed }} -# 2022110701 +# 2026052001 [Unit] Description=Nextcloud Scan Files Service @@ -8,4 +8,4 @@ Description=Nextcloud Scan Files Service ExecStart=/usr/bin/nice --adjustment 19 /usr/bin/php occ files:scan --all --unscanned WorkingDirectory=/var/www/html/nextcloud Type=oneshot -User=apache +User={{ nextcloud__webserver_user }} diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 index 953c62244..c5ad20d7a 100644 --- a/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 +++ b/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 @@ -1,6 +1,6 @@ #!/usr/bin/env bash # {{ ansible_managed }} -# 2026051102 +# 2026052001 set -euo pipefail @@ -11,9 +11,9 @@ error_handler() { } trap 'error_handler "${LINENO}"' ERR -WEBSERVER_USER="apache" -WEBSERVER_GROUP="apache" -PHP_SERVICE_NAME="php-fpm" +WEBSERVER_USER="{{ nextcloud__webserver_user }}" +WEBSERVER_GROUP="{{ nextcloud__webserver_group }}" +PHP_SERVICE_NAME="{{ nextcloud__php_fpm_service_name }}" NC_DIR="/var/www/html/nextcloud" DATA_DIR=$(sudo -u "${WEBSERVER_USER}" php "${NC_DIR}/occ" config:system:get datadirectory) @@ -84,7 +84,7 @@ else echo 'skipping.' fi -{% if ansible_os_family == "RedHat" %} +{% if ansible_facts["os_family"] == "RedHat" %} echo echo 'setsebool httpd_unified on' echo '--------------------------' @@ -140,7 +140,7 @@ else echo 'skipping.' fi -{% if ansible_os_family == "RedHat" %} +{% if ansible_facts["os_family"] == "RedHat" %} echo echo 'setsebool httpd_unified off' echo '---------------------------' @@ -186,7 +186,7 @@ else echo 'skipping.' fi -{% if ansible_os_family == "RedHat" %} +{% if ansible_facts["os_family"] == "RedHat" %} echo echo "restorecon" echo '----------' diff --git a/roles/nextcloud/vars/RedHat.yml b/roles/nextcloud/vars/RedHat.yml new file mode 100644 index 000000000..d8f8c5311 --- /dev/null +++ b/roles/nextcloud/vars/RedHat.yml @@ -0,0 +1,8 @@ +__nextcloud__packages: + - 'bzip2' + - 'jq' + - 'openldap-clients' + - 'samba-client' +__nextcloud__php_fpm_service_name: 'php-fpm' +__nextcloud__webserver_group: 'apache' +__nextcloud__webserver_user: 'apache' From 4b0838eb05560e4cd0bf8634eb5b02df36963aba Mon Sep 17 00:00:00 2001 From: Danyal Berchtold Date: Wed, 20 May 2026 10:29:43 +0200 Subject: [PATCH 02/14] refactor(roles/nextcloud): add argument_specs and align internal var name Add meta/argument_specs.yml declaring all user-facing variables so Ansible validates required variables (nextcloud__fqdn, nextcloud__users) and types at role entry, including the new nextcloud__webserver_user, nextcloud__webserver_group and nextcloud__php_fpm_service_name. Rename the internal __nextcloud__packages to __nextcloud__required_packages for consistency with the example role's __example__required_packages. --- CHANGELOG.md | 1 + roles/nextcloud/meta/argument_specs.yml | 209 ++++++++++++++++++++++++ roles/nextcloud/tasks/main.yml | 2 +- roles/nextcloud/vars/RedHat.yml | 2 +- 4 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 roles/nextcloud/meta/argument_specs.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bb2c2103..e48575e50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* **role:nextcloud**: Add `meta/argument_specs.yml` declaring all user-facing variables (including the new `nextcloud__webserver_user`, `nextcloud__webserver_group` and `nextcloud__php_fpm_service_name`), so Ansible validates required variables and types at role entry. * **role:tmux**: Installs tmux and deploys a system-wide `/etc/tmux.conf` with sensible defaults, such as a larger scrollback buffer and mouse support. Selections are copied to the local clipboard over SSH via OSC 52 (where the terminal emulator supports it), and `prefix + P` dumps a pane's whole scrollback buffer to a file. * **role:graylog_server**: Make more HTTP, Elasticsearch, processing/output buffer and message journal settings configurable via `graylog_server__http_external_uri`, `graylog_server__http_enable_cors`, `graylog_server__elasticsearch_max_total_connections`, `graylog_server__elasticsearch_max_total_connections_per_route`, `graylog_server__output_batch_size`, `graylog_server__processbuffer_processors`, `graylog_server__outputbuffer_processors`, `graylog_server__ring_size`, `graylog_server__inputbuffer_ring_size`, `graylog_server__message_journal_max_age` and `graylog_server__message_journal_max_size`. * **role:mariadb_server**: Make `aria_pagecache_buffer_size`, `key_buffer_size` and `sort_buffer_size` configurable via the corresponding `mariadb_server__cnf_*` variables. diff --git a/roles/nextcloud/meta/argument_specs.yml b/roles/nextcloud/meta/argument_specs.yml new file mode 100644 index 000000000..03fb1d27c --- /dev/null +++ b/roles/nextcloud/meta/argument_specs.yml @@ -0,0 +1,209 @@ +argument_specs: + main: + options: + + nextcloud__app_configs__dependent_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Key-value pairs for configuring apps. Dependent-role injection.' + + nextcloud__app_configs__group_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Key-value pairs for configuring apps. Group-level override.' + + nextcloud__app_configs__host_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Key-value pairs for configuring apps. Host-level override.' + + nextcloud__apps__dependent_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Nextcloud apps to install. Dependent-role injection.' + + nextcloud__apps__group_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Nextcloud apps to install. Group-level override.' + + nextcloud__apps__host_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Nextcloud apps to install. Host-level override.' + + nextcloud__database_host: + type: 'str' + required: false + default: 'localhost' + description: 'Host where MariaDB is located.' + + nextcloud__database_name: + type: 'str' + required: false + default: 'nextcloud' + description: 'Name of the Nextcloud database in MariaDB.' + + nextcloud__datadir: + type: 'str' + required: false + default: '/data' + description: 'Where to store the user files.' + + nextcloud__fqdn: + type: 'str' + required: true + description: 'The FQDN of the Nextcloud instance.' + + nextcloud__icinga2_api_url: + type: 'str' + required: false + description: 'The URL of the Icinga2 API used to set/remove a downtime in the nextcloud-update script.' + + nextcloud__icinga2_api_user_login: + type: 'dict' + required: false + description: 'The Icinga2 API user used to set/remove a downtime in the nextcloud-update script.' + + nextcloud__icinga2_hostname: + type: 'str' + required: false + description: 'The hostname of the Icinga2 host on which the downtime should be set.' + + nextcloud__mariadb_login: + type: 'dict' + required: false + description: 'The database administrator account. The Nextcloud setup will create its own database account.' + + nextcloud__on_calendar_app_update: + type: 'str' + required: false + description: 'Time to update the Nextcloud apps. See systemd.time(7) for the format.' + + nextcloud__on_calendar_jobs: + type: 'str' + required: false + default: '*:0/5' + description: 'Run interval of OCC background jobs. See systemd.time(7) for the format.' + + nextcloud__on_calendar_scan_files: + type: 'str' + required: false + default: '*:50:15' + description: 'Run interval of rescanning the filesystem. See systemd.time(7) for the format.' + + nextcloud__php_fpm_service_name: + type: 'str' + required: false + description: 'Name of the PHP-FPM systemd service that the role and the nextcloud-update script restart. OS-specific default from vars/.' + + nextcloud__skip_apps: + type: 'bool' + required: false + default: false + description: 'Completely skips the management of Nextcloud apps.' + + nextcloud__skip_notify_push: + type: 'bool' + required: false + default: false + description: 'Skips the configuration of notify_push.' + + nextcloud__storage_backend_s3: + type: 'dict' + required: false + description: 'S3 storage backend. If omitted, local storage is used.' + + nextcloud__storage_backend_swift: + type: 'dict' + required: false + description: 'Swift storage backend. If omitted, local storage is used.' + + nextcloud__sysconfig__dependent_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Nextcloud system config settings. Dependent-role injection.' + + nextcloud__sysconfig__group_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Nextcloud system config settings. Group-level override.' + + nextcloud__sysconfig__host_var: + type: 'list' + elements: 'dict' + required: false + default: [] + description: 'Nextcloud system config settings. Host-level override.' + + nextcloud__timer_app_update_enabled: + type: 'bool' + required: false + default: false + description: 'Enables/disables the systemd timer for updating apps.' + + nextcloud__timer_jobs_enabled: + type: 'bool' + required: false + default: true + description: 'Enables/disables the systemd timer for running OCC background jobs.' + + nextcloud__timer_ldap_show_remnants_enabled: + type: 'bool' + required: false + default: true + description: 'Enables/disables the systemd timer for mailing LDAP remnants once a month.' + + nextcloud__timer_scan_files_enabled: + type: 'bool' + required: false + default: true + description: 'Enables/disables the systemd timer for re-scanning the Nextcloud files.' + + nextcloud__users: + type: 'list' + elements: 'dict' + required: true + description: 'User accounts to create. The first user has to be the primary administrator account.' + + nextcloud__version: + type: 'str' + required: false + default: 'latest' + description: "Which version to install. One of 'latest', 'latest-XX' or 'nextcloud-XX.X.XX'." + + nextcloud__vhost_virtualhost_ip: + type: 'str' + required: false + description: 'Used within the directive.' + + nextcloud__vhost_virtualhost_port: + type: 'int' + required: false + description: 'Used within the directive.' + + nextcloud__webserver_group: + type: 'str' + required: false + description: 'Group of the web server, used for file ownership. OS-specific default from vars/.' + + nextcloud__webserver_user: + type: 'str' + required: false + description: 'User of the web server, used for file ownership, occ commands and as the User= of the deployed systemd services. OS-specific default from vars/.' diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index b03756433..330d551cf 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -13,7 +13,7 @@ - name: 'Install required packages' ansible.builtin.package: - name: '{{ __nextcloud__packages }}' + name: '{{ __nextcloud__required_packages }}' state: 'present' - name: 'wget https://download.nextcloud.com/server/releases/{{ nextcloud__version }}.tar.bz2' diff --git a/roles/nextcloud/vars/RedHat.yml b/roles/nextcloud/vars/RedHat.yml index d8f8c5311..3612dbb94 100644 --- a/roles/nextcloud/vars/RedHat.yml +++ b/roles/nextcloud/vars/RedHat.yml @@ -1,4 +1,4 @@ -__nextcloud__packages: +__nextcloud__required_packages: - 'bzip2' - 'jq' - 'openldap-clients' From ef7cf3c5cdff5f16405ae00c7e758900e536424b Mon Sep 17 00:00:00 2001 From: Danyal Berchtold Date: Thu, 4 Jun 2026 17:09:27 +0200 Subject: [PATCH 03/14] refactor(roles/nextcloud): source OS-specific names from shared and php vars, select php modules via platform_select --- CHANGELOG.md | 4 +- roles/nextcloud/README.md | 16 +------ roles/nextcloud/defaults/main.yml | 48 ++++--------------- roles/nextcloud/meta/argument_specs.yml | 12 +---- roles/nextcloud/tasks/create-user.yml | 4 +- roles/nextcloud/tasks/main.yml | 42 ++++++++-------- .../system/nextcloud-app-update.service.j2 | 2 +- .../systemd/system/nextcloud-jobs.service.j2 | 2 +- .../nextcloud-ldap-show-remnants.service.j2 | 2 +- .../system/nextcloud-scan-files.service.j2 | 2 +- .../usr/local/bin/nextcloud-update.j2 | 4 +- roles/nextcloud/vars/RedHat.yml | 3 -- roles/nextcloud/vars/main.yml | 39 +++++++++++++++ 13 files changed, 81 insertions(+), 99 deletions(-) create mode 100644 roles/nextcloud/vars/main.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index e48575e50..b358f3543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* **role:nextcloud**: Add `meta/argument_specs.yml` declaring all user-facing variables (including the new `nextcloud__webserver_user`, `nextcloud__webserver_group` and `nextcloud__php_fpm_service_name`), so Ansible validates required variables and types at role entry. +* **role:nextcloud**: Add `meta/argument_specs.yml` declaring all user-facing variables (including the new `nextcloud__php_fpm_service_name`), so Ansible validates required variables and types at role entry. * **role:tmux**: Installs tmux and deploys a system-wide `/etc/tmux.conf` with sensible defaults, such as a larger scrollback buffer and mouse support. Selections are copied to the local clipboard over SSH via OSC 52 (where the terminal emulator supports it), and `prefix + P` dumps a pane's whole scrollback buffer to a file. * **role:graylog_server**: Make more HTTP, Elasticsearch, processing/output buffer and message journal settings configurable via `graylog_server__http_external_uri`, `graylog_server__http_enable_cors`, `graylog_server__elasticsearch_max_total_connections`, `graylog_server__elasticsearch_max_total_connections_per_route`, `graylog_server__output_batch_size`, `graylog_server__processbuffer_processors`, `graylog_server__outputbuffer_processors`, `graylog_server__ring_size`, `graylog_server__inputbuffer_ring_size`, `graylog_server__message_journal_max_age` and `graylog_server__message_journal_max_size`. * **role:mariadb_server**: Make `aria_pagecache_buffer_size`, `key_buffer_size` and `sort_buffer_size` configurable via the corresponding `mariadb_server__cnf_*` variables. @@ -61,7 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -* **role:nextcloud**: Lay the groundwork for non-RHEL platforms (Debian/Ubuntu) by removing hardcoded RHEL-specific names from the role logic. The web server user/group (previously `apache`), the PHP-FPM service name (previously `php-fpm`) and the base package list (previously the RHEL names `openldap-clients`/`samba-client`) are now sourced from OS-specific `vars/` via `shared/platform-variables.yml`. The web server user/group and PHP-FPM service name are exposed as the overridable variables `nextcloud__webserver_user`, `nextcloud__webserver_group` and `nextcloud__php_fpm_service_name` and are now used throughout the tasks, the deployed systemd services and the notify_push unit (not just the `/usr/local/bin/nextcloud-update` script). The SELinux `restorecon` tasks are now guarded by `ansible_facts["selinux"]["status"] != "disabled"`, and the SELinux blocks in the update script use `ansible_facts["os_family"]` instead of `ansible_os_family`. Only `vars/RedHat.yml` ships, so the role still runs on RHEL only (see `COMPATIBILITY.md`); adding a tested `vars/Debian.yml` is all that is needed to extend support. +* **role:nextcloud**: Lay the groundwork for non-RHEL platforms (Debian/Ubuntu) by removing hardcoded RHEL-specific names from the role logic, sourcing each OS-specific value through the mechanism that fits its scope. The web server user/group (previously the hardcoded `apache`) now come directly from the LFOps-wide shared values `__shared__apache_httpd_user`/`__shared__apache_httpd_group` (apache on RedHat, www-data on Debian, wwwrun/www on Suse) throughout the tasks, templates and systemd units, rather than through a redundant role-local variable. The base package list (previously the RHEL names `openldap-clients`/`samba-client`) is sourced from the role-local `vars/RedHat.yml` via `shared/platform-variables.yml`, while the PHP-FPM service name (`nextcloud__php_fpm_service_name`) now defaults to the `php` role's own `php__fpm_service_name` instead of a duplicated role-local value, so the Debian `php-fpm` naming comes for free. The PHP module list consumed by the earlier-running `php` role (`nextcloud__php__modules__dependent_var`) is now an OS-keyed dict in `vars/main.yml` selected with the `linuxfabrik.lfops.platform_select` filter, since `vars/.yml` loads too late for that cross-role `__dependent_var` hand-off. These variables are now used throughout the tasks, the deployed systemd services and the notify_push unit (not just the `/usr/local/bin/nextcloud-update` script). The SELinux `restorecon` tasks are now guarded by `ansible_facts["selinux"]["status"] != "disabled"`, and the SELinux blocks in the update script use `ansible_facts["os_family"]` instead of `ansible_os_family`. Only RHEL package names ship so far, so the role still runs on RHEL only (see `COMPATIBILITY.md`); adding a tested `vars/Debian.yml` plus the matching `platform_select` keys is all that is needed to extend support. * **playbooks**: Enable the CRB repository on Rocky 10 too, not just Rocky 9. Previously Rocky 10 hosts silently skipped this step, which could leave dependencies such as `python3-virtualenv` uninstallable. * **role:grafana**: Apply the systemd/chkconfig workaround on RHEL 10 as well, not just RHEL 9. * **role:tools**: Install the German locale package (`glibc-langpack-de`) on RHEL 10 as well. diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index c73f8dee2..28b663a9a 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -239,9 +239,9 @@ nextcloud__users: `nextcloud__php_fpm_service_name` -* Name of the PHP-FPM systemd service that the role restarts (and that the `/usr/local/bin/nextcloud-update` script restarts). Defaults to the OS-specific value (`php-fpm` on RHEL, `php-fpm` on Debian). +* Name of the PHP-FPM systemd service that the role restarts (and that the `/usr/local/bin/nextcloud-update` script restarts). Defaults to the `php` role's `php__fpm_service_name` (`php-fpm` on RHEL, `php-fpm` on Debian). * Type: String. -* Default: Have a look at [vars/](https://github.com/Linuxfabrik/lfops/blob/main/roles/nextcloud/vars/) +* Default: `{{ php__fpm_service_name }}` `nextcloud__skip_apps` @@ -338,18 +338,6 @@ nextcloud__users: * Type: Number. * Default: `80` -`nextcloud__webserver_group` - -* Group of the web server, used for file ownership of the Nextcloud installation. Defaults to the OS-specific value (`apache` on RHEL, `www-data` on Debian). -* Type: String. -* Default: Have a look at [vars/](https://github.com/Linuxfabrik/lfops/blob/main/roles/nextcloud/vars/) - -`nextcloud__webserver_user` - -* User of the web server, used for file ownership, to run the `occ` commands and as the `User=` of the deployed systemd services. Defaults to the OS-specific value (`apache` on RHEL, `www-data` on Debian). -* Type: String. -* Default: Have a look at [vars/](https://github.com/Linuxfabrik/lfops/blob/main/roles/nextcloud/vars/) - Example: ```yaml # optional diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index 67b539830..32a2ecdbb 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -178,7 +178,8 @@ nextcloud__on_calendar_app_update: '06,18,23:{{ 59 | random(seed=inventory_hostn nextcloud__on_calendar_jobs: '*:0/5' # every 5 minutes nextcloud__on_calendar_scan_files: '*:50:15' # every hour at hh:50:15 -nextcloud__php_fpm_service_name: '{{ __nextcloud__php_fpm_service_name }}' +# The php role owns the FPM service name (php-fpm on RedHat, php-fpm on Debian); source it from there instead of duplicating it per OS. +nextcloud__php_fpm_service_name: '{{ php__fpm_service_name }}' nextcloud__skip_apps: false nextcloud__skip_notify_push: false @@ -313,9 +314,6 @@ nextcloud__timer_scan_files_enabled: true # 'latest', 'latest-XX' or 'nextcloud-XX.X.XX' nextcloud__version: 'latest' -nextcloud__webserver_group: '{{ __nextcloud__webserver_group }}' -nextcloud__webserver_user: '{{ __nextcloud__webserver_user }}' - # ----------------------------------------------------------------------------- nextcloud__apache_httpd__mods__dependent_var: @@ -477,41 +475,11 @@ nextcloud__php__ini_memory_limit__dependent_var: '1024M' nextcloud__php__ini_opcache_interned_strings_buffer__dependent_var: '20' nextcloud__php__ini_post_max_size__dependent_var: '16M' nextcloud__php__ini_upload_max_filesize__dependent_var: '10000M' -nextcloud__php__modules__dependent_var: - - name: 'php-bcmath' - state: 'present' - - name: 'php-gd' - state: 'present' - - name: 'php-gmp' - state: 'present' - - name: 'php-imap' - state: 'present' - - name: 'php-imagick' - state: 'present' - - name: 'php-intl' - state: 'present' - - name: 'php-json' - state: 'present' - - name: 'php-ldap' - state: 'present' - - name: 'php-mbstring' - state: 'present' - - name: 'php-memcached' - state: 'present' - - name: 'php-mysqlnd' - state: 'present' - - name: 'php-opcache' - state: 'present' - - name: 'php-pecl-apcu' - state: 'present' - - name: 'php-process' # posix module for oc - state: 'present' - - name: 'php-redis' - state: 'present' - - name: 'php-smbclient' - state: 'present' - - name: 'php-zip' - state: 'present' +# OS-specific package names live in vars/main.yml (see platform_select). The php role runs earlier in the play and consumes this via the __dependent_var pattern. +nextcloud__php__modules__dependent_var: '{{ + __nextcloud__php__modules__dependent_var + | linuxfabrik.lfops.platform_select(ansible_facts) + }}' nextcloud__selinux__booleans__dependent_var: - key: 'httpd_can_network_connect' @@ -552,6 +520,6 @@ nextcloud__systemd_unit__services__dependent_var: Environment=PORT=7867 ExecStartPre=-/bin/chcon --type bin_t /var/www/html/nextcloud/apps/notify_push/bin/x86_64/notify_push ExecStart=/var/www/html/nextcloud/apps/notify_push/bin/x86_64/notify_push /var/www/html/nextcloud/config/config.php - User={{ nextcloud__webserver_user }} + User={{ __shared__apache_httpd_user }} enabled: true state: 'present' diff --git a/roles/nextcloud/meta/argument_specs.yml b/roles/nextcloud/meta/argument_specs.yml index 03fb1d27c..0e6961045 100644 --- a/roles/nextcloud/meta/argument_specs.yml +++ b/roles/nextcloud/meta/argument_specs.yml @@ -107,7 +107,7 @@ argument_specs: nextcloud__php_fpm_service_name: type: 'str' required: false - description: 'Name of the PHP-FPM systemd service that the role and the nextcloud-update script restart. OS-specific default from vars/.' + description: "Name of the PHP-FPM systemd service that the role and the nextcloud-update script restart. Defaults to the php role's php__fpm_service_name (php-fpm on RedHat, php-fpm on Debian)." nextcloud__skip_apps: type: 'bool' @@ -197,13 +197,3 @@ argument_specs: type: 'int' required: false description: 'Used within the directive.' - - nextcloud__webserver_group: - type: 'str' - required: false - description: 'Group of the web server, used for file ownership. OS-specific default from vars/.' - - nextcloud__webserver_user: - type: 'str' - required: false - description: 'User of the web server, used for file ownership, occ commands and as the User= of the deployed systemd services. OS-specific default from vars/.' diff --git a/roles/nextcloud/tasks/create-user.yml b/roles/nextcloud/tasks/create-user.yml index 4f58d50ab..a0f288a64 100644 --- a/roles/nextcloud/tasks/create-user.yml +++ b/roles/nextcloud/tasks/create-user.yml @@ -1,7 +1,7 @@ - name: 'Create Nextcloud user {{ ncuser["username"] }}' ansible.builtin.shell: >- export OC_PASS={{ ncuser["password"] | quote }}; - sudo -E -u {{ nextcloud__webserver_user }} php occ user:add + sudo -E -u {{ __shared__apache_httpd_user }} php occ user:add --password-from-env --group {{ ncuser["group"] | d('""') | quote }} {{ ncuser["username"] | quote }} @@ -15,7 +15,7 @@ - name: 'Update Nextcloud settings for user {{ ncuser["username"] }}' ansible.builtin.command: | - sudo -u {{ nextcloud__webserver_user }} php occ user:setting {{ ncuser["username"] }} {{ item }} + sudo -u {{ __shared__apache_httpd_user }} php occ user:setting {{ ncuser["username"] }} {{ item }} args: chdir: '/var/www/html/nextcloud/' # changed_when: there is no easy way to check for changes diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index 330d551cf..80a173fc8 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -33,25 +33,25 @@ backup: true src: 'var/www/html/nextcloud/config/objectstore.config.php.j2' dest: '/var/www/html/nextcloud/config/objectstore.config.php' - owner: '{{ nextcloud__webserver_user }}' - group: '{{ nextcloud__webserver_group }}' + owner: '{{ __shared__apache_httpd_user }}' + group: '{{ __shared__apache_httpd_group }}' mode: 0o644 when: '(nextcloud__storage_backend_s3["bucket"] is defined and nextcloud__storage_backend_s3["bucket"] | length > 0) or (nextcloud__storage_backend_swift["bucket"] is defined and nextcloud__storage_backend_swift["bucket"] | length > 0)' - - name: 'chown -R {{ nextcloud__webserver_user }}:{{ nextcloud__webserver_group }} /var/www/html/nextcloud' + - name: 'chown -R {{ __shared__apache_httpd_user }}:{{ __shared__apache_httpd_group }} /var/www/html/nextcloud' ansible.builtin.file: path: '/var/www/html/nextcloud' - owner: '{{ nextcloud__webserver_user }}' - group: '{{ nextcloud__webserver_group }}' + owner: '{{ __shared__apache_httpd_user }}' + group: '{{ __shared__apache_httpd_group }}' recurse: true - - name: 'mkdir path/to/data; chown -R {{ nextcloud__webserver_user }}:{{ nextcloud__webserver_group }} path/to/data; chmod 0750 -R path/to/data' + - name: 'mkdir path/to/data; chown -R {{ __shared__apache_httpd_user }}:{{ __shared__apache_httpd_group }} path/to/data; chmod 0750 -R path/to/data' ansible.builtin.file: path: '{{ item }}' state: 'directory' - owner: '{{ nextcloud__webserver_user }}' - group: '{{ nextcloud__webserver_group }}' + owner: '{{ __shared__apache_httpd_user }}' + group: '{{ __shared__apache_httpd_group }}' mode: 0o750 loop: - '/data' @@ -85,7 +85,7 @@ chdir: '/var/www/html/nextcloud/' creates: '/var/www/html/nextcloud/config/config.php' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' - name: 'Convert some database columns to big int' ansible.builtin.command: | @@ -93,7 +93,7 @@ args: chdir: '/var/www/html/nextcloud/' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' register: 'nextcloud__convert_filecache_bigint_result' changed_when: '"All tables already up to date" not in nextcloud__convert_filecache_bigint_result["stdout"]' # changed_when: there is no easy way to check for changes @@ -107,7 +107,7 @@ - name: 'Get Nextcloud config list' ansible.builtin.command: 'php /var/www/html/nextcloud/occ --no-interaction --output=json config:list --private' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' changed_when: false check_mode: false register: '__nextcloud__config_list_result' @@ -120,7 +120,7 @@ state: '{{ item["state"] | d("present") }}' installed_config_json: '{{ __nextcloud__config_list_result["stdout"] | from_json }}' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' loop: '{{ nextcloud__sysconfig__combined_var }}' # do this straight after the installation to get NC up and running @@ -140,7 +140,7 @@ - name: 'Get Nextcloud app list' ansible.builtin.command: 'php /var/www/html/nextcloud/occ --no-interaction --output=json app:list' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' changed_when: false check_mode: false register: '__nextcloud__app_list_result' @@ -152,13 +152,13 @@ force: '{{ item["force"] | d(false) }}' installed_apps_json: '{{ __nextcloud__app_list_result["stdout"] | from_json }}' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' loop: '{{ nextcloud__apps__combined_var }}' - name: 'Get Nextcloud config list' ansible.builtin.command: 'php /var/www/html/nextcloud/occ --no-interaction --output=json config:list --private' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' changed_when: false check_mode: false register: '__nextcloud__config_list_result' @@ -172,7 +172,7 @@ state: '{{ item["state"] | d("present") }}' installed_config_json: '{{ __nextcloud__config_list_result["stdout"] | from_json }}' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' loop: '{{ nextcloud__app_configs__combined_var }}' - name: 'restart {{ nextcloud__php_fpm_service_name }}' @@ -207,7 +207,7 @@ args: chdir: '/var/www/html/nextcloud/' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' when: - 'not nextcloud__skip_notify_push' @@ -232,11 +232,11 @@ - block: - - name: 'chown -R {{ nextcloud__webserver_user }}:{{ nextcloud__webserver_group }} /var/www/html/nextcloud' + - name: 'chown -R {{ __shared__apache_httpd_user }}:{{ __shared__apache_httpd_group }} /var/www/html/nextcloud' ansible.builtin.file: path: '/var/www/html/nextcloud' - owner: '{{ nextcloud__webserver_user }}' - group: '{{ nextcloud__webserver_group }}' + owner: '{{ __shared__apache_httpd_user }}' + group: '{{ __shared__apache_httpd_group }}' recurse: true tags: @@ -271,7 +271,7 @@ args: chdir: '/var/www/html/nextcloud/' become: true - become_user: '{{ nextcloud__webserver_user }}' + become_user: '{{ __shared__apache_httpd_user }}' # changed_when: there is no easy way to check for changes - name: 'Deploy /etc/systemd/system/nextcloud-app-update.service' diff --git a/roles/nextcloud/templates/etc/systemd/system/nextcloud-app-update.service.j2 b/roles/nextcloud/templates/etc/systemd/system/nextcloud-app-update.service.j2 index a5f42bd4e..eea108ae0 100644 --- a/roles/nextcloud/templates/etc/systemd/system/nextcloud-app-update.service.j2 +++ b/roles/nextcloud/templates/etc/systemd/system/nextcloud-app-update.service.j2 @@ -8,4 +8,4 @@ Description=Nextcloud App Update Service ExecStart=/usr/bin/php occ app:update --all --no-interaction --quiet WorkingDirectory=/var/www/html/nextcloud Type=oneshot -User={{ nextcloud__webserver_user }} +User={{ __shared__apache_httpd_user }} diff --git a/roles/nextcloud/templates/etc/systemd/system/nextcloud-jobs.service.j2 b/roles/nextcloud/templates/etc/systemd/system/nextcloud-jobs.service.j2 index 4a8423ddd..15b2d4773 100644 --- a/roles/nextcloud/templates/etc/systemd/system/nextcloud-jobs.service.j2 +++ b/roles/nextcloud/templates/etc/systemd/system/nextcloud-jobs.service.j2 @@ -7,6 +7,6 @@ Description=Nextcloud Background Jobs Service [Service] ExecStart=/usr/bin/php --file /var/www/html/nextcloud/cron.php Type=oneshot -User={{ nextcloud__webserver_user }} +User={{ __shared__apache_httpd_user }} KillMode=process TimeoutStartSec=10m diff --git a/roles/nextcloud/templates/etc/systemd/system/nextcloud-ldap-show-remnants.service.j2 b/roles/nextcloud/templates/etc/systemd/system/nextcloud-ldap-show-remnants.service.j2 index 3418eeeba..4eb81d05c 100644 --- a/roles/nextcloud/templates/etc/systemd/system/nextcloud-ldap-show-remnants.service.j2 +++ b/roles/nextcloud/templates/etc/systemd/system/nextcloud-ldap-show-remnants.service.j2 @@ -8,4 +8,4 @@ Description=Nextcloud LDAP Show Remnants Service # need the help of /bin/sh here, since systemd units don't understand pipes directly ExecStart=/usr/local/bin/nextcloud-ldap-show-remnants Type=oneshot -User={{ nextcloud__webserver_user }} +User={{ __shared__apache_httpd_user }} diff --git a/roles/nextcloud/templates/etc/systemd/system/nextcloud-scan-files.service.j2 b/roles/nextcloud/templates/etc/systemd/system/nextcloud-scan-files.service.j2 index c45fad609..cc06b147b 100644 --- a/roles/nextcloud/templates/etc/systemd/system/nextcloud-scan-files.service.j2 +++ b/roles/nextcloud/templates/etc/systemd/system/nextcloud-scan-files.service.j2 @@ -8,4 +8,4 @@ Description=Nextcloud Scan Files Service ExecStart=/usr/bin/nice --adjustment 19 /usr/bin/php occ files:scan --all --unscanned WorkingDirectory=/var/www/html/nextcloud Type=oneshot -User={{ nextcloud__webserver_user }} +User={{ __shared__apache_httpd_user }} diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 index c5ad20d7a..fd73932ae 100644 --- a/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 +++ b/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 @@ -11,8 +11,8 @@ error_handler() { } trap 'error_handler "${LINENO}"' ERR -WEBSERVER_USER="{{ nextcloud__webserver_user }}" -WEBSERVER_GROUP="{{ nextcloud__webserver_group }}" +WEBSERVER_USER="{{ __shared__apache_httpd_user }}" +WEBSERVER_GROUP="{{ __shared__apache_httpd_group }}" PHP_SERVICE_NAME="{{ nextcloud__php_fpm_service_name }}" NC_DIR="/var/www/html/nextcloud" diff --git a/roles/nextcloud/vars/RedHat.yml b/roles/nextcloud/vars/RedHat.yml index 3612dbb94..cef93f116 100644 --- a/roles/nextcloud/vars/RedHat.yml +++ b/roles/nextcloud/vars/RedHat.yml @@ -3,6 +3,3 @@ __nextcloud__required_packages: - 'jq' - 'openldap-clients' - 'samba-client' -__nextcloud__php_fpm_service_name: 'php-fpm' -__nextcloud__webserver_group: 'apache' -__nextcloud__webserver_user: 'apache' diff --git a/roles/nextcloud/vars/main.yml b/roles/nextcloud/vars/main.yml new file mode 100644 index 000000000..2d68653bb --- /dev/null +++ b/roles/nextcloud/vars/main.yml @@ -0,0 +1,39 @@ +# OS-specific values consumed by a *different* role that runs *earlier* in the same play (the `__dependent_var` pattern). vars/.yml would load too late for an earlier consumer, but vars/main.yml is auto-loaded at play parse and is visible to every role in the play. The public variables in defaults/main.yml select from these dicts with the linuxfabrik.lfops.platform_select filter; to extend platform support, add a Debian / Suse key here. + +# PHP modules installed by the linuxfabrik.lfops.php role via nextcloud__php__modules__dependent_var. +__nextcloud__php__modules__dependent_var: + RedHat: + - name: 'php-bcmath' + state: 'present' + - name: 'php-gd' + state: 'present' + - name: 'php-gmp' + state: 'present' + - name: 'php-imap' + state: 'present' + - name: 'php-imagick' + state: 'present' + - name: 'php-intl' + state: 'present' + - name: 'php-json' + state: 'present' + - name: 'php-ldap' + state: 'present' + - name: 'php-mbstring' + state: 'present' + - name: 'php-memcached' + state: 'present' + - name: 'php-mysqlnd' + state: 'present' + - name: 'php-opcache' + state: 'present' + - name: 'php-pecl-apcu' + state: 'present' + - name: 'php-process' # posix module for oc + state: 'present' + - name: 'php-redis' + state: 'present' + - name: 'php-smbclient' + state: 'present' + - name: 'php-zip' + state: 'present' From 6017ba571d69c183dcd49e691e158122ef0b1fd8 Mon Sep 17 00:00:00 2001 From: Danyal Berchtold Date: Mon, 8 Jun 2026 10:16:25 +0200 Subject: [PATCH 04/14] fix(roles/nextcloud): source ldap-show-remnants recipients from nextcloud__mailto_root__to The nextcloud-ldap-show-remnants script referenced setup_basic__skip_mailto_root, a setup_basic playbook variable that is undefined when the role runs from setup_nextcloud (e.g. with --tags nextcloud:cron), aborting the nextcloud:cron deploy. Add a namespaced nextcloud__mailto_root__to role variable (defaulting to the global mailto_root__to) and gate the template on it: mail the report when recipients are set, print to stdout otherwise. Declared in argument_specs and documented in the README. --- CHANGELOG.md | 1 + roles/nextcloud/README.md | 6 ++++++ roles/nextcloud/defaults/main.yml | 3 ++- roles/nextcloud/meta/argument_specs.yml | 6 ++++++ .../usr/local/bin/nextcloud-ldap-show-remnants.j2 | 13 ++++++------- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d625c7117..2b02be71c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -146,6 +146,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* **role:nextcloud**: The `nextcloud-ldap-show-remnants` script no longer aborts the `nextcloud:cron` deploy with `'setup_basic__skip_mailto_root' is undefined` when the role runs outside the `setup_basic` playbook (e.g. via `--tags nextcloud:cron` in `setup_nextcloud`). The report recipients now come from the new role variable `nextcloud__mailto_root__to` (defaulting to the global `mailto_root__to`); the report is mailed when recipients are set and printed to stdout otherwise. * **role:php**: php-fpm workers now run with a defined `PATH`. Previously the worker environment was cleared, leaving `getenv("PATH")` empty, which broke PHP code that shells out to system binaries and tripped Nextcloud's "PHP getenv" setup warning. * **role:redis**: The Redis configuration file is no longer world-readable. It is now deployed as `root:redis` with mode `0640`, so its contents (e.g. a configured password) can no longer be read by other local users. * **role:acme_sh**: No longer reinstalls every certificate and reloads the web server on every run. Certificates are only reinstalled when they were just (re)issued or when the installed file is missing, so repeated runs are idempotent. diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index eb991d536..7c63cfa05 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -219,6 +219,12 @@ nextcloud__users: * Type: String. * Default: `'{{ ansible_facts["nodename"] }}'` +`nextcloud__mailto_root__to` + +* Recipients of the monthly `ldap:show-remnants` report (users removed from LDAP that still have remnants in Nextcloud) sent by `/usr/local/bin/nextcloud-ldap-show-remnants`. Defaults to the global `mailto_root__to`; when empty the report is printed to stdout instead of being mailed. +* Type: List. +* Default: `'{{ mailto_root__to | d([]) }}'` + `nextcloud__mariadb_login` * The user account for the database administrator. The Nextcloud setup will create its own database account. diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index ebc560cca..b58f94b2f 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -172,13 +172,14 @@ nextcloud__icinga2_api_url: 'https://{{ icinga2_agent__icinga2_master_host | d(" nextcloud__icinga2_api_user_login: '{{ system_update__icinga2_api_user_login }}' nextcloud__icinga2_hostname: '{{ ansible_facts["nodename"] }}' +nextcloud__mailto_root__to: '{{ mailto_root__to | d([]) }}' + nextcloud__mariadb_login: '{{ mariadb_server__admin_user }}' nextcloud__on_calendar_app_update: '06,18,23:{{ 59 | random(seed=inventory_hostname) }}' nextcloud__on_calendar_jobs: '*:0/5' # every 5 minutes nextcloud__on_calendar_scan_files: '*:50:15' # every hour at hh:50:15 -# The php role owns the FPM service name (php-fpm on RedHat, php-fpm on Debian); source it from there instead of duplicating it per OS. nextcloud__php_fpm_service_name: '{{ php__fpm_service_name }}' nextcloud__skip_apps: false diff --git a/roles/nextcloud/meta/argument_specs.yml b/roles/nextcloud/meta/argument_specs.yml index b8bd799cb..e95248558 100644 --- a/roles/nextcloud/meta/argument_specs.yml +++ b/roles/nextcloud/meta/argument_specs.yml @@ -90,6 +90,12 @@ argument_specs: required: false description: 'The URL of the Icinga2 API used to set a downtime during a server update.' + nextcloud__mailto_root__to: + type: 'list' + elements: 'str' + required: false + description: 'Recipients of the ldap-show-remnants report. Defaults to the global mailto_root__to; empty means stdout only.' + nextcloud__on_calendar_app_update: type: 'str' required: false diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 index 0479167c7..49f14258c 100644 --- a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 +++ b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 @@ -1,6 +1,6 @@ #!/usr/bin/env bash # {{ ansible_managed }} -# 2026042001 +# 2026060801 output=$(/usr/bin/php /var/www/html/nextcloud/occ ldap:show-remnants) if [ -z "$output" ]; then @@ -8,11 +8,10 @@ if [ -z "$output" ]; then exit 0 fi -{% if setup_basic__skip_mailto_root %} -echo "$output" -exit 0 +{% if nextcloud__mailto_root__to | length > 0 %} +# only mail the output if recipients are configured +echo "$output" | /usr/bin/mail -s "$(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" {{ nextcloud__mailto_root__to | join(' ') }} {% else %} -# only send output if there is any -echo "$output" | /usr/bin/mail -s "$(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" {{ mailto_root__to | join(' ') }} -exit 0 +echo "$output" {% endif %} +exit 0 From 56a100f844fc3afef9ce6ebb8162c8ad58f16e11 Mon Sep 17 00:00:00 2001 From: Danyal Berchtold Date: Mon, 8 Jun 2026 10:45:43 +0200 Subject: [PATCH 05/14] docs(roles/nextcloud): correct app-update timer default in README --- roles/nextcloud/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index 7c63cfa05..957cb2ddb 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -14,7 +14,7 @@ After installing Nextcloud, head over to your http(s)://nextcloud/index.php/sett ## How the Role Behaves -* App updates are applied automatically by the `nextcloud-app-update.timer` (disabled by default, enable via `nextcloud__timer_app_update_enabled`). The timer runs `/usr/local/bin/nextcloud-app-update`, which first checks whether any app update is pending. Nextcloud is switched into maintenance mode only when there is something to update; when everything is up to date the instance keeps serving requests untouched. After updating, the recommended database migrations (`db:add-missing-indices`, `db:add-missing-columns`, `db:add-missing-primary-keys`) are applied. A failed run leaves maintenance mode disabled again, so the instance does not stay offline, and reports the failure to systemd. +* App updates are applied automatically by the `nextcloud-app-update.timer` (enabled by default, disable via `nextcloud__timer_app_update_enabled`). The timer runs `/usr/local/bin/nextcloud-app-update`, which first checks whether any app update is pending. Nextcloud is switched into maintenance mode only when there is something to update; when everything is up to date the instance keeps serving requests untouched. After updating, the recommended database migrations (`db:add-missing-indices`, `db:add-missing-columns`, `db:add-missing-primary-keys`) are applied. A failed run leaves maintenance mode disabled again, so the instance does not stay offline, and reports the failure to systemd. * This automatic update covers app updates only. Updating the Nextcloud server itself is a separate, manual step via `/usr/local/bin/nextcloud-update`. From 0572e69785df8ed8fb0dd984fa8a69c258cc750f Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 10:50:03 +0200 Subject: [PATCH 06/14] fix(roles/php): tag platform vars and version discovery 'always' The platform-variables import and the PHP version discovery block were gated behind php-specific tags. A consuming role run under its own tag selection (e.g. `setup_nextcloud --tags nextcloud:scripts`) filters out every php block, leaving php__fpm_service_name and php__installed_version undefined. The php:state block referencing php__fpm_service_name therefore already failed on Debian/Ubuntu with `--tags php:state`. Tag both as 'always' so these prerequisite variables are defined whenever the role is in the play, regardless of the active --tags selection. --- CHANGELOG.md | 1 + roles/php/tasks/main.yml | 15 +++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd06aa6e0..bb8f62bd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -162,6 +162,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* **role:php**: Running the role with a specific tag (for example `--tags php:state`) on Debian and Ubuntu no longer fails with an undefined PHP version. Roles that build on php and only restart php-fpm (such as nextcloud) now also work when run with their own tags. * **role:nextcloud**: The `nextcloud-ldap-show-remnants` script no longer aborts the `nextcloud:cron` deploy with `'setup_basic__skip_mailto_root' is undefined` when the role runs outside the `setup_basic` playbook (e.g. via `--tags nextcloud:cron` in `setup_nextcloud`). The report recipients now come from the new role variable `nextcloud__mailto_root__to` (defaulting to the global `mailto_root__to`); the report is mailed when recipients are set and printed to stdout otherwise. * **role:repo_elasticsearch, role:repo_grafana, role:repo_graylog, role:repo_icinga, role:repo_influxdb, role:repo_mariadb, role:repo_mongodb, role:repo_monitoring_plugins, role:repo_mydumper, role:repo_opensearch, role:repo_proxysql, role:repo_redis, role:repo_sury**: Refreshing the apt cache is no longer reported as a change on every run. * **role:repo_remi**: Enabling the php, composer and Redis module streams is now idempotent. Repeated runs no longer report a change or briefly disable and re-enable the stream on every run. diff --git a/roles/php/tasks/main.yml b/roles/php/tasks/main.yml index 8642121a9..7a8782ea0 100644 --- a/roles/php/tasks/main.yml +++ b/roles/php/tasks/main.yml @@ -3,12 +3,7 @@ name: 'shared' tasks_from: 'platform-variables.yml' tags: - - 'php' - - 'php:fpm' - - 'php:ini' - - 'php:modules' - - 'php:state' - - 'php:update' + - 'always' - block: @@ -51,11 +46,11 @@ ansible.builtin.set_fact: php__installed_version: '{{ ansible_facts["packages"]["php"][0]["version"] | regex_search("\d\.\d") }}' + # 'always': php__installed_version / php__fpm_service_name are prerequisites referenced by + # the php:state block and by consuming roles (e.g. nextcloud) under their own --tags runs, + # where the php-specific blocks are filtered out. Tagging the discovery 'always' keeps them defined. tags: - - 'php' - - 'php:ini' - - 'php:modules' - - 'php:update' + - 'always' - block: From fa97e7a5f33f6328958f0340d5af77adf5d35657 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 10:50:17 +0200 Subject: [PATCH 07/14] refactor(roles/nextcloud): make php-fpm service name an internal var nextcloud__php_fpm_service_name only ever mirrored the php role's internal php__fpm_service_name (php-fpm on RHEL, php-fpm on Debian). Exposing it as a user-facing variable offered no useful override, only a way to desync nextcloud from the service the php role actually manages. Move it to vars/main.yml as __nextcloud__php_fpm_service_name and drop it from defaults, the README and the argument_specs note. The variable was introduced on this branch and never released, so no changelog entry is needed. The nextcloud-update template keeps its timestamp: the rename resolves to the same value, so the rendered output is unchanged. --- roles/nextcloud/README.md | 6 ------ roles/nextcloud/defaults/main.yml | 2 -- roles/nextcloud/meta/argument_specs.yml | 1 - roles/nextcloud/tasks/main.yml | 8 ++++---- .../nextcloud/templates/usr/local/bin/nextcloud-update.j2 | 2 +- roles/nextcloud/vars/main.yml | 1 + 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index 957cb2ddb..2a5a1e662 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -249,12 +249,6 @@ nextcloud__users: * Type: String. * Default: `'*:50:15'` -`nextcloud__php_fpm_service_name` - -* Name of the PHP-FPM systemd service that the role restarts (and that the `/usr/local/bin/nextcloud-update` script restarts). Defaults to the `php` role's `php__fpm_service_name` (`php-fpm` on RHEL, `php-fpm` on Debian). -* Type: String. -* Default: `{{ php__fpm_service_name }}` - `nextcloud__skip_apps` * Completely skips the management of Nextcloud apps. Set this to prevent changes via the WebGUI from being overwritten. diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index b58f94b2f..a839fa783 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -180,8 +180,6 @@ nextcloud__on_calendar_app_update: '06,18,23:{{ 59 | random(seed=inventory_hostn nextcloud__on_calendar_jobs: '*:0/5' # every 5 minutes nextcloud__on_calendar_scan_files: '*:50:15' # every hour at hh:50:15 -nextcloud__php_fpm_service_name: '{{ php__fpm_service_name }}' - nextcloud__skip_apps: false nextcloud__skip_notify_push: false diff --git a/roles/nextcloud/meta/argument_specs.yml b/roles/nextcloud/meta/argument_specs.yml index e95248558..421c9a286 100644 --- a/roles/nextcloud/meta/argument_specs.yml +++ b/roles/nextcloud/meta/argument_specs.yml @@ -15,7 +15,6 @@ # * nextcloud__icinga2_api_user_login -> '{{ system_update__icinga2_api_user_login }}' # * nextcloud__icinga2_hostname -> '{{ ansible_facts["nodename"] }}' # * nextcloud__mariadb_login -> '{{ mariadb_server__admin_user }}' -# * nextcloud__php_fpm_service_name -> '{{ php__fpm_service_name }}' argument_specs: main: options: diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index cd617a73b..4887b0125 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -125,9 +125,9 @@ # do this straight after the installation to get NC up and running # otherwise subsequent occ commands might fail - - name: 'restart {{ nextcloud__php_fpm_service_name }}' + - name: 'restart {{ __nextcloud__php_fpm_service_name }}' ansible.builtin.service: - name: '{{ nextcloud__php_fpm_service_name }}' + name: '{{ __nextcloud__php_fpm_service_name }}' state: 'restarted' tags: @@ -175,9 +175,9 @@ become_user: '{{ __shared__apache_httpd_user }}' loop: '{{ nextcloud__app_configs__combined_var }}' - - name: 'restart {{ nextcloud__php_fpm_service_name }}' + - name: 'restart {{ __nextcloud__php_fpm_service_name }}' ansible.builtin.service: - name: '{{ nextcloud__php_fpm_service_name }}' + name: '{{ __nextcloud__php_fpm_service_name }}' state: 'restarted' when: diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 index fd73932ae..36d6545a5 100644 --- a/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 +++ b/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 @@ -13,7 +13,7 @@ trap 'error_handler "${LINENO}"' ERR WEBSERVER_USER="{{ __shared__apache_httpd_user }}" WEBSERVER_GROUP="{{ __shared__apache_httpd_group }}" -PHP_SERVICE_NAME="{{ nextcloud__php_fpm_service_name }}" +PHP_SERVICE_NAME="{{ __nextcloud__php_fpm_service_name }}" NC_DIR="/var/www/html/nextcloud" DATA_DIR=$(sudo -u "${WEBSERVER_USER}" php "${NC_DIR}/occ" config:system:get datadirectory) diff --git a/roles/nextcloud/vars/main.yml b/roles/nextcloud/vars/main.yml index 2d68653bb..98673b841 100644 --- a/roles/nextcloud/vars/main.yml +++ b/roles/nextcloud/vars/main.yml @@ -37,3 +37,4 @@ __nextcloud__php__modules__dependent_var: state: 'present' - name: 'php-zip' state: 'present' +__nextcloud__php_fpm_service_name: '{{ php__fpm_service_name }}' From fa3ffbaa2774364afde7eb9bee526fbe1e30a951 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 10:55:31 +0200 Subject: [PATCH 08/14] refactor(roles/nextcloud): rename mail recipients variable for consistency --- CHANGELOG.md | 2 +- roles/nextcloud/README.md | 2 +- roles/nextcloud/defaults/main.yml | 2 +- roles/nextcloud/meta/argument_specs.yml | 2 +- .../templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb8f62bd5..55cd12e44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,7 +163,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * **role:php**: Running the role with a specific tag (for example `--tags php:state`) on Debian and Ubuntu no longer fails with an undefined PHP version. Roles that build on php and only restart php-fpm (such as nextcloud) now also work when run with their own tags. -* **role:nextcloud**: The `nextcloud-ldap-show-remnants` script no longer aborts the `nextcloud:cron` deploy with `'setup_basic__skip_mailto_root' is undefined` when the role runs outside the `setup_basic` playbook (e.g. via `--tags nextcloud:cron` in `setup_nextcloud`). The report recipients now come from the new role variable `nextcloud__mailto_root__to` (defaulting to the global `mailto_root__to`); the report is mailed when recipients are set and printed to stdout otherwise. +* **role:nextcloud**: The `nextcloud-ldap-show-remnants` script no longer aborts the `nextcloud:cron` deploy with `'setup_basic__skip_mailto_root' is undefined` when the role runs outside the `setup_basic` playbook (e.g. via `--tags nextcloud:cron` in `setup_nextcloud`). The report recipients now come from the new role variable `nextcloud__mail_recipients` (defaulting to the global `mailto_root__to`); the report is mailed when recipients are set and printed to stdout otherwise. * **role:repo_elasticsearch, role:repo_grafana, role:repo_graylog, role:repo_icinga, role:repo_influxdb, role:repo_mariadb, role:repo_mongodb, role:repo_monitoring_plugins, role:repo_mydumper, role:repo_opensearch, role:repo_proxysql, role:repo_redis, role:repo_sury**: Refreshing the apt cache is no longer reported as a change on every run. * **role:repo_remi**: Enabling the php, composer and Redis module streams is now idempotent. Repeated runs no longer report a change or briefly disable and re-enable the stream on every run. * **role:proxysql**: `mysql_servers` entries are now deduplicated by their actual `address` field. The merge key referenced a non-existent `hostname` field, so multiple backends sharing a host group and port were silently collapsed into one. diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index 2a5a1e662..0061722b3 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -219,7 +219,7 @@ nextcloud__users: * Type: String. * Default: `'{{ ansible_facts["nodename"] }}'` -`nextcloud__mailto_root__to` +`nextcloud__mail_recipients` * Recipients of the monthly `ldap:show-remnants` report (users removed from LDAP that still have remnants in Nextcloud) sent by `/usr/local/bin/nextcloud-ldap-show-remnants`. Defaults to the global `mailto_root__to`; when empty the report is printed to stdout instead of being mailed. * Type: List. diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index a839fa783..54f6264ee 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -172,7 +172,7 @@ nextcloud__icinga2_api_url: 'https://{{ icinga2_agent__icinga2_master_host | d(" nextcloud__icinga2_api_user_login: '{{ system_update__icinga2_api_user_login }}' nextcloud__icinga2_hostname: '{{ ansible_facts["nodename"] }}' -nextcloud__mailto_root__to: '{{ mailto_root__to | d([]) }}' +nextcloud__mail_recipients: '{{ mailto_root__to | d([]) }}' nextcloud__mariadb_login: '{{ mariadb_server__admin_user }}' diff --git a/roles/nextcloud/meta/argument_specs.yml b/roles/nextcloud/meta/argument_specs.yml index 421c9a286..0cde44206 100644 --- a/roles/nextcloud/meta/argument_specs.yml +++ b/roles/nextcloud/meta/argument_specs.yml @@ -89,7 +89,7 @@ argument_specs: required: false description: 'The URL of the Icinga2 API used to set a downtime during a server update.' - nextcloud__mailto_root__to: + nextcloud__mail_recipients: type: 'list' elements: 'str' required: false diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 index 49f14258c..b244a1ed1 100644 --- a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 +++ b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 @@ -8,9 +8,9 @@ if [ -z "$output" ]; then exit 0 fi -{% if nextcloud__mailto_root__to | length > 0 %} +{% if nextcloud__mail_recipients | length > 0 %} # only mail the output if recipients are configured -echo "$output" | /usr/bin/mail -s "$(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" {{ nextcloud__mailto_root__to | join(' ') }} +echo "$output" | /usr/bin/mail -s "$(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" {{ nextcloud__mail_recipients | join(' ') }} {% else %} echo "$output" {% endif %} From 3c6a781a9cc6b108276b08d1efe07476951006d7 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 10:57:22 +0200 Subject: [PATCH 09/14] fix(roles/nextcloud): send ldap-show-remnants report via sendmail --- .../usr/local/bin/nextcloud-ldap-show-remnants.j2 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 index b244a1ed1..e77c630d6 100644 --- a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 +++ b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 @@ -1,6 +1,6 @@ #!/usr/bin/env bash # {{ ansible_managed }} -# 2026060801 +# 2026061201 output=$(/usr/bin/php /var/www/html/nextcloud/occ ldap:show-remnants) if [ -z "$output" ]; then @@ -10,7 +10,12 @@ fi {% if nextcloud__mail_recipients | length > 0 %} # only mail the output if recipients are configured -echo "$output" | /usr/bin/mail -s "$(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" {{ nextcloud__mail_recipients | join(' ') }} +{ + echo "To: {{ nextcloud__mail_recipients | join(', ') }}" + echo "Subject: $(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" + echo + echo "$output" +} | /usr/sbin/sendmail -t -i {% else %} echo "$output" {% endif %} From 90fa81db1ae9582bb7210f552fbcd413b8456dcc Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 10:58:35 +0200 Subject: [PATCH 10/14] fix(roles/nextcloud): always print ldap-show-remnants report to stdout --- CHANGELOG.md | 2 +- roles/nextcloud/README.md | 2 +- .../usr/local/bin/nextcloud-ldap-show-remnants.j2 | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55cd12e44..242775e9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,7 +163,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * **role:php**: Running the role with a specific tag (for example `--tags php:state`) on Debian and Ubuntu no longer fails with an undefined PHP version. Roles that build on php and only restart php-fpm (such as nextcloud) now also work when run with their own tags. -* **role:nextcloud**: The `nextcloud-ldap-show-remnants` script no longer aborts the `nextcloud:cron` deploy with `'setup_basic__skip_mailto_root' is undefined` when the role runs outside the `setup_basic` playbook (e.g. via `--tags nextcloud:cron` in `setup_nextcloud`). The report recipients now come from the new role variable `nextcloud__mail_recipients` (defaulting to the global `mailto_root__to`); the report is mailed when recipients are set and printed to stdout otherwise. +* **role:nextcloud**: The `nextcloud-ldap-show-remnants` script no longer aborts the `nextcloud:cron` deploy with `'setup_basic__skip_mailto_root' is undefined` when the role runs outside the `setup_basic` playbook (e.g. via `--tags nextcloud:cron` in `setup_nextcloud`). The report recipients now come from the new role variable `nextcloud__mail_recipients` (defaulting to the global `mailto_root__to`); the report is always printed to stdout and additionally mailed when recipients are set. * **role:repo_elasticsearch, role:repo_grafana, role:repo_graylog, role:repo_icinga, role:repo_influxdb, role:repo_mariadb, role:repo_mongodb, role:repo_monitoring_plugins, role:repo_mydumper, role:repo_opensearch, role:repo_proxysql, role:repo_redis, role:repo_sury**: Refreshing the apt cache is no longer reported as a change on every run. * **role:repo_remi**: Enabling the php, composer and Redis module streams is now idempotent. Repeated runs no longer report a change or briefly disable and re-enable the stream on every run. * **role:proxysql**: `mysql_servers` entries are now deduplicated by their actual `address` field. The merge key referenced a non-existent `hostname` field, so multiple backends sharing a host group and port were silently collapsed into one. diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index 0061722b3..98f311f3c 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -221,7 +221,7 @@ nextcloud__users: `nextcloud__mail_recipients` -* Recipients of the monthly `ldap:show-remnants` report (users removed from LDAP that still have remnants in Nextcloud) sent by `/usr/local/bin/nextcloud-ldap-show-remnants`. Defaults to the global `mailto_root__to`; when empty the report is printed to stdout instead of being mailed. +* Recipients of the monthly `ldap:show-remnants` report (users removed from LDAP that still have remnants in Nextcloud) sent by `/usr/local/bin/nextcloud-ldap-show-remnants`. Defaults to the global `mailto_root__to`. The report is always printed to stdout; when recipients are set it is additionally mailed to them. * Type: List. * Default: `'{{ mailto_root__to | d([]) }}'` diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 index e77c630d6..fca2a5a4c 100644 --- a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 +++ b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 @@ -1,6 +1,6 @@ #!/usr/bin/env bash # {{ ansible_managed }} -# 2026061201 +# 2026061202 output=$(/usr/bin/php /var/www/html/nextcloud/occ ldap:show-remnants) if [ -z "$output" ]; then @@ -8,15 +8,15 @@ if [ -z "$output" ]; then exit 0 fi +echo "$output" + {% if nextcloud__mail_recipients | length > 0 %} -# only mail the output if recipients are configured +# also mail the output if recipients are configured { echo "To: {{ nextcloud__mail_recipients | join(', ') }}" echo "Subject: $(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" echo echo "$output" } | /usr/sbin/sendmail -t -i -{% else %} -echo "$output" {% endif %} exit 0 From fc006459c3544f3063e2f997e8479224fcd94544 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 17:43:47 +0200 Subject: [PATCH 11/14] feat(roles/nextcloud): add Debian and Ubuntu package support Ship the OS-specific package vars for Debian/Ubuntu: vars/Debian.yml (plus the mandated vars/Ubuntu.yml copy) for the base packages, and a Debian key in the vars/main.yml PHP-module dict selected via platform_select. Package names verified on Debian 13 / PHP 8.4 from Sury; COMPATIBILITY.md marks Debian and Ubuntu (x) pending a full end-to-end run. The IMAP extension is handled per PHP version, since it moved out of PHP core in 8.4: RHEL installs php-pecl-imap on >= 8.4 and php-imap on <= 8.3, and Debian installs the versioned php-imap package (the php-imap metapackage there would drag in a second PHP runtime). --- CHANGELOG.md | 3 ++- COMPATIBILITY.md | 2 +- roles/nextcloud/vars/Debian.yml | 5 ++++ roles/nextcloud/vars/Ubuntu.yml | 5 ++++ roles/nextcloud/vars/main.yml | 44 ++++++++++++++++++++++++++++++++- 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 roles/nextcloud/vars/Debian.yml create mode 100644 roles/nextcloud/vars/Ubuntu.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 242775e9d..f5b4cf7a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,7 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -* **role:nextcloud**: Lay the groundwork for non-RHEL platforms (Debian/Ubuntu) by removing hardcoded RHEL-specific names from the role logic, sourcing each OS-specific value through the mechanism that fits its scope. The web server user/group (previously the hardcoded `apache`) now come directly from the LFOps-wide shared values `__shared__apache_httpd_user`/`__shared__apache_httpd_group` (apache on RedHat, www-data on Debian, wwwrun/www on Suse) throughout the tasks, templates and systemd units, rather than through a redundant role-local variable. The base package list (previously the RHEL names `openldap-clients`/`samba-client`) is sourced from the role-local `vars/RedHat.yml` via `shared/platform-variables.yml`, while the PHP-FPM service name (`nextcloud__php_fpm_service_name`) now defaults to the `php` role's own `php__fpm_service_name` instead of a duplicated role-local value, so the Debian `php-fpm` naming comes for free. The PHP module list consumed by the earlier-running `php` role (`nextcloud__php__modules__dependent_var`) is now an OS-keyed dict in `vars/main.yml` selected with the `linuxfabrik.lfops.platform_select` filter, since `vars/.yml` loads too late for that cross-role `__dependent_var` hand-off. These variables are now used throughout the tasks, the deployed systemd services and the notify_push unit (not just the `/usr/local/bin/nextcloud-update` script). The SELinux `restorecon` tasks are now guarded by `ansible_facts["selinux"]["status"] != "disabled"`, and the SELinux blocks in the update script use `ansible_facts["os_family"]` instead of `ansible_os_family`. Only RHEL package names ship so far, so the role still runs on RHEL only (see `COMPATIBILITY.md`); adding a tested `vars/Debian.yml` plus the matching `platform_select` keys is all that is needed to extend support. +* **role:nextcloud**: Adds Debian and Ubuntu support alongside Red Hat-family systems. Package names are verified on Debian 13; the role is marked `(x)` (expected to work, not yet verified end to end) for Debian and Ubuntu in `COMPATIBILITY.md`. SELinux relabeling is now skipped automatically on hosts where SELinux is disabled. * **role:icinga2_master, role:icingadb**: Validate the Icinga 2 configuration before restarting the service. A faulty config now fails the playbook run loudly instead of bouncing the daemon into a broken state and leaving Icinga 2 down. * **role:nextcloud**: Automatic app updates are now enabled by default (`nextcloud__timer_app_update_enabled`). The scheduled app update only switches Nextcloud into maintenance mode when an app update is actually pending, so an instance that is already up to date keeps serving requests without interruption. After updating, the recommended database migrations are applied automatically. A failed run no longer leaves the instance stuck in maintenance mode. * **role:clamav**: Now runs on Debian and Ubuntu in addition to Red Hat-family systems, and works on RHEL 10. The role seeds the signature database on first install so the scanner starts reliably, and runs an EICAR self-test (also available on its own via the `clamav:test` tag) that confirms detection actually works. @@ -162,6 +162,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* **role:nextcloud**: The IMAP PHP extension now installs on current PHP. On PHP 8.4 and newer IMAP was removed from PHP core, so the role installs it from the PECL package instead, where previously the install aborted because the old `php-imap` package no longer exists for that PHP version. * **role:php**: Running the role with a specific tag (for example `--tags php:state`) on Debian and Ubuntu no longer fails with an undefined PHP version. Roles that build on php and only restart php-fpm (such as nextcloud) now also work when run with their own tags. * **role:nextcloud**: The `nextcloud-ldap-show-remnants` script no longer aborts the `nextcloud:cron` deploy with `'setup_basic__skip_mailto_root' is undefined` when the role runs outside the `setup_basic` playbook (e.g. via `--tags nextcloud:cron` in `setup_nextcloud`). The report recipients now come from the new role variable `nextcloud__mail_recipients` (defaulting to the global `mailto_root__to`); the report is always printed to stdout and additionally mailed when recipients are set. * **role:repo_elasticsearch, role:repo_grafana, role:repo_graylog, role:repo_icinga, role:repo_influxdb, role:repo_mariadb, role:repo_mongodb, role:repo_monitoring_plugins, role:repo_mydumper, role:repo_opensearch, role:repo_proxysql, role:repo_redis, role:repo_sury**: Refreshing the apt cache is no longer reported as a change on every run. diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index b01dd2190..d616a49d9 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -105,7 +105,7 @@ Which Ansible role is proven to run on which OS? | motd | (x) | (x) | x | x | x | (x) | (x) | (x) | | | mount | (x) | (x) | x | x | (x) | (x) | (x) | (x) | | | network | - | - | x | x | x | | | | | -| nextcloud | | | x | x | (x) | | | | | +| nextcloud | (x) | (x) | x | x | (x) | (x) | (x) | (x) | | | nfs_client | (x) | (x) | x | x | (x) | (x) | (x) | (x) | | | nfs_server | (x) | (x) | x | (x) | (x) | (x) | (x) | (x) | | | nodejs | (x) | (x) | x | x | (x) | (x) | (x) | (x) | | diff --git a/roles/nextcloud/vars/Debian.yml b/roles/nextcloud/vars/Debian.yml new file mode 100644 index 000000000..49d2a9df2 --- /dev/null +++ b/roles/nextcloud/vars/Debian.yml @@ -0,0 +1,5 @@ +__nextcloud__required_packages: + - 'bzip2' + - 'jq' + - 'ldap-utils' + - 'smbclient' diff --git a/roles/nextcloud/vars/Ubuntu.yml b/roles/nextcloud/vars/Ubuntu.yml new file mode 100644 index 000000000..49d2a9df2 --- /dev/null +++ b/roles/nextcloud/vars/Ubuntu.yml @@ -0,0 +1,5 @@ +__nextcloud__required_packages: + - 'bzip2' + - 'jq' + - 'ldap-utils' + - 'smbclient' diff --git a/roles/nextcloud/vars/main.yml b/roles/nextcloud/vars/main.yml index 98673b841..89042aba5 100644 --- a/roles/nextcloud/vars/main.yml +++ b/roles/nextcloud/vars/main.yml @@ -9,7 +9,9 @@ __nextcloud__php__modules__dependent_var: state: 'present' - name: 'php-gmp' state: 'present' - - name: 'php-imap' + # IMAP was removed from PHP core in 8.4 and ships as the PECL package php-pecl-imap there; on + # <= 8.3 it is still the core php-imap. Pick the name from the PHP version the php role detected. + - name: '{{ "php-pecl-imap" if php__installed_version is version("8.4", ">=") else "php-imap" }}' state: 'present' - name: 'php-imagick' state: 'present' @@ -37,4 +39,44 @@ __nextcloud__php__modules__dependent_var: state: 'present' - name: 'php-zip' state: 'present' + # Debian/Ubuntu install PHP from Sury (php-* packages, pulled in via the php- + # metapackages). Verified against Debian 13 / PHP 8.4. Differences from the RedHat list: + # * php-mysqlnd -> php-mysql, php-pecl-apcu -> php-apcu (different upstream package names) + # * php-imap -> php-imap: the php-imap metapackage pins php8.3-imap (IMAP is + # unbundled from PHP 8.4), which would drag a second PHP runtime onto the host; + # the versioned package is the clean per-version build (core on <= 8.3, PECL on >= 8.4) + # * php-opcache dropped: php-opcache is pulled in by the php base package already + # * php-process dropped: the posix extension is built into the Debian php-cli/-common packages + # * php-json dropped: JSON is compiled into PHP 8 core (php-json is only a transitional dummy) + Debian: + - name: 'php-apcu' + state: 'present' + - name: 'php-bcmath' + state: 'present' + - name: 'php-gd' + state: 'present' + - name: 'php-gmp' + state: 'present' + - name: 'php-imagick' + state: 'present' + # Versioned PECL/core package; Sury names it uniformly across PHP versions, so unlike the + # php-imap metapackage it does not drag a second PHP runtime onto the host. + - name: 'php{{ php__installed_version }}-imap' + state: 'present' + - name: 'php-intl' + state: 'present' + - name: 'php-ldap' + state: 'present' + - name: 'php-mbstring' + state: 'present' + - name: 'php-memcached' + state: 'present' + - name: 'php-mysql' + state: 'present' + - name: 'php-redis' + state: 'present' + - name: 'php-smbclient' + state: 'present' + - name: 'php-zip' + state: 'present' __nextcloud__php_fpm_service_name: '{{ php__fpm_service_name }}' From 8252dcd47fef273cdc53b94dab39212ea3142695 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 17:44:06 +0200 Subject: [PATCH 12/14] fix(roles/nextcloud): type text/workspace_available as boolean Current Nextcloud ships a config lexicon that types text/workspace_available as BOOL and rejects config:app:set with --type=string. Set the value as a boolean so the app config applies instead of aborting the run. --- CHANGELOG.md | 1 + roles/nextcloud/defaults/main.yml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5b4cf7a6..e3c60b451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -162,6 +162,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* **role:nextcloud**: App configuration no longer aborts on current Nextcloud. The `text` app's `workspace_available` setting is now applied with the boolean type the Nextcloud config lexicon requires, instead of the string type that newer Nextcloud rejects. * **role:nextcloud**: The IMAP PHP extension now installs on current PHP. On PHP 8.4 and newer IMAP was removed from PHP core, so the role installs it from the PECL package instead, where previously the install aborted because the old `php-imap` package no longer exists for that PHP version. * **role:php**: Running the role with a specific tag (for example `--tags php:state`) on Debian and Ubuntu no longer fails with an undefined PHP version. Roles that build on php and only restart php-fpm (such as nextcloud) now also work when run with their own tags. * **role:nextcloud**: The `nextcloud-ldap-show-remnants` script no longer aborts the `nextcloud:cron` deploy with `'setup_basic__skip_mailto_root' is undefined` when the role runs outside the `setup_basic` playbook (e.g. via `--tags nextcloud:cron` in `setup_nextcloud`). The report recipients now come from the new role variable `nextcloud__mail_recipients` (defaulting to the global `mailto_root__to`); the report is always printed to stdout and additionally mailed when recipients are set. diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index 54f6264ee..37ef7550f 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -34,8 +34,8 @@ nextcloud__app_configs__role_var: type: 'boolean' state: 'present' - key: 'text workspace_available' - value: 0 - type: 'string' # for some reason this has to be string, not integer or boolean + value: false + type: 'boolean' state: 'present' - key: 'theming imprintUrl' value: 'https://www.linuxfabrik.ch/ueber-uns/agb/' From 1c8575ff11ff3edd33b02f32a3de6c82b0b30dff Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 17:44:17 +0200 Subject: [PATCH 13/14] test(playbooks/setup_nextcloud): add molecule scenario Add a setup_nextcloud scenario that converges the full stack on the Red Hat-family targets and verifies the observable end state: occ reports the instance installed (CLI), and a status.php request routed to the vhost proves the Apache -> mod_proxy_fcgi -> PHP-FPM -> Nextcloud path actually serves. --- .../molecule/setup_nextcloud/converge.yml | 2 + .../group_vars/systems_under_test.yml | 21 ++++++++++ .../setup_nextcloud/inventory/hosts.yml | 15 ++++++++ .../molecule/setup_nextcloud/molecule.yml | 1 + .../molecule/setup_nextcloud/verify.yml | 38 +++++++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 extensions/molecule/setup_nextcloud/converge.yml create mode 100644 extensions/molecule/setup_nextcloud/inventory/group_vars/systems_under_test.yml create mode 100644 extensions/molecule/setup_nextcloud/inventory/hosts.yml create mode 100644 extensions/molecule/setup_nextcloud/molecule.yml create mode 100644 extensions/molecule/setup_nextcloud/verify.yml diff --git a/extensions/molecule/setup_nextcloud/converge.yml b/extensions/molecule/setup_nextcloud/converge.yml new file mode 100644 index 000000000..7987d0df3 --- /dev/null +++ b/extensions/molecule/setup_nextcloud/converge.yml @@ -0,0 +1,2 @@ +- name: 'Converge setup_nextcloud playbook' + ansible.builtin.import_playbook: 'linuxfabrik.lfops.setup_nextcloud' diff --git a/extensions/molecule/setup_nextcloud/inventory/group_vars/systems_under_test.yml b/extensions/molecule/setup_nextcloud/inventory/group_vars/systems_under_test.yml new file mode 100644 index 000000000..5252bd573 --- /dev/null +++ b/extensions/molecule/setup_nextcloud/inventory/group_vars/systems_under_test.yml @@ -0,0 +1,21 @@ +apache_httpd__conf_server_admin: 'root@localhost' + +coturn__denied_peer_ip: [] +coturn__realm: 'nextcloud.example.com' +coturn__static_auth_secret: 'linuxfabrik' + +mariadb_server__admin_user: + username: 'mariadb-admin' + password: 'linuxfabrik' + +nextcloud__fqdn: 'nextcloud.example.com' +nextcloud__skip_notify_push: true +nextcloud__users: + - username: 'nextcloud-admin' + password: 'linuxfabrik' + group: 'admin' + +repo_mariadb__version: '11.4' + +repo_remi__enabled_php_version: '8.5' +repo_remi__enabled_redis_version: '8.8' diff --git a/extensions/molecule/setup_nextcloud/inventory/hosts.yml b/extensions/molecule/setup_nextcloud/inventory/hosts.yml new file mode 100644 index 000000000..c88cf7830 --- /dev/null +++ b/extensions/molecule/setup_nextcloud/inventory/hosts.yml @@ -0,0 +1,15 @@ +# yamllint disable rule:empty-values + +# setup_nextcloud targets 'lfops_setup_nextcloud' (see playbooks/setup_nextcloud.yml: hosts). +# Only Red Hat-family hosts run: the playbook installs PHP from Remi (repo_remi, RedHat-gated) +# and has no repo_sury step, so it is not yet Debian-complete. nextcloud is proven (x/(x)) on +# RHEL in COMPATIBILITY.md; Debian/Ubuntu stay at (x) until the playbook grows a Sury path. +lfops_setup_nextcloud: + children: + systems_under_test: + +systems_under_test: + hosts: + rocky8-vm: + rocky9-vm: + rocky10-vm: diff --git a/extensions/molecule/setup_nextcloud/molecule.yml b/extensions/molecule/setup_nextcloud/molecule.yml new file mode 100644 index 000000000..1e47cbff8 --- /dev/null +++ b/extensions/molecule/setup_nextcloud/molecule.yml @@ -0,0 +1 @@ +# Molecule scenario marker diff --git a/extensions/molecule/setup_nextcloud/verify.yml b/extensions/molecule/setup_nextcloud/verify.yml new file mode 100644 index 000000000..0c1ebd7d3 --- /dev/null +++ b/extensions/molecule/setup_nextcloud/verify.yml @@ -0,0 +1,38 @@ +# verify.yml runs after converge and again after the idempotence step. It asserts the observable +# end state rather than "package installed": Nextcloud answers occ as installed (CLI side), and it +# actually serves over HTTP. `occ status` only returns installed=true once the database, config.php +# and the web server user line up; requesting status.php then proves the Apache -> mod_proxy_fcgi -> +# PHP-FPM -> Nextcloud request path works end to end, not just that the FPM unit is running. +- name: 'Verify Nextcloud is installed and serving' + hosts: 'systems_under_test' + gather_facts: false + become: true + tasks: + + - name: 'php occ status --output=json (as the web server user)' + ansible.builtin.command: 'php /var/www/html/nextcloud/occ status --output=json' + args: + chdir: '/var/www/html/nextcloud/' + become_user: 'apache' + register: '__molecule__nextcloud_status_result' + changed_when: false + + - name: 'Assert Nextcloud reports itself installed' + ansible.builtin.assert: + that: + - '(__molecule__nextcloud_status_result["stdout"] | from_json)["installed"] | bool' + fail_msg: 'Nextcloud is not installed. occ status: {{ __molecule__nextcloud_status_result["stdout"] }}' + + - name: 'GET status.php through Apache and PHP-FPM (Host: {{ nextcloud__fqdn }})' + ansible.builtin.uri: + url: 'http://localhost/status.php' + headers: + Host: '{{ nextcloud__fqdn }}' + return_content: true + register: '__molecule__nextcloud_statusphp_result' + + - name: 'Assert Nextcloud serves status.php as installed' + ansible.builtin.assert: + that: + - '(__molecule__nextcloud_statusphp_result["content"] | from_json)["installed"] | bool' + fail_msg: 'status.php did not report installed. response: {{ __molecule__nextcloud_statusphp_result["content"] | d("") }}' From 4af862594fb0b57ad3c5b44ebe1c777fda8d52b9 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Fri, 12 Jun 2026 17:59:41 +0200 Subject: [PATCH 14/14] refactor(roles/nextcloud): align ldap-show-remnants sendmail with the repo pattern --- roles/nextcloud/README.md | 6 ++++++ roles/nextcloud/defaults/main.yml | 1 + roles/nextcloud/meta/argument_specs.yml | 1 + .../usr/local/bin/nextcloud-ldap-show-remnants.j2 | 14 ++++++++------ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index 98f311f3c..14ec40278 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -219,6 +219,12 @@ nextcloud__users: * Type: String. * Default: `'{{ ansible_facts["nodename"] }}'` +`nextcloud__mail_from` + +* Sender used in the `From:` header and as the envelope sender (`sendmail -f`) of the monthly `ldap:show-remnants` report. Defaults to the global `mailto_root__from`. +* Type: String. +* Default: `'{{ mailto_root__from }}'` + `nextcloud__mail_recipients` * Recipients of the monthly `ldap:show-remnants` report (users removed from LDAP that still have remnants in Nextcloud) sent by `/usr/local/bin/nextcloud-ldap-show-remnants`. Defaults to the global `mailto_root__to`. The report is always printed to stdout; when recipients are set it is additionally mailed to them. diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index 37ef7550f..24c924d44 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -172,6 +172,7 @@ nextcloud__icinga2_api_url: 'https://{{ icinga2_agent__icinga2_master_host | d(" nextcloud__icinga2_api_user_login: '{{ system_update__icinga2_api_user_login }}' nextcloud__icinga2_hostname: '{{ ansible_facts["nodename"] }}' +nextcloud__mail_from: '{{ mailto_root__from }}' nextcloud__mail_recipients: '{{ mailto_root__to | d([]) }}' nextcloud__mariadb_login: '{{ mariadb_server__admin_user }}' diff --git a/roles/nextcloud/meta/argument_specs.yml b/roles/nextcloud/meta/argument_specs.yml index 0cde44206..5f9c9552a 100644 --- a/roles/nextcloud/meta/argument_specs.yml +++ b/roles/nextcloud/meta/argument_specs.yml @@ -14,6 +14,7 @@ # never touch the feature, so they are validated in the tasks where they are used instead: # * nextcloud__icinga2_api_user_login -> '{{ system_update__icinga2_api_user_login }}' # * nextcloud__icinga2_hostname -> '{{ ansible_facts["nodename"] }}' +# * nextcloud__mail_from -> '{{ mailto_root__from }}' # * nextcloud__mariadb_login -> '{{ mariadb_server__admin_user }}' argument_specs: main: diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 index fca2a5a4c..a66af560b 100644 --- a/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 +++ b/roles/nextcloud/templates/usr/local/bin/nextcloud-ldap-show-remnants.j2 @@ -1,6 +1,6 @@ #!/usr/bin/env bash # {{ ansible_managed }} -# 2026061202 +# 2026061203 output=$(/usr/bin/php /var/www/html/nextcloud/occ ldap:show-remnants) if [ -z "$output" ]; then @@ -13,10 +13,12 @@ echo "$output" {% if nextcloud__mail_recipients | length > 0 %} # also mail the output if recipients are configured { - echo "To: {{ nextcloud__mail_recipients | join(', ') }}" - echo "Subject: $(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" - echo - echo "$output" -} | /usr/sbin/sendmail -t -i + printf 'From: %s\n' '{{ nextcloud__mail_from }}' + printf 'To: %s\n' '{{ nextcloud__mail_recipients | join(", ") }}' + printf 'Subject: %s\n' "$(hostname --short) - Users not available on LDAP anymore, but have remnants in Nextcloud" + printf 'Content-Type: text/plain; charset="utf-8"\n' + printf 'Content-Transfer-Encoding: 8bit\n' + printf '\n%s\n' "$output" +} | /usr/sbin/sendmail -oi -t -f '{{ nextcloud__mail_from }}' {% endif %} exit 0