fix(nextcloud): run occ on the node that hosts the task

NEXTCLOUD_DOCKER_EXEC resolved the container id through a subshell evaluated on the manager. When the manager holds no running replica, resolve_container_id.sh falls back to a cluster-wide lookup with no node filter and returns the id of a task on another node, which the local daemon does not know: 'No such container', sixty times, then the wait in 02_config.yml fails the deploy.

08_manager_ops.yml now resolves the hosting node and its node-local container id once via resolve_host_cid.yml and delegates every include there with apply:. Redefining NEXTCLOUD_DOCKER_EXEC in terms of NEXTCLOUD_LOCAL_CID lets all seventeen consumers inherit the fix without touching a task line. It re-resolves after the handler flush and after the upgrade, the two points where a rolling restart can move the task. Compose degenerates to delegate-to-self with the previous container name.

Four call sites built their exec inline because they need -u root or -e flags, and their subshell would have run on the worker where resolve-container-id exits 64 for want of a manager. Without converting them, delegation would have made things worse rather than better.

This is the outermost of four stacked defects and not the reason the job was red: the manager was empty because the object store had no free volume slots, which left a half-installed instance holding the init lock. Those layers are fixed separately. This one is a genuine cross-node bug that the rest of the repo already solved with the same pattern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-02 14:04:17 +02:00
1 parent 1f91114ce5
commit a963280c1c
6 files changed
+38 -8

No files matched your search

