diff --git a/CHANGELOG.md b/CHANGELOG.md index f81c08d0..e3c60b45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* **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. @@ -161,6 +162,10 @@ 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. * **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/COMPATIBILITY.md b/COMPATIBILITY.md index b01dd219..d616a49d 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/extensions/molecule/setup_nextcloud/converge.yml b/extensions/molecule/setup_nextcloud/converge.yml new file mode 100644 index 00000000..7987d0df --- /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 00000000..5252bd57 --- /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 00000000..c88cf783 --- /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 00000000..1e47cbff --- /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 00000000..0c1ebd7d --- /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("") }}' diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index 0024d944..14ec4027 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -14,7 +14,8 @@ 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` (managed 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`. +* 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`. ## Dependent Roles @@ -218,6 +219,18 @@ 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. +* 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 5dca7306..24c924d4 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/' @@ -172,11 +172,15 @@ 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 }}' 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__skip_apps: false nextcloud__skip_notify_push: false @@ -471,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' @@ -546,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=apache + 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 0e116345..5f9c9552 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: @@ -89,6 +90,12 @@ argument_specs: required: false description: 'The URL of the Icinga2 API used to set a downtime during a server update.' + nextcloud__mail_recipients: + 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/tasks/create-user.yml b/roles/nextcloud/tasks/create-user.yml index 1a9680b9..6129a8cc 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 {{ __shared__apache_httpd_user }} php occ user:add --password-from-env --group {{ ncuser["group"] | d('""') | quote }} {{ ncuser["username"] | quote }} @@ -16,7 +16,7 @@ - name: 'Update Nextcloud settings for user {{ ncuser["username"] }}' ansible.builtin.command: | - sudo -u apache 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 46990d3c..4887b012 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__required_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: '{{ __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 apache:apache /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: 'apache' - group: 'apache' + owner: '{{ __shared__apache_httpd_user }}' + group: '{{ __shared__apache_httpd_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 {{ __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: 'apache' - group: 'apache' + owner: '{{ __shared__apache_httpd_user }}' + group: '{{ __shared__apache_httpd_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: '{{ __shared__apache_httpd_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: '{{ __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 @@ -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: '{{ __shared__apache_httpd_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: '{{ __shared__apache_httpd_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: '{{ __shared__apache_httpd_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: '{{ __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: 'apache' + become_user: '{{ __shared__apache_httpd_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: '{{ __shared__apache_httpd_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: '{{ __shared__apache_httpd_user }}' when: - 'not nextcloud__skip_notify_push' @@ -221,11 +232,11 @@ - block: - - name: 'chown -R apache:apache /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: 'apache' - group: 'apache' + owner: '{{ __shared__apache_httpd_user }}' + group: '{{ __shared__apache_httpd_group }}' recurse: true tags: @@ -260,7 +271,7 @@ args: chdir: '/var/www/html/nextcloud/' become: true - become_user: 'apache' + become_user: '{{ __shared__apache_httpd_user }}' # changed_when: there is no easy way to check for changes - name: 'Deploy /usr/local/bin/nextcloud-app-update' 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 907f8b76..796c6eae 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/local/bin/nextcloud-app-update WorkingDirectory=/var/www/html/nextcloud Type=oneshot -User=apache +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 668db63b..15b2d477 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={{ __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 3e69a47d..4eb81d05 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={{ __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 f5b66f9e..cc06b147 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={{ __shared__apache_httpd_user }} 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 0479167c..a66af560 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 +# 2026061203 output=$(/usr/bin/php /var/www/html/nextcloud/occ ldap:show-remnants) if [ -z "$output" ]; then @@ -8,11 +8,17 @@ if [ -z "$output" ]; then exit 0 fi -{% if setup_basic__skip_mailto_root %} echo "$output" -exit 0 -{% 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 + +{% if nextcloud__mail_recipients | length > 0 %} +# also mail the output if recipients are configured +{ + 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 diff --git a/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 b/roles/nextcloud/templates/usr/local/bin/nextcloud-update.j2 index 953c6224..36d6545a 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="{{ __shared__apache_httpd_user }}" +WEBSERVER_GROUP="{{ __shared__apache_httpd_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/Debian.yml b/roles/nextcloud/vars/Debian.yml new file mode 100644 index 00000000..49d2a9df --- /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/RedHat.yml b/roles/nextcloud/vars/RedHat.yml new file mode 100644 index 00000000..cef93f11 --- /dev/null +++ b/roles/nextcloud/vars/RedHat.yml @@ -0,0 +1,5 @@ +__nextcloud__required_packages: + - 'bzip2' + - 'jq' + - 'openldap-clients' + - 'samba-client' diff --git a/roles/nextcloud/vars/Ubuntu.yml b/roles/nextcloud/vars/Ubuntu.yml new file mode 100644 index 00000000..49d2a9df --- /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 new file mode 100644 index 00000000..89042aba --- /dev/null +++ b/roles/nextcloud/vars/main.yml @@ -0,0 +1,82 @@ +# 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' + # 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' + - 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' + # 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 }}' diff --git a/roles/php/tasks/main.yml b/roles/php/tasks/main.yml index 8642121a..7a8782ea 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: