The role created ~/podman-data/nextcloud/redis-session.ini and the unit bind-mounted it over the image path, purely because the entrypoint cannot write there itself. Removed: the task, the mount, and -- because it is the only thing that triggers the write -- REDIS_HOST from the environment file. The entrypoint now skips configure_redis_session entirely and starts. The Redis attach stays. The valkey role still puts nextcloud_net on the cache container with the alias `redis`, so an instance whose config.php names that host reaches it. Writing that configuration is config.php's business, and the readme gives the three `occ config:system:set` lines for anyone who wants it, including that this playbook will not do it for them. The only thing lost is PHP session storage, which falls back to the container's own filesystem -- a redeploy logs users out. That is the trade for not shipping a file whose sole purpose is to work around a directory the image owns as root. The env template and the valkey role comment both say why REDIS_HOST is absent, so that adding it back is recognisable as the regression it is.
108 lines
4.2 KiB
YAML
108 lines
4.2 KiB
YAML
---
|
|
# Nextcloud as a rootless Podman Quadlet unit.
|
|
#
|
|
# Everything runs as the connection user; requires gather_facts (set in
|
|
# playbook.yml), which is where podman_user and the home directory come from.
|
|
#
|
|
# A fresh webroot is a valid state. The image initialises the code and serves the
|
|
# setup wizard, and this play succeeds. Set nextcloud_admin_user and
|
|
# nextcloud_admin_password in vars/default.yml to have it installed unattended
|
|
# instead.
|
|
|
|
- name: Ensure the webroot exists
|
|
ansible.builtin.file:
|
|
path: "{{ host_directory }}"
|
|
state: directory
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0755'
|
|
|
|
# Read by the entrypoint, not by Ansible, so it has to exist before the unit
|
|
# starts. Mode 0600: POSTGRES_PASSWORD is in it, and the unit file is
|
|
# world-readable.
|
|
- name: Write the environment file
|
|
ansible.builtin.template:
|
|
src: nextcloud.env.j2
|
|
dest: "{{ env_file }}"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0600'
|
|
notify: Restart Nextcloud
|
|
|
|
- name: Install the Quadlet unit
|
|
ansible.builtin.template:
|
|
src: nextcloud.container.j2
|
|
dest: "{{ quadlet_dir }}/{{ container_name }}.container"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0644'
|
|
notify:
|
|
- Reload systemd user units
|
|
- Restart Nextcloud
|
|
|
|
# Quadlet applies [Install] WantedBy= when it generates the unit, so a generated
|
|
# unit is never `systemctl enable`d.
|
|
- name: Start Nextcloud and wait for it to serve
|
|
block:
|
|
- name: Start Nextcloud
|
|
ansible.builtin.systemd_service:
|
|
name: "{{ unit_name }}"
|
|
scope: user
|
|
state: started
|
|
daemon_reload: true
|
|
|
|
# `systemctl start` returns while the unit is still `activating`, and a
|
|
# crash-looping container is `activating` most of the time as well, so
|
|
# is-active proves nothing. status.php is Nextcloud's own endpoint: it answers
|
|
# only once the entrypoint has finished (including any `occ upgrade`) and
|
|
# Apache is up, it reports `installed` either way — so one gate fits both a
|
|
# fresh webroot and an update — and `maintenance` is in it too, because a
|
|
# maintenance-mode 503 on every page is otherwise invisible to a probe.
|
|
- name: Wait for Nextcloud to serve status.php
|
|
ansible.builtin.command: >-
|
|
podman exec {{ container_name }} php -r
|
|
'$b=@file_get_contents("http://127.0.0.1/status.php"); if ($b === false) exit(1);
|
|
$j=json_decode($b, true); if (!is_array($j) || !array_key_exists("installed", $j)) exit(1);
|
|
exit(empty($j["maintenance"]) ? 0 : 1);'
|
|
register: nextcloud_serving
|
|
until: nextcloud_serving.rc == 0
|
|
retries: 60
|
|
delay: 5
|
|
changed_when: false
|
|
rescue:
|
|
# systemd reports "control process exited with error code" and nothing else.
|
|
# The reason is in the entrypoint's own output.
|
|
- name: Collect diagnostics
|
|
ansible.builtin.command: "{{ item }}"
|
|
loop:
|
|
- "systemctl --user status {{ unit_name }} --no-pager -l"
|
|
- "journalctl --user -u {{ unit_name }} --no-pager -n 60"
|
|
- "podman logs --tail 60 {{ container_name }}"
|
|
register: nextcloud_diag
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Show them
|
|
ansible.builtin.debug:
|
|
msg: "{{ nextcloud_diag.results | map(attribute='stdout') | list }}"
|
|
|
|
- name: Fail
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
Nextcloud is not serving — see the diagnostics above. A "Permission
|
|
denied" under /usr/local/etc/php/conf.d, anything about `db` or `redis`,
|
|
and "downgrading is not supported" each point somewhere different.
|
|
|
|
# Nextcloud wants `cron.php` every five minutes. This has to be *this user's*
|
|
# crontab, not root's: root cannot `podman exec` into another user's rootless
|
|
# containers. It works without any environment plumbing because the caddy
|
|
# playbook enables lingering, so /run/user/<uid> exists whether or not anyone is
|
|
# logged in — which is where podman keeps the state `exec` needs.
|
|
- name: Schedule Nextcloud's background jobs
|
|
ansible.builtin.cron:
|
|
name: Nextcloud background jobs
|
|
minute: "{{ cron_minute }}"
|
|
job: >-
|
|
/usr/bin/podman exec -u www-data {{ container_name }}
|
|
php {{ webroot }}/cron.php
|