+2 -3
View File
@@ -20,11 +20,10 @@
(occ_status.stdout | default('')) is search('installed: true')
changed_when: false
# nocheck: container-exec-resolver NEXTCLOUD_CONTAINER_ADDRESS is a swarm-mode subshell that only a shell can expand; command/argv would pass it literally
- name: "📄 Install the infinito additional-config loader into config/"
ansible.builtin.shell:
cmd: >-
container exec -i -u root {{ NEXTCLOUD_CONTAINER_ADDRESS }}
cmd: >- # nocheck: container-exec-resolver NEXTCLOUD_LOCAL_CID resolved at delegate_to runtime; container_address would return a manager-only subshell
container exec -i -u root {{ NEXTCLOUD_LOCAL_CID | quote }}
sh -c 'cat > /var/www/html/config/zzz-infinito.config.php
&& chmod 0444 /var/www/html/config/zzz-infinito.config.php'
stdin: "{{ lookup('template', 'include.php.j2') }}"
+1 -1
View File
@@ -1,7 +1,7 @@
---
- name: ✅ Ensure Nextcloud administrator is in the 'admin' group
shell: >
container exec -u {{ NEXTCLOUD_DOCKER_USER }} {{ NEXTCLOUD_CONTAINER_ADDRESS }}
{{ NEXTCLOUD_DOCKER_EXEC }}
php occ group:adduser admin {{ NEXTCLOUD_ADMINISTRATOR_USERNAME }}
register: add_admin_to_group
changed_when: "not ASYNC_ENABLED and 'Added user' in add_admin_to_group.stdout"
@@ -1,10 +1,26 @@
---
- name: 🔍 Resolve the Nextcloud hosting node and container id
include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
vars:
resolve_service_key: "{{ NEXTCLOUD_SERVICE }}"
resolve_output_prefix: NEXTCLOUD
resolve_compose_cid: "{{ NEXTCLOUD_CONTAINER_ADDRESS }}"
- name: 🔧 Setup config.php
include_tasks: 02_config.yml
apply:
delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
- name: 🚿 Flush all handlers immediately so that occ can be used
meta: flush_handlers
- name: 🔍 Re-resolve the hosting node after the handler flush
include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
vars:
resolve_service_key: "{{ NEXTCLOUD_SERVICE }}"
resolve_output_prefix: NEXTCLOUD
resolve_compose_cid: "{{ NEXTCLOUD_CONTAINER_ADDRESS }}"
- name: ⏳ Wait until Redis is ready (PONG)
shell: "container exec {{ NEXTCLOUD_REDIS_CONTAINER_ADDRESS }} redis-cli ping"
register: redis_ping
@@ -16,6 +32,15 @@
- name: 🔄 Update\Upgrade Nextcloud
include_tasks: 03_upgrade.yml
apply:
delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
- name: 🔍 Re-resolve the hosting node after the upgrade restarts
include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
vars:
resolve_service_key: "{{ NEXTCLOUD_SERVICE }}"
resolve_output_prefix: NEXTCLOUD
resolve_compose_cid: "{{ NEXTCLOUD_CONTAINER_ADDRESS }}"
- name: 📂 Load system configuration steps
include_tasks: "{{ nextcloud_step }}"
@@ -24,6 +49,8 @@
- 05_system_config.yml
loop_control:
loop_var: nextcloud_step
apply:
delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
- name: 🔧 Setup Nextcloud Plugins
include_tasks: 06_plugin.yml
@@ -34,6 +61,10 @@
plugin_key: "{{ plugin_item.key }}"
plugin_value: "{{ plugin_item.value }}"
when: NEXTCLOUD_PLUGINS_ENABLED | bool
apply:
delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
- name: 🔧 Reconcile administrator identities after plugin setup
include_tasks: 07_admin_identities.yml
apply:
delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
@@ -96,7 +96,7 @@
register: nextcloud_mm_oauth_url_set
changed_when: "' set to ' in nextcloud_mm_oauth_url_set.stdout"
# nocheck: container-exec-resolver NEXTCLOUD_CONTAINER_ADDRESS is a swarm-mode subshell that only a shell can expand; command/argv would pass it literally
# nocheck: container-exec-resolver NEXTCLOUD_LOCAL_CID resolved at delegate_to runtime; container_address would return a manager-only subshell
- name: 🔐 Store integration_mattermost OAuth credentials Nextcloud-encrypted (lazy)
ansible.builtin.shell:
cmd: >-
@@ -104,7 +104,7 @@
-e NC_APP_ID=integration_mattermost
-e NC_ENC_MODE=sensitive
-e "NC_ENC_B64={{ {'client_id': nextcloud_mm_client_id, 'client_secret': nextcloud_mm_client_secret} | to_json | b64encode }}"
{{ NEXTCLOUD_CONTAINER_ADDRESS }} php
{{ NEXTCLOUD_LOCAL_CID | quote }} php
stdin: "{{ lookup('file', 'set_encrypted_appconfig.php') }}"
register: nextcloud_mm_enc_set
changed_when: "'OK' in nextcloud_mm_enc_set.stdout"
@@ -58,7 +58,7 @@
-e 'NC_APP_ID=integration_zammad'
-e 'NC_ENC_MODE=sensitive'
-e 'NC_ENC_B64={{ {'client_id': nextcloud_zammad_client_id, 'client_secret': nextcloud_zammad_client_secret} | to_json | b64encode }}'
{{ NEXTCLOUD_CONTAINER_ADDRESS }}
{{ NEXTCLOUD_LOCAL_CID | quote }}
php
stdin: "{{ lookup('file', 'set_encrypted_appconfig.php') }}"
register: nextcloud_zammad_enc_set
+1 -1
View File
@@ -150,7 +150,7 @@ NEXTCLOUD_DOCKER_USER_ID: 82
NEXTCLOUD_DOCKER_USER: "www-data"
NEXTCLOUD_INTERNAL_OCC_COMMAND: "{{ [ NEXTCLOUD_DOCKER_WORK_DIRECTORY, 'occ'] | path_join }}"
NEXTCLOUD_DOCKER_EXEC: "container exec -u {{ NEXTCLOUD_DOCKER_USER }} {{ NEXTCLOUD_CONTAINER_ADDRESS }}"
NEXTCLOUD_DOCKER_EXEC: "container exec -u {{ NEXTCLOUD_DOCKER_USER }} {{ NEXTCLOUD_LOCAL_CID | quote }}" # nocheck: container-exec-resolver NEXTCLOUD_LOCAL_CID resolved at delegate_to runtime; container_address would return a manager-only subshell
NEXTCLOUD_DOCKER_EXEC_OCC: "{{ NEXTCLOUD_DOCKER_EXEC }} {{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}"
NEXTCLOUD_REDIS_CONTAINER_ADDRESS: "{{ lookup('container_address', application_id, 'redis') }}"