diff --git a/nextcloud/readme.md b/nextcloud/readme.md index 6c66fb8..9cdc587 100644 --- a/nextcloud/readme.md +++ b/nextcloud/readme.md @@ -37,7 +37,7 @@ nextcloud/ └── roles/ ├── postgres/ # the database, and this app's network on the cluster ├── valkey/ # this app's network on the cache - ├── nextcloud/ # webroot, env file, unit, session config, cron + ├── nextcloud/ # webroot, env file, unit, cron └── caddy/ # the site block in the shared Caddyfile ``` @@ -48,8 +48,7 @@ nextcloud/ | `~/.config/containers/systemd/*.container.d/50-nextcloud.conf` | attaches Caddy, Postgres and Valkey to it | | `~/.config/containers/systemd/*.container.d/10-network-aliases.conf` | the shared `db` and `redis` aliases | | `~/podman-data/nextcloud/` | the whole `/var/www/html` | -| `~/podman-data/nextcloud/nextcloud.env` | `POSTGRES_*` + `REDIS_HOST`, mode `0600` | -| `~/podman-data/nextcloud/redis-session.ini` | mounted into the image; see [below](#the-php-session-file) | +| `~/podman-data/nextcloud/nextcloud.env` | `POSTGRES_*`, mode `0600` | ## What is state, and what is not @@ -88,12 +87,11 @@ of downtime for every attached app; see **Valkey.** The same, via [valkey/roles/attach](../valkey/roles/attach/tasks/main.yaml), with -`NetworkAlias=redis`. `redis` is not a name this playbook chose: `REDIS_HOST` is -what the image writes PHP's session config from, and Redis is where the setup -wizard puts `memcache.locking`. An unresolvable `redis` therefore breaks uploads -at write time rather than at startup, which is why the attach is flushed before -the app starts as well. The recreate costs a few seconds of cache availability; -the keys survive it (see [valkey/readme.md](../valkey/readme.md)). +`NetworkAlias=redis`: the attach puts `nextcloud_net` on the cache container and +makes the host `redis` resolve from Nextcloud. What *uses* it is `config.php`, and +this playbook does not write that — see "Redis" below. The recreate costs a few +seconds of cache availability, and the keys survive it +(see [valkey/readme.md](../valkey/readme.md)). ## Background jobs @@ -127,7 +125,7 @@ Quadlet units is the alternative, and it is two small files. | `container_port` | `80`, what Caddy proxies to | | `env_file` | `~/podman-data/nextcloud/nextcloud.env`, mode `0600` | | `pg_*` / `db_host` | The database, its credentials, and the `db` alias | -| `valkey_container` / `redis_host` | The cache unit, and the `redis` alias | +| `valkey_container` / `valkey_unit` | The cache unit this app attaches to | | `nextcloud_admin_user` / `nextcloud_admin_password` | Empty by default; set both to install unattended | | `cron_minute` | The `cron.php` interval | | `caddy_*` | The shared Caddy unit and its Caddyfile | @@ -147,31 +145,34 @@ owned by `tanshu` and plain `tar` and `rm -rf` keep working on it. `123456`. It lives in `nextcloud.env` at mode `0600` rather than in the world-readable unit file. -## The PHP session file +## Redis -The entrypoint generates PHP's session config on every start, with a redirect: +The Valkey attach above makes the host `redis` resolve from the container, and +that is all this playbook does about Redis: which caches Nextcloud uses is +`config.php`'s business, and this playbook does not write it. -```sh -/entrypoint.sh:121 - } > /usr/local/etc/php/conf.d/redis-session.ini -``` - -That directory belongs to the image's root — the php base image creates it with -`mkdir -p "$PHP_INI_DIR/conf.d"` and never chowns it — so the write is refused, -the entrypoint exits `2` before Apache is reached, `Restart=always` retries it, -systemd hits its start limit, and the unit ends up **`failed`** with the only -explanation in the journal: +It deliberately does **not** set `REDIS_HOST`, which is the one Redis variable the +image acts on. That variable exists to write PHP's session config, with a redirect +into `/usr/local/etc/php/conf.d` — a directory the image owns as root and this +container cannot write: ``` cannot create /usr/local/etc/php/conf.d/redis-session.ini: Permission denied ``` -The role creates `~/podman-data/nextcloud/redis-session.ini` and the unit binds it -over that exact path. A bind-mounted file replaces the directory entry, so the -directory's ownership stops mattering and the write lands on a file owned by -`podman_user`. Apache's own runtime directories need none of this — the same base -image chowns `/var/run/apache2`, `/var/lock/apache2` and `/var/log/apache2` to -`www-data` and chmods them `1777`. +The entrypoint exits `2` before Apache is reached, `Restart=always` retries it, and +systemd's start limit marks the unit `failed`. Leaving `REDIS_HOST` unset skips +that write entirely; the only thing lost is PHP session storage, which falls back +to the container's own filesystem. + +So to put Nextcloud's caches on Valkey, set it in `config.php` — `memcache.locking` +and `memcache.distributed` to `\OC\Memcache\Redis`, and `redis` `host` to `redis`: + +```bash +podman exec -u www-data nextcloud php occ config:system:set memcache.distributed --value='\OC\Memcache\Redis' +podman exec -u www-data nextcloud php occ config:system:set memcache.locking --value='\OC\Memcache\Redis' +podman exec -u www-data nextcloud php occ config:system:set redis host --value=redis +``` ## First run and updates diff --git a/nextcloud/roles/nextcloud/tasks/main.yaml b/nextcloud/roles/nextcloud/tasks/main.yaml index 64e1ebc..3b1459c 100644 --- a/nextcloud/roles/nextcloud/tasks/main.yaml +++ b/nextcloud/roles/nextcloud/tasks/main.yaml @@ -17,22 +17,6 @@ group: "{{ podman_user }}" mode: '0755' -# The entrypoint generates PHP's session config with a redirect into -# /usr/local/etc/php/conf.d, a directory the image creates as root and never -# chowns. That write is refused, the entrypoint exits 2, and the unit crash-loops -# into systemd's start limit. Bind-mounting a file over that path gives the write -# somewhere it is allowed; the file has to exist first, or the mount lands as a -# directory. -- name: Create the file the entrypoint writes PHP's session config to - ansible.builtin.file: - path: "{{ host_directory }}/redis-session.ini" - state: touch - owner: "{{ podman_user }}" - group: "{{ podman_user }}" - mode: '0644' - access_time: preserve - modification_time: preserve - # 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. diff --git a/nextcloud/roles/nextcloud/templates/nextcloud.container.j2 b/nextcloud/roles/nextcloud/templates/nextcloud.container.j2 index c3a59cc..d3c4e44 100644 --- a/nextcloud/roles/nextcloud/templates/nextcloud.container.j2 +++ b/nextcloud/roles/nextcloud/templates/nextcloud.container.j2 @@ -32,11 +32,6 @@ Network={{ network_name }}.network # rsyncs new code in and upgrades the database in place. Volume={{ host_directory }}:{{ webroot }} -# PHP's session config, which the entrypoint generates on every start. Without -# this mount it cannot: /usr/local/etc/php/conf.d belongs to the image's root. -# See the file task in roles/nextcloud for the failure this prevents. -Volume={{ host_directory }}/redis-session.ini:/usr/local/etc/php/conf.d/redis-session.ini - [Service] Restart=always # A first start initialises the webroot; a start after an image bump rsyncs code diff --git a/nextcloud/roles/nextcloud/templates/nextcloud.env.j2 b/nextcloud/roles/nextcloud/templates/nextcloud.env.j2 index fe86646..be91dbe 100644 --- a/nextcloud/roles/nextcloud/templates/nextcloud.env.j2 +++ b/nextcloud/roles/nextcloud/templates/nextcloud.env.j2 @@ -4,13 +4,16 @@ # Mode 0600: this file is read by systemd, and POSTGRES_PASSWORD is in it. # # POSTGRES_* are what the entrypoint installs with on a fresh webroot; on an -# installed instance they only have to agree with config.php. REDIS_HOST is live on -# every start — the entrypoint regenerates PHP's session config from it. +# installed instance they only have to agree with config.php. +# +# REDIS_HOST is deliberately absent: it makes the entrypoint write PHP's session +# config into /usr/local/etc/php/conf.d, which the image owns as root and this +# container cannot write — the unit crash-loops on it. Redis is config.php's +# business, not this variable's. See readme.md, "Redis". POSTGRES_DB={{ pg_database }} POSTGRES_USER={{ pg_user }} POSTGRES_PASSWORD={{ pg_password }} POSTGRES_HOST={{ db_host }} -REDIS_HOST={{ redis_host }} {% if nextcloud_admin_user | length > 0 and nextcloud_admin_password | length > 0 %} # Consumed only while there is no config.php, by the entrypoint's unattended # install. Empty by default, which leaves the setup wizard in charge. diff --git a/nextcloud/roles/valkey/tasks/main.yaml b/nextcloud/roles/valkey/tasks/main.yaml index 515104b..fc7774c 100644 --- a/nextcloud/roles/valkey/tasks/main.yaml +++ b/nextcloud/roles/valkey/tasks/main.yaml @@ -8,10 +8,9 @@ # asserts that nextcloud.network already exists, and notifies the "Restart # Valkey" handler defined in this role. # -# `redis` is the alias the app connects to, not the container name: REDIS_HOST is -# what the image writes the PHP session handler from, and what this play puts in -# config.php's Redis configuration on an unattended install. Renaming it would -# mean editing a live instance's configuration for nothing. +# `redis` is the alias the app connects to, not the container name: it is the host +# an instance configured for Redis has in config.php. This playbook does not write +# that configuration, it only makes the name resolvable. - name: Attach Nextcloud to Valkey ansible.builtin.include_tasks: "{{ playbook_dir }}/../valkey/roles/attach/tasks/main.yaml" diff --git a/nextcloud/vars/default.yml b/nextcloud/vars/default.yml index f4964b4..f6371ab 100644 --- a/nextcloud/vars/default.yml +++ b/nextcloud/vars/default.yml @@ -58,12 +58,11 @@ pg_user: postgres pg_password: '123456' pg_database: nextcloud -# Valkey, and the alias its attach role adds. REDIS_HOST is not cosmetic: -# config.php puts `memcache.locking` on Redis, so an unresolvable `redis` breaks -# uploads rather than merely slowing them down. +# Valkey, which the attach role puts this app's network on with the alias +# `redis`. Whether Nextcloud uses it is config.php's decision: the cache and +# `memcache.locking` live there, not in this playbook. See readme.md. valkey_container: valkey valkey_unit: "{{ valkey_container }}.service" -redis_host: redis caddy_name: caddy caddyfile_path: "{{ podman_data_dir }}/{{ caddy_name }}/conf/Caddyfile"