Run the Nextcloud container as root, with www-data still mapped onto the user
The second real run failed with:
(13)Permission denied: AH00072: make_sock: could not bind to address [::]:80
no listening sockets available, shutting down
`keep-id:uid=33` does not only build the mapping, it also runs the container
process as the mapped uid -- so the entrypoint and apache2 were running as
www-data, and www-data has no capabilities. Port 80 inside the container's own
network namespace therefore belongs to root. The caddy unit is the control: it
binds 80 and 443 with keep-id:uid=0, and container-root in a userns does have
NET_BIND_SERVICE.
Tested on emily before writing this, because the last two fixes here were reasoned
from the wrong model:
podman run --userns keep-id:uid=33,gid=33 IMG id -> uid=33(www-data)
podman run --userns keep-id:uid=33,gid=33 --user 0 IMG id -> uid=0(root)
So User=0 restores the process root, and it does not touch the mapping: www-data
is still the connection user, so the webroot stays theirs, the entrypoint's
--chown lands on them, and occ keeps running as www-data through the entrypoint's
own su. That is the configuration the image is written for -- it expects root to
bind the port and to chown the webroot -- which is the real argument for this
option over the two that also tested clean: --sysctl
net.ipv4.ip_unprivileged_port_start=80 and --cap-add NET_BIND_SERVICE. Both would
have fixed the symptom while leaving the entrypoint permanently unprivileged,
which is exactly what produced this failure and the redis-session.ini one before
it.
The readme's debugging section now lists all three failures this playbook has
produced and what each means, and the rescue message points there instead of
repeating a stale list.
This commit is contained in:
1 parent
b5a7328a27
commit
ecb8913153
3 files changed
+37
-15
No files matched your search
+23
-6
@@ -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
|
Caddy, the play carries no `become`, so there is no guard against connecting as
|
||||||
`root`; that check lives in `caddy/playbook.yml`.
|
`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
|
Two lines decide who the container is, and both are needed:
|
||||||
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
|
- **`UserNS=keep-id:uid=33,gid=33`** — `33` is `www-data`, the uid the webroot is
|
||||||
owned by `tanshu` and plain `tar` and `rm -rf` keep working on it.
|
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
|
`POSTGRES_PASSWORD` is committed in plaintext, like the other playbooks' shared
|
||||||
`123456`. It lives in `nextcloud.env` at mode `0600` rather than in the
|
`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
|
podman ps -a --filter name=nextcloud
|
||||||
```
|
```
|
||||||
|
|
||||||
`journalctl` is the one that matters: the entrypoint's own output is there. A
|
`journalctl` is the one that matters: the entrypoint's own output is there. The
|
||||||
`failed` unit needs its start-limit counter cleared before it will try again:
|
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
|
```bash
|
||||||
systemctl --user reset-failed nextcloud.service
|
systemctl --user reset-failed nextcloud.service
|
||||||
|
|||||||
@@ -89,13 +89,10 @@
|
|||||||
- name: Fail
|
- name: Fail
|
||||||
ansible.builtin.fail:
|
ansible.builtin.fail:
|
||||||
msg: >-
|
msg: >-
|
||||||
Nextcloud is not serving — the reason is in the diagnostics above.
|
Nextcloud is not serving — the reason is in the diagnostics above, and
|
||||||
`no such file or directory` for nextcloud.env or a path under
|
readme.md, "Debugging a unit that will not start", names the failures
|
||||||
/var/www/html means this host's webroot was removed without re-running
|
this playbook has produced before. If the unit is in the `failed` state
|
||||||
this play, and anything about `db`, `redis` or "downgrading is not
|
from an earlier attempt: systemctl --user reset-failed {{ unit_name }}
|
||||||
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*
|
# 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
|
# crontab, not root's: root cannot `podman exec` into another user's rootless
|
||||||
|
|||||||
@@ -17,10 +17,18 @@ Image={{ image }}
|
|||||||
|
|
||||||
# Maps the container's www-data (uid {{ app_uid }}) onto {{ podman_user }}, so the
|
# 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
|
# 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
|
# entrypoint rsyncs the code in as www-data and Apache runs its workers as it.
|
||||||
# workers as that account.
|
|
||||||
UserNS=keep-id:uid={{ app_uid }},gid={{ app_gid }}
|
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,
|
# 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.
|
# because POSTGRES_PASSWORD is in it and this unit file is world-readable.
|
||||||
EnvironmentFile={{ env_file }}
|
EnvironmentFile={{ env_file }}
|
||||||
|
|||||||
Reference in new issue
Block a user