Running this play against an empty ~/podman-data/nextcloud does not fail. It
creates the webroot, writes the environment file, starts the image, and the
entrypoint initialises a fresh, uninstalled instance there -- its own code
rsynced in, data/.ocdata created, and no config.php, because there is no
NEXTCLOUD_ADMIN_USER to install with.
Copying monoco's tree on top of that then merges two versions of the code, which
is the drift the rest of this role warns about, and nothing in the play's output
says so. The role now stats config/config.php before the container starts and
warns when it is missing, naming both cases: a copy that never landed, and a play
that ran before the copy.
It warns rather than fails because installing a new instance through the web UI is
a legitimate use of this playbook and has no config.php until the installer
finishes. readme.md gains the recovery step next to the deploy step, including
why the rm -rf needs no `podman unshare`.
The first real run of this play stopped at "Verify Nextcloud is running":
fatal: [emily]: FAILED! => {"cmd": ["systemctl", "--user", "is-active",
"nextcloud.service"], "rc": 3, "stdout": "activating"}
Nothing was wrong with the container. `systemctl start` returns while the unit
is still `activating` -- the start job completes before conmon has signalled
readiness -- and `is-active` exits 3 for that state, so checking once reports a
healthy start as a failure. Every other app role here checks is-active only
after a wait that already tolerates startup (postgres waits on pg_isready,
valkey on valkey-cli ping, bitwarden on its healthcheck); Nextcloud is the one
where the check comes first, because its readiness gates are the expensive ones.
The wait retries only through the transition. A unit that is `failed` or
`inactive` is not going to become `active` by waiting, so that fails immediately
with the state in the message rather than five minutes later. What genuinely
needs patience -- the entrypoint rsyncing code and running `occ upgrade` before
Apache answers -- is the readiness gate that follows, which already has the
richer rescue.
Replaces the docker_container playbook, which bind-mounted /var/lib/nextcloud at
/var/www/html on monoco and ran cron.php from root's crontab. Nextcloud now runs
on emily behind the shared Caddy, with its database in the shared Postgres and
its cache in the shared Valkey, each attached the way every other service
attaches to them.
/var/www/html is the unusual part: it holds the code as well as the state --
version.php, core/, lib/ and apps/ alongside config/config.php, data/ and
custom_apps/ -- and the entrypoint's version check reads version.php from the
mount. That is why the migration copies the whole tree rather than only the three
irreplaceable directories (docs/backup-plan.md 2.1 lists the rest as regenerable,
and they are, but only from an image that is the same version or newer). It is
also what makes an image bump an in-place upgrade: a newer image rsyncs its code
in and runs `occ upgrade` on first start, and an older one refuses to start at
all, so the version on both hosts is worth checking before the copy.
UserNS=keep-id:uid=33,gid=33. 33 is www-data, the uid the server actually writes
as -- `docker exec nextcloud id -u` reports 0, because the entrypoint drops
privileges after starting (docs/podman-compat-audit.md 3.2). 33 is also what the
data is owned by on monoco, so the copy needs no chown pass: extracting as the
connection user is already the ownership the container expects. One file is
created by container-root and never chowned, the entrypoint's
nextcloud-init-sync.lock, so the role pre-creates it -- the entrypoint truncates
that file rather than recreating it, so its owner then never changes, and the
webroot ends up with no subuid-owned file in it.
The Valkey attach is not optional. config.php carries memcache.locking pointed at
Redis, written by the image from REDIS_HOST, and the same variable rewrites PHP's
session-handler config on every start -- so an unresolvable `redis` does not just
make things slower, it fails uploads at write time. Hence the `redis` alias
rather than the container name, and hence roles/valkey flushing handlers before
the app starts, the same as roles/postgres.
cron.php moves from root's crontab to a systemd user timer, because root cannot
`podman exec` into another user's rootless containers and there is no container
user to own a crontab (docs/podman-migration.md 8b). Unlike the Quadlet units
this pair is plain and is genuinely enabled, which is what brings it back after a
reboot; journalctl --user -u nextcloud-cron shows every run, which the crontab
never did.
Readiness is three gates, because no one of them is sufficient: is-active (which
Restart=always satisfies even while crash-looping), `occ status` reporting
installed, and status.php served over loopback by Apache rather than by the PHP
CLI. The second is followed by a wait for maintenance mode to clear rather than
an assert: maintenance mode is on for the whole of a first-start `occ upgrade`,
so asserting it off would fail a healthy deploy halfway through. The case that
never clears is a config.php copied with `maintenance => true`, which
docs/backup-plan.md 3.4 tells an operator to set before a consistent dump, and
which is invisible from outside because status.php is exempt from maintenance
mode -- so the play names it and prints the command that clears it.
Register and attach ordering is the same as the other service playbooks: register
runs in pre_tasks, because the unit references nextcloud.network and Quadlet
fails unit generation when that file does not exist, and both attaches flush
handlers before the app starts, which is also what pulls Caddy onto the network.
The Caddy snippet is unchanged, HSTS header included; only caddyfile_path moves,
from /var/lib/caddy/conf/Caddyfile to ~/podman-data/caddy/conf/Caddyfile. The
Docker-era roles/network is deleted rather than ported: it existed to attach an
already-created container to a network with the `redis` alias, and
podman_network has no connected:/appends: equivalent
(docs/podman-compat-audit.md 3.3). Nothing is published host-side; Caddy reaches
nextcloud:80 over nextcloud_net.
The rootless-Podman prerequisites are not applied here: they are the shared task
file at caddy/roles/podman, and a host that has run caddy/playbook.yml has them.
valkey/playbook.yml has to have been run first, or the first Restart Valkey
handler fails against a unit that does not exist yet.
Updated the web nginx.conf and the source is not given in the readme.
Restricted to gondor.
Webserver image and container names are now picked from the vars file.