diff --git a/nextcloud/readme.md b/nextcloud/readme.md index d50de65..6cc0890 100644 --- a/nextcloud/readme.md +++ b/nextcloud/readme.md @@ -136,10 +136,19 @@ that file wholesale — it also sets `host_directory`. Keep them in sync. Like Caddy, the play carries no `become`, so there is no guard against connecting as `root`; that check lives in `caddy/playbook.yml`. -`UserNS=keep-id:uid=33,gid=33`. `33` is `www-data`, the uid the webroot is owned -by: the entrypoint rsyncs the code in as that uid and Apache runs its workers as -it. `keep-id` maps that one uid onto the connection user, so the webroot stays -owned by `tanshu` and plain `tar` and `rm -rf` keep working on it. +Two lines decide who the container is, and both are needed: + +- **`UserNS=keep-id:uid=33,gid=33`** — `33` is `www-data`, the uid the webroot is + owned by: the entrypoint rsyncs the code in as that uid and Apache runs its + workers as it. `keep-id` maps that one uid onto the connection user, so the + webroot stays owned by `tanshu` and plain `tar` and `rm -rf` keep working on it. +- **`User=0`** — `keep-id` also runs the container *process* as the mapped uid, + i.e. as `www-data`, and the image's entrypoint is written to start as root. It + fails at the first thing that needs it: apache2 as `www-data` cannot bind port + 80 (`(13)Permission denied: AH00072: make_sock: could not bind to address + [::]:80`). Forcing the process back to root fixes that, and does not disturb the + mapping above — `www-data` is still `tanshu`, so the webroot is still `tanshu`'s + and `occ` still runs as `www-data` through the entrypoint's own `su`. `POSTGRES_PASSWORD` is committed in plaintext, like the other playbooks' shared `123456`. It lives in `nextcloud.env` at mode `0600` rather than in the @@ -253,8 +262,16 @@ journalctl --user -u nextcloud.service --no-pager -n 60 podman ps -a --filter name=nextcloud ``` -`journalctl` is the one that matters: the entrypoint's own output is there. A -`failed` unit needs its start-limit counter cleared before it will try again: +`journalctl` is the one that matters: the entrypoint's own output is there. The +three failures this playbook has actually produced, and what each one means: + +| In the journal | Cause | +|---|---| +| `parsing file .../nextcloud.env: no such file or directory`, `status=125` | the webroot was removed without re-running the play | +| `make_sock: could not bind to address [::]:80` with `Permission denied` | the container process is not root, so it may not use a privileged port — the unit's `User=0` is missing | +| `cannot create /usr/local/etc/php/conf.d/...: Permission denied` | `REDIS_HOST` is set; the entrypoint cannot write that path in the image (see [Redis](#redis)) | + +A `failed` unit needs its start-limit counter cleared before it will try again: ```bash systemctl --user reset-failed nextcloud.service diff --git a/nextcloud/roles/nextcloud/tasks/main.yaml b/nextcloud/roles/nextcloud/tasks/main.yaml index dc2ccd2..714d1a8 100644 --- a/nextcloud/roles/nextcloud/tasks/main.yaml +++ b/nextcloud/roles/nextcloud/tasks/main.yaml @@ -89,13 +89,10 @@ - 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 is not serving — the reason is in the diagnostics above, and + readme.md, "Debugging a unit that will not start", names the failures + this playbook has produced before. 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 diff --git a/nextcloud/roles/nextcloud/templates/nextcloud.container.j2 b/nextcloud/roles/nextcloud/templates/nextcloud.container.j2 index d3c4e44..b43c2a3 100644 --- a/nextcloud/roles/nextcloud/templates/nextcloud.container.j2 +++ b/nextcloud/roles/nextcloud/templates/nextcloud.container.j2 @@ -17,10 +17,18 @@ Image={{ image }} # Maps the container's www-data (uid {{ app_uid }}) onto {{ podman_user }}, so the # webroot stays owned by {{ podman_user }} and no subuid-owned files appear. The -# entrypoint rsyncs the code in with --chown www-data:www-data and Apache runs its -# workers as that account. +# entrypoint rsyncs the code in as www-data and Apache runs its workers as it. UserNS=keep-id:uid={{ app_uid }},gid={{ app_gid }} +# keep-id also runs the container *process* as that same uid, i.e. as www-data — +# which takes away the root the image's entrypoint is written for, and it fails at +# the first thing that needs it: apache2 as www-data cannot bind port 80 +# ("(13)Permission denied: AH00072: make_sock: could not bind to address [::]:80"). +# Forcing the process back to root restores it, and changes nothing about the +# mapping above: www-data is still {{ podman_user }}, so the webroot is still +# {{ podman_user }}'s and occ still runs as www-data via the entrypoint's su. +User=0 + # The app's environment, read by systemd rather than by Ansible. Mode 0600, # because POSTGRES_PASSWORD is in it and this unit file is world-readable. EnvironmentFile={{ env_file }}