Files
playbooks/nextcloud/roles/nextcloud/tasks/main.yaml
T
tanshu b5a7328a27 Say what a missing webroot looks like, and stop pointing at a removed failure
The failure that prompted this is the environment file being absent:

    Error: parsing file ".../nextcloud.env": ... no such file or directory
    nextcloud.service: Main process exited, code=exited, status=125/n/a

The play creates that file before it installs or starts anything, so this cannot
come from a play run: it is a start outside one, after ~/podman-data/nextcloud was
removed. The unit is WantedBy=default.target, so it retries at boot and fails the
same way until the play is run again. The readme now says so, because the fix is
not obvious from a 125.

The rescue message was also stale: it sent readers to look for "Permission denied"
under /usr/local/etc/php/conf.d, which was the PHP session file this playbook no
longer creates or mounts, and it had lost the reset-failed hint when the message
was shortened. It now names the failure that actually happens (a removed webroot),
keeps the database/cache/image pointers that are still real, and gives the command
for a unit left failed by an earlier attempt.
2026-10-16 15:57:45 +05:30

112 lines
4.4 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 — the reason is in the diagnostics above.
`no such file or directory` for nextcloud.env or a path under
/var/www/html means this host's webroot was removed without re-running
this play, and anything about `db`, `redis` or "downgrading is not
supported" points at the database, the cache or the image instead. If
the unit is in the `failed` state from an earlier attempt:
systemctl --user reset-failed {{ unit_name }}
# 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