--- - name: "๐Ÿ” List the accounts this instance knows" ansible.builtin.command: argv: - container - exec - -u - "{{ NEXTCLOUD_DOCKER_USER }}" - "{{ NEXTCLOUD_LOCAL_CID }}" # nocheck: container-exec-resolver - php - "{{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}" - user:list - --output=json register: nextcloud_mcp_accounts changed_when: false delegate_to: "{{ NEXTCLOUD_HOST_NODE }}" run_once: true - name: "๐Ÿ‘ค Create the account the MCP clients authenticate as" ansible.builtin.command: argv: - container - exec - -u - "{{ NEXTCLOUD_DOCKER_USER }}" - -e - "OC_PASS={{ NEXTCLOUD_MCP_USER_PASSWORD }}" - "{{ NEXTCLOUD_LOCAL_CID }}" # nocheck: container-exec-resolver - php - "{{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}" - user:add - --password-from-env - "--display-name={{ NEXTCLOUD_MCP_USER_DISPLAY_NAME }}" - "{{ NEXTCLOUD_MCP_USERNAME }}" when: NEXTCLOUD_MCP_USERNAME not in (nextcloud_mcp_accounts.stdout | from_json) changed_when: true no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" delegate_to: "{{ NEXTCLOUD_HOST_NODE }}" run_once: true - name: "๐Ÿ”‘ Set the account's password to the one this deployment declares" ansible.builtin.command: argv: - container - exec - -u - "{{ NEXTCLOUD_DOCKER_USER }}" - -e - "OC_PASS={{ NEXTCLOUD_MCP_USER_PASSWORD }}" - "{{ NEXTCLOUD_LOCAL_CID }}" # nocheck: container-exec-resolver - php - "{{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}" - user:resetpassword - --password-from-env - "{{ NEXTCLOUD_MCP_USERNAME }}" changed_when: true no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" delegate_to: "{{ NEXTCLOUD_HOST_NODE }}" run_once: true - name: "๐Ÿ” List the app passwords the MCP account already holds" ansible.builtin.command: argv: - container - exec - -u - "{{ NEXTCLOUD_DOCKER_USER }}" - "{{ NEXTCLOUD_LOCAL_CID }}" # nocheck: container-exec-resolver - php - "{{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}" - user:auth-tokens:list - "{{ NEXTCLOUD_MCP_USERNAME }}" - --output=json register: nextcloud_mcp_tokens_held changed_when: false no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" delegate_to: "{{ NEXTCLOUD_HOST_NODE }}" run_once: true - name: "๐Ÿงน Revoke the app passwords an earlier run minted under the managed name" ansible.builtin.command: argv: - container - exec - -u - "{{ NEXTCLOUD_DOCKER_USER }}" - "{{ NEXTCLOUD_LOCAL_CID }}" # nocheck: container-exec-resolver - php - "{{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}" - user:auth-tokens:delete - "{{ NEXTCLOUD_MCP_USERNAME }}" - "{{ item | string }}" loop: >- {{ nextcloud_mcp_tokens_held.stdout | from_json | selectattr('name', 'equalto', NEXTCLOUD_MCP_TOKEN_NAME) | map(attribute='id') | list }} changed_when: true no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" delegate_to: "{{ NEXTCLOUD_HOST_NODE }}" run_once: true - name: "๐Ÿ”‘ Mint an app password for the MCP clients" ansible.builtin.command: argv: - container - exec - -u - "{{ NEXTCLOUD_DOCKER_USER }}" - -e - "NC_PASS={{ NEXTCLOUD_MCP_USER_PASSWORD }}" - "{{ NEXTCLOUD_LOCAL_CID }}" # nocheck: container-exec-resolver - php - "{{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}" - user:auth-tokens:add - "{{ NEXTCLOUD_MCP_USERNAME }}" - --password-from-env - "--name={{ NEXTCLOUD_MCP_TOKEN_NAME }}" register: nextcloud_mcp_token_mint changed_when: true no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" delegate_to: "{{ NEXTCLOUD_HOST_NODE }}" run_once: true - name: "๐Ÿ”ง Take the minted app password from the command output" ansible.builtin.set_fact: nextcloud_mcp_token: "{{ nextcloud_mcp_token_mint.stdout_lines | last | trim }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" run_once: true - name: "๐Ÿ›‘ Refuse an instance that issued no usable app password" ansible.builtin.assert: that: - nextcloud_mcp_token | length >= 64 fail_msg: >- occ user:auth-tokens:add returned no app password, so every MCP client would be configured with an empty bearer and fail at first use. - name: "๐Ÿ’พ Persist the app password so MCP clients can read it" ansible.builtin.include_role: name: sys-token-store tasks_from: write.yml vars: sys_token_store_user_key: "{{ lookup('config', application_id, 'mcp.credential.owner') }}" sys_token_store_app: "{{ application_id }}" sys_token_store_token: "{{ nextcloud_mcp_token }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"