Files
computer-playbook/roles/web-app-homeassistant/tasks/utils/mcp/provision.yml
kevinveenbirkenbachandClaude Opus 5 82452789c9 fix(web-app-homeassistant): realign the owner password the hub kept from onboarding
Home Assistant writes the owner password into the auth store on its volume
during onboarding and never reads the environment again. When the deploy
rotates credentials the container gets the new value and the store keeps the
old one, so the login that every later step needs is refused.

The provisioning script returned that refusal as an ordinary step, and the
/auth/token call that followed failed on a missing result key - a message about
the transport for a problem in the credentials. It now exits on any login flow
that does not reach create_entry, naming the account.

That turns a five-minute wait into an immediate branch: the retry loop stopped
only on success, so a password that will never be accepted was tried thirty
times. The loop now also breaks on the refusal, and only that one outcome is
allowed past failed_when - a crash or a traceback still fails the task, which
matters because the step after this reads a token out of stdout.

hass --script auth change_password reaches the store through the filesystem, so
it needs no working login. The hub holds the store in memory, hence the restart
before the same mint runs again.

e3caa074b2 fixed the account one layer in: the MCP service account, through
admin_change_password, which needs an admin token. This is the login that
issues that token, so without it that fix cannot be reached at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 14:09:32 +02:00

71 lines
2.6 KiB
YAML

---
- name: "🔑 Mint the long-lived token and create the MCP config entry"
ansible.builtin.command:
argv:
- container
- exec
- -i
- -e
- "HA_PORT={{ HOMEASSISTANT_PORT_INTERNAL }}"
- -e
- "HA_USERNAME={{ HOMEASSISTANT_ADMIN_USERNAME }}"
- -e
- "HA_PASSWORD={{ HOMEASSISTANT_ADMIN_PASSWORD }}"
- -e
- "HA_TOKEN_CLIENT_NAME={{ HOMEASSISTANT_TOKEN_CLIENT_NAME }}"
- -e
- "HA_SERVICE_USERNAME={{ HOMEASSISTANT_MCP_ACCOUNT_USERNAME }}"
- -e
- "HA_SERVICE_PASSWORD={{ HOMEASSISTANT_MCP_ACCOUNT_PASSWORD }}"
- -e
- "HA_SERVICE_NAME={{ HOMEASSISTANT_MCP_ACCOUNT_NAME }}"
- -e
- "HA_SERVICE_GROUP={{ HOMEASSISTANT_MCP_ACCOUNT_GROUP }}"
- "{{ HOMEASSISTANT_LOCAL_CID }}" # nocheck: container-exec-resolver
- python3
- "-"
stdin: "{{ lookup('file', 'python/provision_mcp.py') }}"
register: homeassistant_provision
retries: 30
delay: 10
until: homeassistant_provision.rc == 0 or (HOMEASSISTANT_LOGIN_REFUSED in homeassistant_provision.stderr)
failed_when:
- homeassistant_provision.rc != 0
- HOMEASSISTANT_LOGIN_REFUSED not in homeassistant_provision.stderr
changed_when: "'CHANGED' in homeassistant_provision.stdout"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
delegate_to: "{{ HOMEASSISTANT_HOST_NODE }}"
run_once: true
- name: "🔑 Realign the owner password the hub still holds from its onboarding"
ansible.builtin.include_tasks:
file: realign_owner.yml
apply:
delegate_to: "{{ HOMEASSISTANT_HOST_NODE }}"
when: HOMEASSISTANT_LOGIN_REFUSED in homeassistant_provision.stderr
run_once: true
- name: "🔧 Take the minted token from the provisioning output"
ansible.builtin.set_fact:
homeassistant_mcp_token: "{{ homeassistant_provision.stdout_lines | last | trim }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
run_once: true
- name: "🛑 Refuse a hub that issued no usable token"
ansible.builtin.assert:
that:
- homeassistant_mcp_token | length >= 50
fail_msg: >-
The hub did not return a long-lived access token, so every MCP client
would be configured with an empty bearer and fail at first use.
- name: "💾 Persist the token 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: "{{ homeassistant_mcp_token }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"