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>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-30 14:09:32 +02:00
1 parent d7de6e3805
commit 82452789c9
4 files changed
+77 -1

No files matched your search

@@ -94,6 +94,10 @@ def access_token_via_login(username, password):
"password": password,
},
)
if step.get("type") != "create_entry":
raise SystemExit(
f"home assistant refused the login of {username}: {json.dumps(step)}"
)
return request(
"/auth/token",
{
@@ -28,12 +28,23 @@
register: homeassistant_provision
retries: 30
delay: 10
until: homeassistant_provision.rc == 0
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 }}"
@@ -0,0 +1,59 @@
---
- name: "🔑 Write the rotated owner password into the hub's auth store"
ansible.builtin.command:
argv:
- container
- exec
- -i
- "{{ HOMEASSISTANT_LOCAL_CID }}" # nocheck: container-exec-resolver
- hass
- --script
- auth
- --config
- "{{ HOMEASSISTANT_CONFIG_DIR }}"
- change_password
- "{{ HOMEASSISTANT_ADMIN_USERNAME }}"
- "{{ HOMEASSISTANT_ADMIN_PASSWORD }}"
changed_when: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "🔄 Restart the hub so it reads the store it no longer has in memory"
ansible.builtin.command:
argv:
- container
- restart
- "{{ HOMEASSISTANT_LOCAL_CID }}" # nocheck: container-exec-resolver
changed_when: true
- name: "🔑 Mint the long-lived token now that the hub accepts the rotated password"
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
changed_when: "'CHANGED' in homeassistant_provision.stdout"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
@@ -11,6 +11,8 @@ HOMEASSISTANT_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }
HOMEASSISTANT_ADMIN_USERNAME: "{{ lookup('users', 'administrator').username }}"
HOMEASSISTANT_ADMIN_PASSWORD: "{{ lookup('config', application_id, 'secrets.credentials.administrator_password') }}"
HOMEASSISTANT_TOKEN_CLIENT_NAME: "infinito-mcp"
HOMEASSISTANT_CONFIG_DIR: "{{ lookup('volume', application_id, 'homeassistant_config').target }}"
HOMEASSISTANT_LOGIN_REFUSED: "home assistant refused the login of"
HOMEASSISTANT_MCP_ACCOUNT: "{{ lookup('users', 'mcp-web-app-homeassistant') }}"
HOMEASSISTANT_MCP_ACCOUNT_USERNAME: "{{ HOMEASSISTANT_MCP_ACCOUNT.username }}"
HOMEASSISTANT_MCP_ACCOUNT_NAME: "Infinito MCP"