Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dcc01ec
refactor(roles/nextcloud): parameterize OS-specific names for multi-O…
danyalberchtoldlf May 20, 2026
4b0838e
refactor(roles/nextcloud): add argument_specs and align internal var …
danyalberchtoldlf May 20, 2026
ef7cf3c
refactor(roles/nextcloud): source OS-specific names from shared and p…
danyalberchtoldlf Jun 4, 2026
666b2ee
chore: merge origin/main into feat/nextcloud-os-handling
danyalberchtoldlf Jun 8, 2026
6017ba5
fix(roles/nextcloud): source ldap-show-remnants recipients from nextc…
danyalberchtoldlf Jun 8, 2026
56a100f
docs(roles/nextcloud): correct app-update timer default in README
danyalberchtoldlf Jun 8, 2026
c6c55c5
Merge remote-tracking branch 'origin/main' into feat/nextcloud-os-han…
NavidSassan Jun 12, 2026
0572e69
fix(roles/php): tag platform vars and version discovery 'always'
NavidSassan Jun 12, 2026
fa97e7a
refactor(roles/nextcloud): make php-fpm service name an internal var
NavidSassan Jun 12, 2026
fa3ffba
refactor(roles/nextcloud): rename mail recipients variable for consis…
NavidSassan Jun 12, 2026
3c6a781
fix(roles/nextcloud): send ldap-show-remnants report via sendmail
NavidSassan Jun 12, 2026
90fa81d
fix(roles/nextcloud): always print ldap-show-remnants report to stdout
NavidSassan Jun 12, 2026
fc00645
feat(roles/nextcloud): add Debian and Ubuntu package support
NavidSassan Jun 12, 2026
8252dcd
fix(roles/nextcloud): type text/workspace_available as boolean
NavidSassan Jun 12, 2026
1c8575f
test(playbooks/setup_nextcloud): add molecule scenario
NavidSassan Jun 12, 2026
4af8625
refactor(roles/nextcloud): align ldap-show-remnants sendmail with the…
NavidSassan Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) | |
Expand Down
2 changes: 2 additions & 0 deletions extensions/molecule/setup_nextcloud/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- name: 'Converge setup_nextcloud playbook'
ansible.builtin.import_playbook: 'linuxfabrik.lfops.setup_nextcloud'
Original file line number Diff line number Diff line change
@@ -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'
15 changes: 15 additions & 0 deletions extensions/molecule/setup_nextcloud/inventory/hosts.yml
Original file line number Diff line number Diff line change
@@ -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:
1 change: 1 addition & 0 deletions extensions/molecule/setup_nextcloud/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Molecule scenario marker
38 changes: 38 additions & 0 deletions extensions/molecule/setup_nextcloud/verify.yml
Original file line number Diff line number Diff line change
@@ -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("") }}'
15 changes: 14 additions & 1 deletion roles/nextcloud/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
50 changes: 12 additions & 38 deletions roles/nextcloud/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/'
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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'
7 changes: 7 additions & 0 deletions roles/nextcloud/meta/argument_specs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions roles/nextcloud/tasks/create-user.yml
Original file line number Diff line number Diff line change
@@ -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 }}
Expand All @@ -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
Expand Down
Loading